Browse Source

Added dropshadow to watermark plus cleanup

Former-commit-id: 12a5dedf1664752ae503559c7e4998966b8d78b8
af/merge-core
JimBobSquarePants 14 years ago
parent
commit
901aebb1af
  1. 404
      src/ImageProcessor/Imaging/OctreeQuantizer.cs
  2. 13
      src/ImageProcessor/Imaging/TextLayer.cs
  3. 10
      src/ImageProcessor/Processors/Filter.cs
  4. 41
      src/ImageProcessor/Processors/Watermark.cs
  5. 1
      src/Test/Test/Images/color-vision-test.gif.REMOVED.git-id

404
src/ImageProcessor/Imaging/OctreeQuantizer.cs

@ -121,20 +121,78 @@ namespace ImageProcessor.Imaging
/// </summary>
private class Octree
{
#region Fields
/// <summary>
/// Mask used when getting the appropriate pixels for a given node
/// </summary>
private static int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
/// <summary>
/// The root of the octree
/// </summary>
private OctreeNode root;
/// <summary>
/// Number of leaves in the tree
/// </summary>
private int leafCount;
/// <summary>
/// Array of reducible nodes
/// </summary>
private OctreeNode[] reducibleNodes;
/// <summary>
/// Maximum number of significant bits in the image
/// </summary>
private int maxColorBits;
/// <summary>
/// Store the last node quantized
/// </summary>
private OctreeNode previousNode;
/// <summary>
/// Cache the previous color quantized
/// </summary>
private int previousColor;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="T:ImageProcessor.Imaging.OctreeQuantizer.Octree">Octree</see> class.
/// </summary>
/// <param name="maxColorBits">The maximum number of significant bits in the image</param>
public Octree(int maxColorBits)
/// <param name="maxBits">The maximum number of significant bits in the image</param>
public Octree(int maxBits)
{
this._maxColorBits = maxColorBits;
this._leafCount = 0;
this._reducibleNodes = new OctreeNode[9];
this._root = new OctreeNode(0, this._maxColorBits, this);
this._previousColor = 0;
this._previousNode = null;
this.maxColorBits = maxBits;
this.leafCount = 0;
this.reducibleNodes = new OctreeNode[9];
this.root = new OctreeNode(0, this.maxColorBits, this);
this.previousColor = 0;
this.previousNode = null;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the number of leaves in the tree
/// </summary>
public int Leaves
{
get { return this.leafCount; }
set { this.leafCount = value; }
}
/// <summary>
/// Gets the array of reducible nodes
/// </summary>
protected OctreeNode[] ReducibleNodes
{
get { return this.reducibleNodes; }
}
#endregion
/// <summary>
/// Add a given colour value to the octree
/// </summary>
@ -144,25 +202,25 @@ namespace ImageProcessor.Imaging
public void AddColor(Color32 pixel)
{
// Check if this request is for the same colour as the last
if (this._previousColor == pixel.ARGB)
if (this.previousColor == pixel.ARGB)
{
// If so, check if I have a previous node setup. This will only occur if the first colour in the image
// happens to be black, with an alpha component of zero.
if (null == this._previousNode)
if (null == this.previousNode)
{
this._previousColor = pixel.ARGB;
this._root.AddColor(pixel, this._maxColorBits, 0, this);
this.previousColor = pixel.ARGB;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
else
{
// Just update the previous node
this._previousNode.Increment(pixel);
this.previousNode.Increment(pixel);
}
}
else
{
this._previousColor = pixel.ARGB;
this._root.AddColor(pixel, this._maxColorBits, 0, this);
this.previousColor = pixel.ARGB;
this.root.AddColor(pixel, this.maxColorBits, 0, this);
}
}
@ -172,48 +230,22 @@ namespace ImageProcessor.Imaging
public void Reduce()
{
// Find the deepest level containing at least one reducible node
int index = this._maxColorBits - 1;
while ((index > 0) && (this._reducibleNodes[index] == null))
int index = this.maxColorBits - 1;
while ((index > 0) && (this.reducibleNodes[index] == null))
{
index--;
}
// Reduce the node most recently added to the list at level 'index'
OctreeNode node = this._reducibleNodes[index];
this._reducibleNodes[index] = node.NextReducible;
OctreeNode node = this.reducibleNodes[index];
this.reducibleNodes[index] = node.NextReducible;
// Decrement the leaf count after reducing the node
this._leafCount -= node.Reduce();
this.leafCount -= node.Reduce();
// And just in case I've reduced the last color to be added, and the next color to
// be added is the same, invalidate the previousNode...
this._previousNode = null;
}
/// <summary>
/// Get or sets the number of leaves in the tree
/// </summary>
public int Leaves
{
get { return this._leafCount; }
set { this._leafCount = value; }
}
/// <summary>
/// Return the array of reducible nodes
/// </summary>
protected OctreeNode[] ReducibleNodes
{
get { return this._reducibleNodes; }
}
/// <summary>
/// Keep track of the previous node that was quantized
/// </summary>
/// <param name="node">The node last quantized</param>
protected void TrackPrevious(OctreeNode node)
{
this._previousNode = node;
this.previousNode = null;
}
/// <summary>
@ -231,92 +263,132 @@ namespace ImageProcessor.Imaging
// Now palletize the nodes
ArrayList palette = new ArrayList(this.Leaves);
int paletteIndex = 0;
this._root.ConstructPalette(palette, ref paletteIndex);
this.root.ConstructPalette(palette, ref paletteIndex);
// And return the palette
return palette;
}
/// <summary>
/// Get the palette index for the passed colour
/// Get the palette index for the passed colour.
/// </summary>
/// <param name="pixel"></param>
/// <returns></returns>
/// <param name="pixel">
/// The color to return the palette index for.
/// </param>
/// <returns>
/// The palette index for the passed colour.
/// </returns>
public int GetPaletteIndex(Color32 pixel)
{
return this._root.GetPaletteIndex(pixel, 0);
return this.root.GetPaletteIndex(pixel, 0);
}
/// <summary>
/// Mask used when getting the appropriate pixels for a given node
/// Keep track of the previous node that was quantized
/// </summary>
private static int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
/// <param name="node">The node last quantized</param>
protected void TrackPrevious(OctreeNode node)
{
this.previousNode = node;
}
/// <summary>
/// The root of the octree
/// Class which encapsulates each node in the tree
/// </summary>
private OctreeNode _root;
protected class OctreeNode
{
#region Fields
/// <summary>
/// Flag indicating that this is a leaf node
/// </summary>
private bool leaf;
/// <summary>
/// Number of leaves in the tree
/// </summary>
private int _leafCount;
/// <summary>
/// Number of pixels in this node
/// </summary>
private int pixelCount;
/// <summary>
/// Array of reducible nodes
/// </summary>
private OctreeNode[] _reducibleNodes;
/// <summary>
/// Red component
/// </summary>
private int red;
/// <summary>
/// Maximum number of significant bits in the image
/// </summary>
private int _maxColorBits;
/// <summary>
/// Green Component
/// </summary>
private int green;
/// <summary>
/// Store the last node quantized
/// </summary>
private OctreeNode _previousNode;
/// <summary>
/// Blue component
/// </summary>
private int blue;
/// <summary>
/// Cache the previous color quantized
/// </summary>
private int _previousColor;
/// <summary>
/// Pointers to any child nodes
/// </summary>
private OctreeNode[] children;
/// <summary>
/// Class which encapsulates each node in the tree
/// </summary>
protected class OctreeNode
{
/// <summary>
/// Construct the node
/// The index of this node in the palette
/// </summary>
/// <param name="level">The level in the tree = 0 - 7</param>
/// <param name="colorBits">The number of significant color bits in the image</param>
/// <param name="octree">The tree to which this node belongs</param>
private int paletteIndex;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="OctreeNode"/> class.
/// </summary>
/// <param name="level">
/// The level in the tree = 0 - 7
/// </param>
/// <param name="colorBits">
/// The number of significant color bits in the image
/// </param>
/// <param name="octree">
/// The tree to which this node belongs
/// </param>
public OctreeNode(int level, int colorBits, Octree octree)
{
// Construct the new node
this._leaf = (level == colorBits);
this.leaf = level == colorBits;
this._red = _green = _blue = 0;
this._pixelCount = 0;
this.red = this.green = this.blue = 0;
this.pixelCount = 0;
// If a leaf, increment the leaf count
if (this._leaf)
if (this.leaf)
{
octree.Leaves++;
this._nextReducible = null;
this._children = null;
this.NextReducible = null;
this.children = null;
}
else
{
// Otherwise add this to the reducible nodes
this._nextReducible = octree.ReducibleNodes[level];
this.NextReducible = octree.ReducibleNodes[level];
octree.ReducibleNodes[level] = this;
this._children = new OctreeNode[8];
this.children = new OctreeNode[8];
}
}
}
#endregion
#region Properties
/// <summary>
/// Gets or the next reducible node
/// </summary>
public OctreeNode NextReducible { get; private set; }
/// <summary>
/// Gets the child nodes
/// </summary>
private OctreeNode[] Children
{
get { return this.children; }
}
#endregion
#region Methods
/// <summary>
/// Add a color into the tree
/// </summary>
@ -327,9 +399,10 @@ namespace ImageProcessor.Imaging
public void AddColor(Color32 pixel, int colorBits, int level, Octree octree)
{
// Update the color information if this is a leaf
if (this._leaf)
if (this.leaf)
{
Increment(pixel);
this.Increment(pixel);
// Setup the previous node
octree.TrackPrevious(this);
}
@ -339,38 +412,20 @@ namespace ImageProcessor.Imaging
int shift = 7 - level;
int index = ((pixel.Red & mask[level]) >> (shift - 2)) |
((pixel.Green & mask[level]) >> (shift - 1)) |
((pixel.Blue & mask[level]) >> (shift));
((pixel.Blue & mask[level]) >> shift);
OctreeNode child = this._children[index];
OctreeNode child = this.Children[index];
if (null == child)
{
// Create a new child node & store in the array
child = new OctreeNode(level + 1, colorBits, octree);
this._children[index] = child;
this.Children[index] = child;
}
// Add the color to the child node
child.AddColor(pixel, colorBits, level + 1, octree);
}
}
/// <summary>
/// Get or Sets the next reducible node
/// </summary>
public OctreeNode NextReducible
{
get { return _nextReducible; }
set { _nextReducible = value; }
}
/// <summary>
/// Return the child nodes
/// </summary>
public OctreeNode[] Children
{
get { return _children; }
}
/// <summary>
@ -379,73 +434,84 @@ namespace ImageProcessor.Imaging
/// <returns>The number of leaves removed</returns>
public int Reduce()
{
this._red = this._green = this._blue = 0;
int children = 0;
this.red = this.green = this.blue = 0;
int childPosition = 0;
// Loop through all children and add their information to this node
for (int index = 0; index < 8; index++)
{
if (null != this._children[index])
if (null != this.Children[index])
{
this._red += this._children[index]._red;
this._green += this._children[index]._green;
this._blue += this._children[index]._blue;
this._pixelCount += this._children[index]._pixelCount;
++children;
this._children[index] = null;
this.red += this.Children[index].red;
this.green += this.Children[index].green;
this.blue += this.Children[index].blue;
this.pixelCount += this.Children[index].pixelCount;
++childPosition;
this.Children[index] = null;
}
}
// Now change this to a leaf node
this._leaf = true;
this.leaf = true;
// Return the number of nodes to decrement the leaf count by
return children - 1;
return childPosition - 1;
}
/// <summary>
/// Traverse the tree, building up the color palette
/// </summary>
/// <param name="palette">The palette</param>
/// <param name="paletteIndex">The current palette index</param>
public void ConstructPalette(ArrayList palette, ref int paletteIndex)
/// <param name="currentPaletteIndex">The current palette index</param>
public void ConstructPalette(ArrayList palette, ref int currentPaletteIndex)
{
if (_leaf)
if (this.leaf)
{
// Consume the next palette index
_paletteIndex = paletteIndex++;
this.paletteIndex = currentPaletteIndex++;
// And set the color of the palette entry
palette.Add(Color.FromArgb(_red / _pixelCount, _green / _pixelCount, _blue / _pixelCount));
palette.Add(Color.FromArgb(this.red / this.pixelCount, this.green / this.pixelCount, this.blue / this.pixelCount));
}
else
{
// Loop through children looking for leaves
for (int index = 0; index < 8; index++)
{
if (null != _children[index])
_children[index].ConstructPalette(palette, ref paletteIndex);
if (null != this.children[index])
{
this.children[index].ConstructPalette(palette, ref currentPaletteIndex);
}
}
}
}
/// <summary>
/// Return the palette index for the passed color
/// Return the palette index for the passed color.
/// </summary>
/// <param name="pixel">
/// The pixel.
/// </param>
/// <param name="level">
/// The level.
/// </param>
/// <returns>
/// The palette index for the passed color.
/// </returns>
public int GetPaletteIndex(Color32 pixel, int level)
{
int paletteIndex = _paletteIndex;
int currentPaletteIndex = this.paletteIndex;
if (!_leaf)
if (!this.leaf)
{
int shift = 7 - level;
int index = ((pixel.Red & mask[level]) >> (shift - 2)) |
((pixel.Green & mask[level]) >> (shift - 1)) |
((pixel.Blue & mask[level]) >> (shift));
((pixel.Blue & mask[level]) >> shift);
if (null != _children[index])
if (null != this.children[index])
{
paletteIndex = _children[index].GetPaletteIndex(pixel, level + 1);
currentPaletteIndex = this.children[index].GetPaletteIndex(pixel, level + 1);
}
else
{
@ -453,59 +519,23 @@ namespace ImageProcessor.Imaging
}
}
return paletteIndex;
return currentPaletteIndex;
}
/// <summary>
/// Increment the pixel count and add to the color information
/// </summary>
/// <param name="pixel">
/// The pixel.
/// </param>
public void Increment(Color32 pixel)
{
this._pixelCount++;
this._red += pixel.Red;
this._green += pixel.Green;
this._blue += pixel.Blue;
}
/// <summary>
/// Flag indicating that this is a leaf node
/// </summary>
private bool _leaf;
/// <summary>
/// Number of pixels in this node
/// </summary>
private int _pixelCount;
/// <summary>
/// Red component
/// </summary>
private int _red;
/// <summary>
/// Green Component
/// </summary>
private int _green;
/// <summary>
/// Blue component
/// </summary>
private int _blue;
/// <summary>
/// Pointers to any child nodes
/// </summary>
private OctreeNode[] _children;
/// <summary>
/// Pointer to next reducible node
/// </summary>
private OctreeNode _nextReducible;
/// <summary>
/// The index of this node in the palette
/// </summary>
private int _paletteIndex;
this.pixelCount++;
this.red += pixel.Red;
this.green += pixel.Green;
this.blue += pixel.Blue;
}
#endregion
}
}
}

13
src/ImageProcessor/Imaging/TextLayer.cs

@ -51,7 +51,7 @@ namespace ImageProcessor.Imaging
public string Text { get; set; }
/// <summary>
/// Gets or sets TextColor.
/// Gets or sets the Color to render the font.
/// </summary>
public Color TextColor
{
@ -74,7 +74,7 @@ namespace ImageProcessor.Imaging
}
/// <summary>
/// Gets or sets the FontStyle.
/// Gets or sets the FontStyle of the textlayer.
/// </summary>
public FontStyle Style
{
@ -83,7 +83,7 @@ namespace ImageProcessor.Imaging
}
/// <summary>
/// Gets or sets the Opacity.
/// Gets or sets the Opacity of the textlayer.
/// </summary>
public int Opacity
{
@ -101,13 +101,18 @@ namespace ImageProcessor.Imaging
}
/// <summary>
/// Gets or sets Position.
/// Gets or sets the Position of the textlayer.
/// </summary>
public Point Position
{
get { return this.position; }
set { this.position = value; }
}
/// <summary>
/// Gets or sets a value indicating whether a DropShadow should be drawn.
/// </summary>
public bool DropShadow { get; set; }
#endregion
}
}

10
src/ImageProcessor/Processors/Filter.cs

@ -165,6 +165,7 @@ namespace ImageProcessor.Processors
{
Bitmap newImage = null;
Image image = factory.Image;
// Bitmaps for comic pattern
Bitmap hisatchBitmap = null;
Bitmap patternBitmap = null;
@ -329,6 +330,15 @@ namespace ImageProcessor.Processors
}
}
// Add a vignette to finish the effect.
// TODO: This feels a bit mucky so I might chop it out.
if (this.DynamicParameter == "polaroid" || this.DynamicParameter == "lomograph")
{
factory.Image = newImage;
Vignette vignette = new Vignette();
newImage = (Bitmap)vignette.ProcessImage(factory);
}
// Reassign the image.
image.Dispose();
image = newImage;

41
src/ImageProcessor/Processors/Watermark.cs

@ -7,9 +7,12 @@
namespace ImageProcessor.Processors
{
#region Using
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.Linq;
using System.Text.RegularExpressions;
using ImageProcessor.Helpers.Extensions;
using ImageProcessor.Imaging;
@ -60,6 +63,11 @@ namespace ImageProcessor.Processors
/// </summary>
private static readonly Regex OpacityRegex = new Regex(@"opacity-\d{1,2}(?!\d)|opacity-100", RegexOptions.Compiled);
/// <summary>
/// The regular expression to search strings for the shadow attribute.
/// </summary>
private static readonly Regex ShadowRegex = new Regex(@"shadow-true", RegexOptions.Compiled);
#region IGraphicsProcessor Members
/// <summary>
/// Gets the name.
@ -157,6 +165,7 @@ namespace ImageProcessor.Processors
textLayer.Font = this.ParseFontFamily(toParse);
textLayer.Style = this.ParseFontStyle(toParse);
textLayer.Opacity = this.ParseOpacity(toParse);
textLayer.DropShadow = this.ParseDropShadow(toParse);
this.DynamicParameter = textLayer;
}
@ -218,6 +227,23 @@ namespace ImageProcessor.Processors
// Set the hinting and draw the text.
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
if (textLayer.DropShadow)
{
// Shadow opacity should change with the base opacity.
int shadowOpacity = opacity - (int)Math.Ceiling((30 / 100f) * 255);
int finalShadowOpacity = shadowOpacity > 0 ? shadowOpacity : 0;
using (Brush shadowBrush = new SolidBrush(Color.FromArgb(finalShadowOpacity, Color.Black)))
{
// Scale the shadow position to match the font size.
// Magic number but it's based on artistic preference.
int shadowDiff = (int)Math.Ceiling(fontSize / 24f);
Point shadowPoint = new Point(origin.X + shadowDiff, origin.Y + shadowDiff);
graphics.DrawString(text, font, shadowBrush, shadowPoint, drawFormat);
}
}
graphics.DrawString(text, font, brush, origin, drawFormat);
}
}
@ -439,6 +465,21 @@ namespace ImageProcessor.Processors
// full opacity - matches the Textlayer default.
return 100;
}
/// <summary>
/// Returns a value indicating whether the watermark is to have a shadow.
/// </summary>
/// <param name="input">
/// The input string containing the value to parse.
/// </param>
/// <returns>
/// The true if the watermark is to have a shadow; otherwise false.
/// </returns>
private bool ParseDropShadow(string input)
{
return ShadowRegex.Matches(input).Cast<Match>().Any();
}
#endregion
}
}

1
src/Test/Test/Images/color-vision-test.gif.REMOVED.git-id

@ -0,0 +1 @@
35a926115b13b61dc37308f8d903b42d14f92924
Loading…
Cancel
Save