Browse Source

Fixing halftone output and adding helpers

Disabled tracing in tests.


Former-commit-id: 2d04ae7af4c5ceeb67b8632b1b39cb375b9dec30
Former-commit-id: c51ace483233885b16dd9b69f3f6d397125c80c0
af/merge-core
James South 11 years ago
parent
commit
d628fb0096
  1. 30
      src/ImageProcessor.Playground/Program.cs
  2. 8
      src/ImageProcessor/Common/Extensions/DoubleExtensions.cs
  3. 19
      src/ImageProcessor/Common/Extensions/IntegerExtensions.cs
  4. 12
      src/ImageProcessor/Imaging/Colors/CmykColor.cs
  5. 47
      src/ImageProcessor/Imaging/Colors/ColorExtensions.cs
  6. 13
      src/ImageProcessor/Imaging/Colors/HSLAColor.cs
  7. 8
      src/ImageProcessor/Imaging/Colors/YCbCrColor.cs
  8. 44
      src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs
  9. 33
      src/ImageProcessor/Imaging/Helpers/ImageMaths.cs
  10. 30
      src/ImageProcessor/Processors/Halftone.cs

30
src/ImageProcessor.Playground/Program.cs

@ -50,23 +50,23 @@ namespace ImageProcessor.PlayGround
// Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask.png"));
// Image overlay = Image.FromFile(Path.Combine(resolvedPath, "imageprocessor.128.png"));
FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "ej.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "2008.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "new-york.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "mountain.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "Arc-de-Triomphe-France.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "Martin-Schoeller-Jack-Nicholson-Portrait.jpeg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "crop-base-300x200.jpg"));
//FileInfo fileInfo = new FileInfo(Path.Combine(resolvedPath, "cmyk.png"));
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif");
IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".jpg", ".jpeg", ".jfif");
//IEnumerable<FileInfo> files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif");
//foreach (FileInfo fileInfo in files)
//{
//if (fileInfo.Name == "test5.jpg")
//{
// continue;
//}
foreach (FileInfo fileInfo in files)
{
if (fileInfo.Name == "test5.jpg")
{
continue;
}
byte[] photoBytes = File.ReadAllBytes(fileInfo.FullName);
Console.WriteLine("Processing: " + fileInfo.Name);
@ -79,8 +79,8 @@ namespace ImageProcessor.PlayGround
{
using (ImageFactory imageFactory = new ImageFactory(true))
{
Size size = new Size(844, 1017);
ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false);
Size size = new Size(1024, 0);
//ResizeLayer layer = new ResizeLayer(size, ResizeMode.Max, AnchorPosition.Center, false);
//ContentAwareResizeLayer layer = new ContentAwareResizeLayer(size)
//{
@ -102,7 +102,7 @@ namespace ImageProcessor.PlayGround
//.Format(new PngFormat())
//.BackgroundColor(Color.Cyan)
//.ReplaceColor(Color.FromArgb(255, 223, 224), Color.FromArgb(121, 188, 255), 128)
//.Resize(size)
.Resize(size)
//.Resize(new ResizeLayer(size, ResizeMode.Max))
// .Resize(new ResizeLayer(size, ResizeMode.Stretch))
//.DetectEdges(new Laplacian3X3EdgeFilter(), true)
@ -119,8 +119,8 @@ namespace ImageProcessor.PlayGround
//.GaussianSharpen(10)
//.Format(new PngFormat() { IsIndexed = true })
//.Format(new PngFormat() { IsIndexed = true })
//.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name)));
.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".png")));
.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", fileInfo.Name)));
//.Save(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), @"..\..\images\output", Path.GetFileNameWithoutExtension(fileInfo.Name) + ".png")));
stopwatch.Stop();
}
@ -132,7 +132,7 @@ namespace ImageProcessor.PlayGround
Console.WriteLine(@"Completed {0} in {1:s\.fff} secs {2}Peak memory usage was {3} bytes or {4} Mb.", fileInfo.Name, stopwatch.Elapsed, Environment.NewLine, peakWorkingSet64.ToString("#,#"), mB);
//Console.WriteLine("Processed: " + fileInfo.Name + " in " + stopwatch.ElapsedMilliseconds + "ms");
//}
}
Console.ReadLine();
}

8
src/ImageProcessor/Common/Extensions/DoubleExtensions.cs

@ -12,6 +12,8 @@ namespace ImageProcessor.Common.Extensions
{
using System;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Double"/> class.
/// </summary>
@ -24,15 +26,15 @@ namespace ImageProcessor.Common.Extensions
/// those restricted ranges.
/// </remarks>
/// </summary>
/// <param name="d">
/// <param name="value">
/// The <see cref="T:System.Double"/> to convert.
/// </param>
/// <returns>
/// The <see cref="T:System.Byte"/>.
/// </returns>
public static byte ToByte(this double d)
public static byte ToByte(this double value)
{
return Convert.ToByte(Math.Max(0.0d, Math.Min(255d, d)));
return Convert.ToByte(ImageMaths.Clamp(value, 0, 255));
}
}
}

19
src/ImageProcessor/Common/Extensions/IntegerExtensions.cs

@ -10,8 +10,11 @@
namespace ImageProcessor.Common.Extensions
{
using System;
using System.Globalization;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Int32"/> class.
/// </summary>
@ -24,27 +27,27 @@ namespace ImageProcessor.Common.Extensions
/// those restricted ranges.
/// </remarks>
/// </summary>
/// <param name="integer">
/// <param name="value">
/// The <see cref="T:System.Int32"/> to convert.
/// </param>
/// <returns>
/// The <see cref="T:System.Byte"/>.
/// </returns>
public static byte ToByte(this int integer)
public static byte ToByte(this int value)
{
return ((double)integer).ToByte();
return Convert.ToByte(ImageMaths.Clamp(value, 0, 255));
}
/// <summary>
/// Converts the string representation of a number in a specified culture-specific format to its
/// 32-bit signed integer equivalent using invariant culture.
/// </summary>
/// <param name="integer">The integer.</param>
/// <param name="s">A string containing a number to convert.</param>
/// <returns>A 32-bit signed integer equivalent to the number specified in s.</returns>
public static int ParseInvariant(this int integer, string s)
/// <param name="value">The integer.</param>
/// <param name="toParse">A string containing a number to convert.</param>
/// <returns>A 32-bit signed integer equivalent to the number specified in toParse.</returns>
public static int ParseInvariant(this int value, string toParse)
{
return int.Parse(s, CultureInfo.InvariantCulture);
return int.Parse(toParse, CultureInfo.InvariantCulture);
}
}
}

12
src/ImageProcessor/Imaging/Colors/CmykColor.cs

@ -14,6 +14,7 @@ namespace ImageProcessor.Imaging.Colors
using System.Drawing;
using ImageProcessor.Common.Extensions;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// Represents an CMYK (cyan, magenta, yellow, keyline) color.
@ -355,16 +356,7 @@ namespace ImageProcessor.Imaging.Colors
/// </returns>
private static float Clamp(float value)
{
if (value < 0.0)
{
value = 0.0f;
}
else if (value > 100)
{
value = 100f;
}
return value;
return ImageMaths.Clamp(value, 0, 100);
}
/// <summary>

47
src/ImageProcessor/Imaging/Colors/ColorExtensions.cs

@ -1,17 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ColorExtensions.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Provides extensions for manipulating colors.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Colors
{
using System;
using System.Drawing;
using ImageProcessor.Common.Extensions;
/// <summary>
/// Provides extensions for manipulating colors.
/// </summary>
internal static class ColorExtensions
{
/// <summary>
/// Adds colors together using the RGBA color format.
/// </summary>
/// <param name="color">
/// The color to add to.
/// </param>
/// <param name="colors">
/// The colors to add to the initial one.
/// </param>
/// <returns>
/// The combined <see cref="Color"/>.
/// </returns>
public static Color Add(this Color color, params Color[] colors)
{
int red = color.A > 0 ? color.R : 0;
@ -37,6 +57,18 @@ namespace ImageProcessor.Imaging.Colors
return Color.FromArgb((alpha / counter).ToByte(), (red / counter).ToByte(), (green / counter).ToByte(), (blue / counter).ToByte());
}
/// <summary>
/// Adds colors together using the CMYK color format.
/// </summary>
/// <param name="color">
/// The color to add to.
/// </param>
/// <param name="colors">
/// The colors to add to the initial one.
/// </param>
/// <returns>
/// The combined <see cref="CmykColor"/>.
/// </returns>
public static CmykColor AddAsCmykColor(this Color color, params Color[] colors)
{
CmykColor cmyk = color;
@ -57,11 +89,6 @@ namespace ImageProcessor.Imaging.Colors
}
}
//c = Math.Max(0.0f, Math.Min(100f, c));
//m = Math.Max(0.0f, Math.Min(100f, m));
//y = Math.Max(0.0f, Math.Min(100f, y));
//k = Math.Max(0.0f, Math.Min(100f, k));
return CmykColor.FromCmykColor(c, m, y, k);
}
}

13
src/ImageProcessor/Imaging/Colors/HSLAColor.cs

@ -10,6 +10,8 @@ namespace ImageProcessor.Imaging.Colors
using System;
using System.Drawing;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// Represents an HSLA (hue, saturation, luminosity, alpha) color.
/// Adapted from <see href="http://richnewman.wordpress.com/about/code-listings-and-diagrams/hslcolor-class/"/>
@ -493,16 +495,7 @@ namespace ImageProcessor.Imaging.Colors
/// </returns>
private static float Clamp(float value)
{
if (value < 0.0)
{
value = 0.0f;
}
else if (value > 1.0)
{
value = 1.0f;
}
return value;
return ImageMaths.Clamp(value, 0, 1);
}
/// <summary>

8
src/ImageProcessor/Imaging/Colors/YCbCrColor.cs

@ -14,6 +14,8 @@ namespace ImageProcessor.Imaging.Colors
using System;
using System.Drawing;
using ImageProcessor.Imaging.Helpers;
/// <summary>
/// Represents an YCbCr (luminance, chroma, chroma) color conforming to the ITU-R BT.601 standard used in digital imaging systems.
/// <see href="http://en.wikipedia.org/wiki/YCbCr"/>
@ -48,9 +50,9 @@ namespace ImageProcessor.Imaging.Colors
/// <param name="cr">The v chroma component.</param>
private YCbCrColor(float y, float cb, float cr)
{
this.y = Math.Max(0f, Math.Min(255f, y));
this.cb = Math.Max(-255f, Math.Min(255f, cb));
this.cr = Math.Max(-255f, Math.Min(255f, cr));
this.y = ImageMaths.Clamp(y, 0, 255);
this.cb = ImageMaths.Clamp(cb, -255, 255);
this.cr = ImageMaths.Clamp(cr, -255, 255);
}
/// <summary>

44
src/ImageProcessor/Imaging/Filters/Artistic/HalftoneFilter.cs

@ -12,6 +12,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic
{
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading.Tasks;
using ImageProcessor.Imaging.Colors;
@ -167,9 +168,14 @@ namespace ImageProcessor.Imaging.Filters.Artistic
int width = source.Width;
int height = source.Height;
int minHeight = -height * 2;
int maxHeight = height * 2;
int minWidth = -width * 2;
int maxWidth = width * 2;
float multiplier = 4 * (float)Math.Sqrt(2);
float max = this.distance + (float)Math.Sqrt(2);
float keylineMax = max + 1;
float max = this.distance + ((float)Math.Sqrt(2) / 2);
float keylineMax = this.distance + (float)Math.Sqrt(2) + ((float)Math.Sqrt(2) / 2);
// Cyan color sampled from Wikipedia page. Keyline brush is declared
// separately.
@ -199,19 +205,27 @@ namespace ImageProcessor.Imaging.Filters.Artistic
using (Graphics graphicsYellow = Graphics.FromImage(yellow))
using (Graphics graphicsKeyline = Graphics.FromImage(keyline))
{
// Ensure cleared out.
// 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 = -height * 2; y < height * 2; y += this.distance)
for (int y = minHeight; y < maxHeight; y += d)
{
for (int x = -width * 2; x < width * 2; x += this.distance)
for (int x = minWidth; x < maxWidth; x += d)
{
Color color;
CmykColor cmykColor;
@ -225,7 +239,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.C / 255f) * multiplier));
brushWidth = ImageMaths.Clamp(d * (cmykColor.C / 255f) * multiplier, 0, max);
graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth);
}
@ -237,7 +251,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.M / 255f) * multiplier));
brushWidth = ImageMaths.Clamp(d * (cmykColor.M / 255f) * multiplier, 0, max);
graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth);
}
@ -249,7 +263,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = Math.Max(0, Math.Min(max, this.distance * (cmykColor.Y / 255f) * multiplier));
brushWidth = ImageMaths.Clamp(d * (cmykColor.Y / 255f) * multiplier, 0, max);
graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth);
}
@ -261,7 +275,7 @@ namespace ImageProcessor.Imaging.Filters.Artistic
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = Math.Max(0, Math.Min(keylineMax, this.distance * (cmykColor.K / 255f) * multiplier));
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));
@ -342,5 +356,17 @@ namespace ImageProcessor.Imaging.Filters.Artistic
return source;
}
private void SetGraphicsSettings(ref Graphics graphics)
{
// Set the quality properties.
graphics.SmoothingMode = graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
// Set up the canvas.
graphics.Clear(Color.Transparent);
}
}
}

33
src/ImageProcessor/Imaging/Helpers/ImageMaths.cs

@ -20,6 +20,39 @@ namespace ImageProcessor.Imaging.Helpers
/// </summary>
public static class ImageMaths
{
/// <summary>
/// Restricts a value to be within a specified range.
/// </summary>
/// <param name="value">
/// The The value to clamp.
/// </param>
/// <param name="min">
/// The minimum value. If value is less than min, min will be returned.
/// </param>
/// <param name="max">
/// The maximum value. If value is greater than max, max will be returned.
/// </param>
/// <typeparam name="T">
/// The <see cref="System.Type"/> to clamp.
/// </typeparam>
/// <returns>
/// The <see cref="T"/> representing the clamped value.
/// </returns>
public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
{
if (value.CompareTo(min) < 0)
{
return min;
}
if (value.CompareTo(max) > 0)
{
return max;
}
return value;
}
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> from the given points.
/// </summary>

30
src/ImageProcessor/Processors/Halftone.cs

@ -70,7 +70,7 @@ namespace ImageProcessor.Processors
int width = image.Width;
int height = image.Height;
Bitmap newImage = null;
Bitmap edgeBitmap = null;
//Bitmap edgeBitmap = null;
try
{
HalftoneFilter filter = new HalftoneFilter(5);
@ -79,34 +79,34 @@ namespace ImageProcessor.Processors
newImage = filter.ApplyFilter(newImage);
// Draw the edges.
edgeBitmap = new Bitmap(width, height);
edgeBitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution);
edgeBitmap = Trace(image, edgeBitmap, 120);
//edgeBitmap = new Bitmap(width, height);
//edgeBitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//edgeBitmap = Trace(image, edgeBitmap, 120);
using (Graphics graphics = Graphics.FromImage(newImage))
{
// Overlay the image.
graphics.DrawImage(edgeBitmap, 0, 0);
//graphics.DrawImage(edgeBitmap, 0, 0);
Rectangle rectangle = new Rectangle(0, 0, width, height);
// Draw an edge around the image.
using (Pen blackPen = new Pen(Color.Black))
{
blackPen.Width = 4;
graphics.DrawRectangle(blackPen, rectangle);
}
//using (Pen blackPen = new Pen(Color.Black))
//{
// blackPen.Width = 4;
// graphics.DrawRectangle(blackPen, rectangle);
//}
}
edgeBitmap.Dispose();
//edgeBitmap.Dispose();
image.Dispose();
image = newImage;
}
catch (Exception ex)
{
if (edgeBitmap != null)
{
edgeBitmap.Dispose();
}
//if (edgeBitmap != null)
//{
// edgeBitmap.Dispose();
//}
if (newImage != null)
{

Loading…
Cancel
Save