// -----------------------------------------------------------------------
//
// TODO: Update copyright text.
//
// -----------------------------------------------------------------------
namespace ImageProcessor
{
#region Using
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using ImageProcessor.Imaging;
using ImageProcessor.Processors;
#endregion
///
/// Encapsulates methods for processing image files.
///
public class ImageFactory : IDisposable
{
#region Fields
///
/// The default quality for jpeg files.
///
private const int DefaultJpegQuality = 90;
///
/// A value indicating whether this instance of the given entity has been disposed.
///
/// if this instance has been disposed; otherwise, .
///
/// If the entity is disposed, it must not be disposed a second
/// time. The isDisposed field is set the first time the entity
/// is disposed. If the isDisposed field is true, then the Dispose()
/// method will not dispose again. This help not to prolong the entity's
/// life in the Garbage Collector.
///
private bool isDisposed;
#endregion
#region Destructors
///
/// Finalizes an instance of the ImageFactory class.
///
///
/// Use C# destructor syntax for finalization code.
/// This destructor will run only if the Dispose method
/// does not get called.
/// It gives your base class the opportunity to finalize.
/// Do not provide destructors in types derived from this class.
///
~ImageFactory()
{
// Do not re-create Dispose clean-up code here.
// Calling Dispose(false) is optimal in terms of
// readability and maintainability.
this.Dispose(false);
}
#endregion
#region Properties
///
/// Gets or sets the local image for manipulation.
///
public Image Image { get; set; }
///
/// Gets the path to the local image for manipulation.
///
public string ImagePath { get; private set; }
///
/// Gets the querystring params for web image manipulation.
///
public string QueryString { get; private set; }
///
/// Gets a value indicating whether the image factory should process the file.
///
public bool ShouldProcess { get; private set; }
///
/// Gets or sets the quality of output for jpeg images as a percentile.
///
internal int JpegQuality { get; set; }
///
/// Gets or sets the file format of the image.
///
internal ImageFormat ImageFormat { get; set; }
#endregion
#region Methods
///
/// Loads the image to process. Always call this method first.
///
///
/// The containing the image information.
///
///
/// The current instance of the class.
///
public ImageFactory Load(MemoryStream memoryStream)
{
// Set our image as the memorystream value.
this.Image = Image.FromStream(memoryStream);
// Store the stream in the image Tag property so we can dispose of it later.
this.Image.Tag = memoryStream;
// Set the other properties.
this.JpegQuality = DefaultJpegQuality;
this.ImageFormat = ImageFormat.Jpeg;
this.ShouldProcess = true;
return this;
}
///
/// Loads the image to process. Always call this method first.
///
/// The absolute path to the image to load.
///
/// The current instance of the class.
///
public ImageFactory Load(string imagePath)
{
string[] paths = imagePath.Split('?');
string path = paths[0];
string query = string.Empty;
if (paths.Length > 1)
{
query = paths[1];
}
string imageName = Path.GetFileName(path);
if (File.Exists(path))
{
this.ImagePath = path;
this.QueryString = query;
// Open a filstream to prevent the need for lock.
using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
MemoryStream memoryStream = new MemoryStream();
// Copy the stream.
fileStream.CopyTo(memoryStream);
// Set the position to 0 afterwards.
fileStream.Position = memoryStream.Position = 0;
// Set our image as the memorystream value.
this.Image = Image.FromStream(memoryStream);
// Store the stream in the image Tag property so we can dispose of it later.
this.Image.Tag = memoryStream;
// Set the other properties.
this.JpegQuality = DefaultJpegQuality;
this.ImageFormat = ImageUtils.GetImageFormat(imageName);
this.ShouldProcess = true;
}
}
return this;
}
#region Manipulation
///
/// Adds a querystring to the image factory to allow autoprocessing of remote files.
///
/// The querystring parameter to process.
///
/// The current instance of the class.
///
public ImageFactory AddQueryString(string query)
{
if (this.ShouldProcess)
{
this.QueryString = query;
}
return this;
}
///
/// Changes the opacity of the current image.
///
/// The percentage by which to alter the images opacity.
///
/// The current instance of the class.
///
public ImageFactory Alpha(int percentage)
{
if (this.ShouldProcess)
{
Alpha alpha = new Alpha { DynamicParameter = percentage };
this.Image = alpha.ProcessImage(this);
}
return this;
}
///
/// Crops an image to the given coordinates.
///
///
/// The containing the coordinates to crop the image to.
///
///
/// The current instance of the class.
///
public ImageFactory Crop(Rectangle rectangle)
{
if (this.ShouldProcess)
{
Crop crop = new Crop { DynamicParameter = rectangle };
this.Image = crop.ProcessImage(this);
}
return this;
}
///
/// Applies a filter to an image.
///
///
/// The name of the filter to add to the image.
///
///
/// The current instance of the class.
///
public ImageFactory Filter(string filterName)
{
if (this.ShouldProcess)
{
Filter filter = new Filter { DynamicParameter = filterName };
this.Image = filter.ProcessImage(this);
}
return this;
}
///
/// Sets the output format of the image to the matching .
///
/// The . to set the image to.
///
/// The current instance of the class.
///
public ImageFactory Format(ImageFormat imageFormat)
{
if (this.ShouldProcess)
{
this.ImageFormat = imageFormat;
}
return this;
}
///
/// Applies a filter to an image.
///
/// A value between 1 and 100 to set the quality to.
///
/// The current instance of the class.
///
public ImageFactory Quality(int percentage)
{
if (this.ShouldProcess)
{
this.JpegQuality = percentage;
}
return this;
}
///
/// Resizes an image to the given dimensions.
///
/// The width to set the image to.
/// The height to set the image to.
///
/// The current instance of the class.
///
public ImageFactory Resize(int width, int height)
{
if (this.ShouldProcess)
{
var resizeSettings = new Dictionary { { "MaxWidth", width.ToString("G") }, { "MaxHeight", height.ToString("G") } };
Resize resize = new Resize { DynamicParameter = new Size(width, height), Settings = resizeSettings };
this.Image = resize.ProcessImage(this);
}
return this;
}
///
/// Rotates the current image by the given angle.
///
///
/// The angle by which to rotate the image.
///
///
/// The background Colour.
///
///
/// The current instance of the class.
///
public ImageFactory Rotate(int angle, Color backgroundColour = default(Color))
{
if (this.ShouldProcess)
{
// Sanitize the input.
if (angle > 360 || angle < 0)
{
angle = 0;
}
RotateLayer rotateLayer = new RotateLayer { Angle = angle };
if (backgroundColour != default(Color))
{
rotateLayer.BackgroundColor = backgroundColour;
}
Rotate rotate = new Rotate { DynamicParameter = rotateLayer };
this.Image = rotate.ProcessImage(this);
}
return this;
}
///
/// Adds a vignette image effect to the current image.
///
///
/// The current instance of the class.
///
public ImageFactory Vignette()
{
if (this.ShouldProcess)
{
Vignette vignette = new Vignette();
this.Image = vignette.ProcessImage(this);
}
return this;
}
///
/// Adds a text based watermark to the image
///
///
/// The text layer containing the properties necessary to add the text based watermark to the image.
///
///
/// The current instance of the class.
///
public ImageFactory Watermark(TextLayer textLayer)
{
if (this.ShouldProcess)
{
Watermark watermark = new Watermark { DynamicParameter = textLayer };
this.Image = watermark.ProcessImage(this);
}
return this;
}
#endregion
///
/// Saves the current image to the specified file path.
///
/// The path to save the image to.
public void Save(string filePath)
{
if (this.ShouldProcess)
{
// We need to check here if the path has an extension and remove it if so.
// This is so we can add the correct image format.
int length = filePath.LastIndexOf(".", StringComparison.Ordinal);
string extension = ImageUtils.GetExtensionFromImageFormat(this.ImageFormat);
filePath = length == -1 ? filePath + extension : filePath.Substring(0, length) + extension;
// Fix the colour palette of gif images.
this.FixGifs();
if (object.Equals(this.ImageFormat, ImageFormat.Jpeg))
{
// Jpegs can be saved with different settings to include a quality setting for the JPEG compression.
// This improves output compression and quality.
using (EncoderParameters encoderParameters = ImageUtils.GetEncodingParameters(this.JpegQuality))
{
ImageCodecInfo imageCodecInfo =
ImageCodecInfo.GetImageEncoders().FirstOrDefault(
ici => ici.MimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase));
this.Image.Save(filePath, imageCodecInfo, encoderParameters);
}
}
else
{
this.Image.Save(filePath, this.ImageFormat);
}
}
}
///
/// Saves the current image to the specified output stream.
///
///
/// The to save the image information to.
///
public void Save(MemoryStream memoryStream)
{
if (this.ShouldProcess)
{
// Fix the colour palette of gif images.
this.FixGifs();
if (object.Equals(this.ImageFormat, ImageFormat.Jpeg))
{
// Jpegs can be saved with different settings to include a quality setting for the JPEG compression.
// This improves output compression and quality.
using (EncoderParameters encoderParameters = ImageUtils.GetEncodingParameters(this.JpegQuality))
{
ImageCodecInfo imageCodecInfo =
ImageCodecInfo.GetImageEncoders().FirstOrDefault(
ici => ici.MimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase));
if (imageCodecInfo != null)
{
this.Image.Save(memoryStream, imageCodecInfo, encoderParameters);
}
}
}
else
{
this.Image.Save(memoryStream, this.ImageFormat);
}
}
}
#region IDisposable Members
///
/// Disposes the object and frees resources for the Garbage Collector.
///
public void Dispose()
{
this.Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
///
/// Disposes the object and frees resources for the Garbage Collector.
///
/// If true, the object gets disposed.
protected virtual void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
// Dispose of any managed resources here.
if (this.Image != null)
{
// Dispose of the memorystream from Load and the image.
if (this.Image.Tag != null)
{
((IDisposable)this.Image.Tag).Dispose();
this.Image.Tag = null;
}
this.Image.Dispose();
this.Image = null;
}
}
// Call the appropriate methods to clean up
// unmanaged resources here.
// Note disposing is done.
this.isDisposed = true;
}
#endregion
///
/// Uses the
/// to fix the colour palette of gif images.
///
private void FixGifs()
{
// Fix the colour palette of gif images.
// TODO: Why does the palette not get fixed when resized to the same dimensions.
if (object.Equals(this.ImageFormat, ImageFormat.Gif))
{
OctreeQuantizer quantizer = new OctreeQuantizer(255, 8);
this.Image = quantizer.Quantize(this.Image);
}
}
#endregion
}
}