using System;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Drawing.Imaging;
using GalleryServerPro.Business.Interfaces;
namespace GalleryServerPro.Business
{
/// <summary>
/// The Watermark class contains functionality for applying a text and/or image watermark to an image.
/// </summary>
public class Watermark : IDisposable
{
#region Private Fields
private ImageAttributes _imageAttributes;
private System.Drawing.Image _watermarkImage;
private int _watermarkImageWidth = int.MinValue;
private int _watermarkImageHeight = int.MinValue;
private string _imagePath;
private ContentAlignment _imageLocation;
private int _imageWidthPercent;
private int _imageOpacityPercent;
private string _watermarkText;
private System.Drawing.Color _textColor;
private int _textHeightPixels;
private int _textWidthPercent;
private string _textFontName;
private ContentAlignment _textLocation;
private int _textOpacityPercent;
private const int MIN_FONT_HEIGHT_PIXELS = 4;
private const int DEFAULT_TEXT_HEIGHT_PIXELS = 12; // Text height used if not specified.
private const float BORDER_PERCENT = .01f; // The distance from the border to place the watermark text and image. Ex: .01
// means border thickness should be 1% of the width of the recipient image.
private bool _hasBeenDisposed; // Used by Dispose() methods
#endregion
#region Public Properties
/// <summary>
/// Gets or sets the location for the watermark image on the recipient image.
/// </summary>
/// <value>The image location.</value>
public ContentAlignment ImageLocation
{
get { return _imageLocation; }
set { _imageLocation = value; }
}
/// <summary>
/// Gets or sets the percent of the overall width of the recipient image that should be covered with the
/// watermark image. The size of the image is automatically scaled to achieve the desired width. For example,
/// a value of 50 means the watermark image is 50% as wide as the recipient image. Valid values are 0 - 100.
/// A value of 0 turns off this feature and causes the image to be rendered its actual size.
/// </summary>
/// <value>The image width, in percent.</value>
public int ImageWidthPercent
{
get { return _imageWidthPercent; }
set { _imageWidthPercent = value; }
}
/// <summary>
/// Gets or sets the watermark text to be applied to the recipient image.
/// </summary>
/// <value>The watermark text.</value>
public string WatermarkText
{
get { return _watermarkText; }
set { _watermarkText = value; }
}
/// <summary>
/// Gets or sets the height, in pixels, of the watermark text. This value is ignored if the property
/// TextWidthPercent is non-zero. Valid values are 0 - 10000.
/// </summary>
/// <value>The text height, in pixels.</value>
public int TextHeightPixels
{
get { return _textHeightPixels; }
set
{
if ((value < 0) || (value > 10000))
{
throw new ArgumentOutOfRangeException(String.Format(CultureInfo.CurrentCulture, "TextHeightPixels must be an integer between 0 and 10000. The value {0} is invalid.", value));
}
_textHeightPixels = value;
}
}
/// <summary>
/// Gets or sets the percent of the overall width of the recipient image that should be covered with the
/// watermark text. The size of the text is automatically scaled up or down to achieve the desired width. For example,
/// a value of 50 means the text is 50% as wide as the recipient image. Valid values are 0 - 100. The text is never
/// rendered in a font smaller than 6 pixels, so in cases of long text it may stretch wider than the percentage
/// specified in this setting. A value of 0 turns off this feature and causes the text size to be determined by the
/// TextSizePixels property.
/// </summary>
/// <value>The text width, in percent.</value>
public int TextWidthPercent
{
get { return _textWidthPercent; }
set
{
if ((value < 0) || (value > 100))
{
throw new ArgumentOutOfRangeException(String.Format(CultureInfo.CurrentCulture, "TextWidthPercent must be an integer between 0 and 100. The value {0} is invalid.", value));
}
_textWidthPercent = value;
}
}
/// <summary>
/// Gets or sets the font family name to use for the watermark text applied to the recipient image.
/// If the name does not represent a font installed on the server, a generic sans serif font is used.
/// </summary>
/// <value>The name of the text font.</value>
public string TextFontName
{
get { return _textFontName; }
set { _textFontName = value; }
}
/// <summary>
/// Gets or sets the location for the watermark text on the recipient image.
/// </summary>
/// <value>The text location.</value>
public ContentAlignment TextLocation
{
get { return _textLocation; }
set { _textLocation = value; }
}
/// <summary>
/// Gets or sets the color of the watermark text.
/// </summary>
/// <value>The color of the text.</value>
public System.Drawing.Color TextColor
{
get { return _textColor; }
set { _textColor = value; }
}
/// <summary>
/// Gets or sets the opacity of the watermark text. Valid values are 0 - 100, with 0 being completely
/// transparent and 100 completely opaque.
/// </summary>
/// <value>The text opacity, in percent.</value>
public int TextOpacityPercent
{
get { return _textOpacityPercent; }
set { _textOpacityPercent = value; }
}
/// <summary>
/// Gets or sets the opacity of the watermark image. Valid values are 0 - 100, with 0 being completely
/// transparent and 100 completely opaque.
/// </summary>
/// <value>The image opacity, in percent.</value>
public int ImageOpacityPercent
{
get { return _imageOpacityPercent; }
set { _imageOpacityPercent = value; }
}
/// <summary>
/// Gets or sets the full path to a watermark image to be applied to the recipient image. The image
/// must be in a format that allows it to be instantiated in a System.Drawing.Bitmap object. If a relative
/// path is assigned to this property, it is combined with the current application's path and checked to ensure
/// it exists. A System.IO.FileNotFoundException is thrown if this property is assigned a non-empty value and
/// the value does not represent a file on the hard drive. Setting this property
/// also assigns the WatermarkImage property. An exception is thrown if .NET is unable to create a
/// System.Drawing.Image object from the file path. Returns String.Empty if user did not specify a value in
/// the configuration file.
/// </summary>
/// <value>The full path to a watermark image to be applied to the recipient image.</value>
public string ImagePath
{
get { return _imagePath; }
set
{
if (String.IsNullOrEmpty(value))
{
_imagePath = String.Empty;
_watermarkImage = null;
return;
}
else if (File.Exists(value))
{
_imagePath = value; // File exists. OK to set property.
}
else
{
// File doesn't exist, but maybe user specified a relative path. Combine with the application path and try again.
string relativePath = value.TrimStart(new char[] { '/', '\\' }).Replace("/", "\\");
string fullPath = Path.Combine(AppSetting.Instance.PhysicalApplicationPath, relativePath);
if (!File.Exists(fullPath))
{
throw new System.IO.FileNotFoundException(String.Format(CultureInfo.CurrentCulture, "No image file exists at {0} or {1}. Check the configuration file to ensure the watermarkImagePath setting points to a valid file that exists in the specified location.", value, fullPath));
}
// File exists! Assign.
_imagePath = fullPath;
}
// Assign the WatermarkImage property. We clone it and dispose of the first instance to release the lock on the file.
// If we don't do this, the watermark image is locked for the lifetime of the application.
_watermarkImage = System.Drawing.Image.FromFile(_imagePath);
System.Drawing.Image watermarkImageCopy = (System.Drawing.Image)_watermarkImage.Clone();
_watermarkImage.Dispose();
_watermarkImage = watermarkImageCopy;
}
}
/// <summary>
/// Gets the watermark image to be applied to the recipient image. The image is created when the
/// ImagePath property is assigned. Returns null if ImagePath is not specified (that is, the user did
/// not enter a value in the watermarkImagePath property in the configuration file).
/// </summary>
/// <value>The watermark image to be applied to the recipient image.</value>
public System.Drawing.Image WatermarkImage
{
get { return _watermarkImage; }
}
/// <summary>
/// Gets the width, in pixels, of the watermark image. Returns int.MinValue if no watermark image is specified.
/// </summary>
/// <value>The width, in pixels, of the watermark image.</value>
public int WatermarkImageWidth
{
get
{
if ((_watermarkImageWidth == int.MinValue) && (this.WatermarkImage != null))
{
_watermarkImageWidth = this.WatermarkImage.Width;
}
return _watermarkImageWidth;
}
}
/// <summary>
/// Gets the height, in pixels, of the watermark image. Returns int.MinValue if no watermark image is specified.
/// </summary>
/// <value>The height, in pixels, of the watermark image.</value>
public int WatermarkImageHeight
{
get
{
if ((_watermarkImageHeight == int.MinValue) && (this.WatermarkImage != null))
{
_watermarkImageHeight = this.WatermarkImage.Height;
}
return _watermarkImageHeight;
}
}
#endregion
#region Constructors
private Watermark()
{
}
#endregion
#region Destructor (finalizer)
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="Watermark"/> is reclaimed by garbage collection.
/// </summary>
~Watermark()
{
Dispose(false);
}
#endregion
#region Public Methods
/// <summary>
/// Overlay the text and/or image watermark over the image specified in the <paramref name="filePath"/> parameter and return.
/// </summary>
/// <param name="filePath">A string representing the full path to the image file
/// (e.g. "C:\mypics\myprettypony.jpg", "myprettypony.jpg").</param>
/// <returns>Returns a <see cref="System.Drawing.Image" /> instance containing the image with the watermark applied.</returns>
public System.Drawing.Image ApplyWatermark(string filePath)
{
System.Drawing.Image img = System.Drawing.Image.FromFile(filePath);
ApplyTextWatermark(img);
if (this.WatermarkImage != null)
{
System.Drawing.Image watermarkedImage = ApplyImageWatermark(img);
img.Dispose();
return watermarkedImage;
}
else
{
return img;
}
}
/// <summary>
/// Gets the watermark that is configured for the specified <paramref name="galleryId" />.
/// </summary>
/// <param name="galleryId">The gallery ID.</param>
/// <returns>Returns a <see cref="Watermark" /> instance.</returns>
public static Watermark GetUserSpecifiedWatermark(int galleryId)
{
IGallerySettings gallerySetting = Factory.LoadGallerySetting(galleryId);
Watermark tempWatermark = new Watermark();
tempWatermark.WatermarkText = gallerySetting.WatermarkText;
tempWatermark.TextFontName = gallerySetting.WatermarkTextFontName;
tempWatermark.TextColor = HelperFunctions.GetColor(gallerySetting.WatermarkTextColor);
tempWatermark.TextHeightPixels = gallerySetting.WatermarkTextFontSize;
tempWatermark.TextWidthPercent = gallerySetting.WatermarkTextWidthPercent;
tempWatermark.TextOpacityPercent = gallerySetting.WatermarkTextOpacityPercent;
tempWatermark.TextLocation = gallerySetting.WatermarkTextLocation;
tempWatermark.ImagePath = gallerySetting.WatermarkImagePath;
tempWatermark.ImageWidthPercent = gallerySetting.WatermarkImageWidthPercent;
tempWatermark.ImageOpacityPercent = gallerySetting.WatermarkImageOpacityPercent;
tempWatermark.ImageLocation = gallerySetting.WatermarkImageLocation;
return tempWatermark;
}
/// <summary>
/// Gets the watermark to use when the application is in reduced functionality mode.
/// </summary>
/// <param name="galleryId">The gallery ID.</param>
/// <returns>Returns a <see cref="Watermark" /> instance.</returns>
public static Watermark GetReducedFunctionalityModeWatermark(int galleryId)
{
IGallerySettings gallerySetting = Factory.LoadGallerySetting(galleryId);
Watermark tempWatermark = new Watermark();
tempWatermark.WatermarkText = GalleryServerPro.Business.Properties.Resources.Reduced_Functionality_Mode_Watermark_Text;
tempWatermark.TextFontName = gallerySetting.WatermarkTextFontName;
tempWatermark.TextColor = HelperFunctions.GetColor(gallerySetting.WatermarkTextColor);
tempWatermark.TextHeightPixels = 0;
tempWatermark.TextWidthPercent = 100;
tempWatermark.TextOpacityPercent = 100;
tempWatermark.TextLocation = ContentAlignment.MiddleCenter;
tempWatermark._watermarkImage = GalleryServerPro.Business.Properties.Resources.GSP_Logo;
tempWatermark.ImageWidthPercent = 85;
tempWatermark.ImageOpacityPercent = 50;
tempWatermark.ImageLocation = ContentAlignment.BottomCenter;
return tempWatermark;
}
#endregion
#region Private Methods
private System.Drawing.Image ApplyImageWatermark(System.Drawing.Image recipientImage)
{
if (recipientImage == null)
throw new ArgumentNullException("recipientImage");
if (this.WatermarkImage == null)
return recipientImage;
// Create a Bitmap from the image we are going to draw the watermark on.
Bitmap recipientBitmap = new Bitmap(recipientImage);
recipientBitmap.SetResolution(recipientImage.HorizontalResolution, recipientImage.VerticalResolution);
int recipientImageWidth = recipientImage.Width;
int recipientImageHeight = recipientImage.Height;
// Get the watermark image, scaling it up or down if needed.
System.Drawing.Image watermarkImage = GetWatermarkImage(recipientImageWidth, recipientImageHeight);
int watermarkWidth = watermarkImage.Width;
int watermarkHeight = watermarkImage.Height;
// Turn off the border if the watermark image is too big to allow for it.
float borderPercent = BORDER_PERCENT;
if ((watermarkHeight > (recipientImageHeight - (recipientImageHeight * borderPercent))) ||
(watermarkWidth > (recipientImageWidth - (recipientImageWidth * borderPercent))))
{
borderPercent = 0;
}
// Get the X and Y position for where to start drawing the watermark image on the recipient image.
Point watermarkStartingPoint = GetWatermarkStartingPoint((float)watermarkWidth, (float)watermarkHeight, (float)recipientImageWidth, (float)recipientImageHeight, this.ImageLocation, borderPercent);
// Draw the watermark image on the recipient image.
using (Graphics grWatermark = Graphics.FromImage(recipientBitmap))
{
grWatermark.DrawImage(watermarkImage,
new Rectangle(watermarkStartingPoint.X, watermarkStartingPoint.Y, watermarkWidth, watermarkHeight), //Set the destination position
0, // x-coordinate of recipient image to start drawing watermark
0, // y-coordinate of of recipient image to start drawing watermark
watermarkWidth,
watermarkHeight,
GraphicsUnit.Pixel,
GetWatermarkImageAttributes(this.ImageOpacityPercent));
//Replace the original image with the one with the newly drawn watermark image.
recipientImage = recipientBitmap;
}
return recipientImage;
}
private System.Drawing.Image GetWatermarkImage(int recipientImageWidth, int recipientImageHeight)
{
int watermarkWidth = this.WatermarkImageWidth;
int watermarkHeight = this.WatermarkImageHeight;
if (this.ImageWidthPercent > 0)
{
// We need to resize the watermark image so that its width takes up the specified percentage of
// the overall width of the recipient image.
int resizedWatermarkWidth = (int)(recipientImageWidth * (((float)this.ImageWidthPercent) / 100));
int resizedWatermarkHeight = (resizedWatermarkWidth * watermarkHeight) / watermarkWidth;
// If the resized height is taller than the recipient image, then readjust the width and height
// to make the watermark as tall as the recipient image.
if (resizedWatermarkHeight > recipientImageHeight)
{
resizedWatermarkHeight = recipientImageHeight;
resizedWatermarkWidth = (watermarkWidth * resizedWatermarkHeight) / watermarkHeight;
}
// Get the resized image and assign the width and height vars.
return ImageHelper.CreateResizedBitmap(this.WatermarkImage, watermarkWidth, watermarkHeight, resizedWatermarkWidth, resizedWatermarkHeight);
}
else
{
return this.WatermarkImage;
}
}
private ImageAttributes GetWatermarkImageAttributes(int imageOpacityPercent)
{
// Change the opacity of the watermark. This is done by applying a 5x5 matrix that contains the
// coordinates for the RGBA space. Set the 3rd row and 3rd column to the desired opacity. (0.0 - 1.0).
if (_imageAttributes != null)
return _imageAttributes;
float opacity = imageOpacityPercent / 100.0f;
float[][] colorMatrixElements = {
new float[] {1.0f, 0.0f, 0.0f, 0.0f, 0.0f},
new float[] {0.0f, 1.0f, 0.0f, 0.0f, 0.0f},
new float[] {0.0f, 0.0f, 1.0f, 0.0f, 0.0f},
new float[] {0.0f, 0.0f, 0.0f, opacity, 0.0f},
new float[] {0.0f, 0.0f, 0.0f, 0.0f, 1.0f}};
ColorMatrix wmColorMatrix = new ColorMatrix(colorMatrixElements);
_imageAttributes = new ImageAttributes();
_imageAttributes.SetColorMatrix(wmColorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Default);
_imageAttributes.SetColorKey(Color.Transparent, Color.Transparent);
return _imageAttributes;
}
private void ApplyTextWatermark(System.Drawing.Image img)
{
if (String.IsNullOrEmpty(this.WatermarkText))
return;
int opacity = (int)((255.0f * this._textOpacityPercent) / 100.0f);
int recipientImageWidth = img.Width;
int recipientImageHeight = img.Height;
Font font = null;
Graphics gr = null;
try
{
gr = Graphics.FromImage(img);
#region Generate font
if (TextWidthPercent == 0)
{
// We want to use the TextHeightPixels property to set the size.
int fontSize = (this.TextHeightPixels == 0 ? DEFAULT_TEXT_HEIGHT_PIXELS : this.TextHeightPixels);
font = new Font(this.TextFontName, fontSize);
}
else
{
// We have a value for TextWidthPercent, which means we want to create a font/size combination
// whose width takes up the specified percentage across the recipient image.
int fontSize = MIN_FONT_HEIGHT_PIXELS;
float maxTextWidth = recipientImageWidth * (TextWidthPercent / 100.0f);
font = new Font(this.TextFontName, fontSize);
// Starting with the default minimum font size, keep increasing it until we reach the desired width. Note that
// we may end up with a font height taller than the image. An early version of this routine limited the font height
// to no larger than the recipient image height, but that resulted in undesirable empty space above and below the
// text, since the measured height includes space for all characters in the character set. This created the
// impression that the character was not really as tall as the recipient image. 'tis better to let the watermark text be
// taller than the image in certain circumstances - the user can always reduce the TextWidthPercent until the
// desired height is achieved.
while (gr.MeasureString(this.WatermarkText, font).Width < maxTextWidth)
{
fontSize += 1;
font.Dispose();
font = new Font(this.TextFontName, fontSize);
}
// At this point the font size is one larger than it should be. Reduce it and create the final font object.
fontSize -= 1;
font.Dispose();
font = new Font(this.TextFontName, fontSize);
}
#endregion
SizeF watermarkSize = gr.MeasureString(this.WatermarkText, font);
// Turn off the border if the watermark text is too big to allow for it.
float borderPercent = BORDER_PERCENT;
if ((watermarkSize.Height > (recipientImageHeight - (recipientImageHeight * borderPercent))) ||
(watermarkSize.Width > (recipientImageWidth - (recipientImageWidth * borderPercent))))
{
borderPercent = 0;
}
Point textStartingPoint = GetWatermarkStartingPoint(watermarkSize.Width, watermarkSize.Height, (float)recipientImageWidth, (float)recipientImageHeight, this.TextLocation, borderPercent);
using (Brush semitransparentBrush = new SolidBrush(Color.FromArgb(opacity, this.TextColor)))
{
gr.DrawString(this.WatermarkText, font, semitransparentBrush, textStartingPoint);
}
}
finally
{
if (font != null)
{
font.Dispose();
}
if (gr != null)
{
gr.Dispose();
}
}
}
private static Point GetWatermarkStartingPoint(float watermarkWidth, float watermarkHeight, float imageWidth, float imageHeight, ContentAlignment watermarkLocation, float borderPercent)
{
Point startingPoint = new Point();
switch (watermarkLocation)
{
case ContentAlignment.TopLeft:
startingPoint.X = (int)(imageWidth * borderPercent);
startingPoint.Y = (int)(imageHeight * borderPercent);
break;
case ContentAlignment.TopCenter:
startingPoint.X = (int)(imageWidth - watermarkWidth) / 2;
startingPoint.Y = (int)(imageHeight * borderPercent);
break;
case ContentAlignment.TopRight:
startingPoint.X = (int)(imageWidth - watermarkWidth - (imageWidth * borderPercent));
startingPoint.Y = (int)(imageHeight * borderPercent);
break;
case ContentAlignment.MiddleLeft:
startingPoint.X = (int)(imageWidth * borderPercent);
startingPoint.Y = (int)(imageHeight - watermarkHeight) / 2;
break;
case ContentAlignment.MiddleCenter:
startingPoint.X = (int)(imageWidth - watermarkWidth) / 2;
startingPoint.Y = (int)(imageHeight - watermarkHeight) / 2;
break;
case ContentAlignment.MiddleRight:
startingPoint.X = (int)(imageWidth - watermarkWidth - (imageWidth * borderPercent));
startingPoint.Y = (int)(imageHeight - watermarkHeight) / 2;
break;
case ContentAlignment.BottomLeft:
startingPoint.X = (int)(imageWidth * borderPercent);
startingPoint.Y = (int)(imageHeight - watermarkHeight - (imageHeight * borderPercent));
break;
case ContentAlignment.BottomCenter:
startingPoint.X = (int)(imageWidth - watermarkWidth) / 2;
startingPoint.Y = (int)(imageHeight - watermarkHeight - (imageHeight * borderPercent));
break;
case ContentAlignment.BottomRight:
startingPoint.X = (int)(imageWidth - watermarkWidth - (imageWidth * borderPercent));
startingPoint.Y = (int)(imageHeight - watermarkHeight - (imageHeight * borderPercent));
break;
default:
startingPoint.X = (int)(imageWidth * borderPercent);
startingPoint.Y = (int)(imageHeight * borderPercent);
break;
}
return startingPoint;
}
#endregion
#region IDisposable
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (!this._hasBeenDisposed)
{
// Dispose of resources held by this instance.
if (this._watermarkImage != null)
{
this._watermarkImage.Dispose();
}
// Set the sentinel.
this._hasBeenDisposed = true;
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}