// ----------------------------------------------------------------------- // // Copyright (c) James South. // Licensed under the Apache License, Version 2.0. // // ----------------------------------------------------------------------- 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; /// /// The backup image format. /// private ImageFormat backupImageFormat; /// /// 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 query-string parameters 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 the file format of the image. /// public ImageFormat ImageFormat { get; private set; } /// /// Gets or sets the quality of output for jpeg images as a percentile. /// internal int JpegQuality { 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.backupImageFormat = ImageFormat.Jpeg; 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; ImageFormat imageFormat = ImageUtils.GetImageFormat(imageName); this.backupImageFormat = imageFormat; this.ImageFormat = imageFormat; this.ShouldProcess = true; } } return this; } /// /// Resets the current image to its original loaded state. /// /// /// The current instance of the class. /// public ImageFactory Reset() { if (this.ShouldProcess) { MemoryStream memoryStream = (MemoryStream)this.Image.Tag; // Set our new image as the memorystream value. Image newImage = Image.FromStream(memoryStream); // Store the stream in the image Tag property so we can dispose of it later. newImage.Tag = memoryStream; // Dispose and reassign the image. this.Image.Dispose(); this.Image = newImage; // Set the other properties. this.JpegQuality = DefaultJpegQuality; this.ImageFormat = this.backupImageFormat; } return this; } #region Manipulation /// /// Adds a query-string to the image factory to allow auto-processing of remote files. /// /// The query-string 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. /// Any integer between 0 and 100. /// /// /// The current instance of the class. /// public ImageFactory Alpha(int percentage) { if (this.ShouldProcess) { // Sanitize the input. if (percentage > 100 || percentage < 0) { percentage = 0; } Alpha alpha = new Alpha { DynamicParameter = percentage }; this.Image = alpha.ProcessImage(this); } return this; } /// /// Changes the brightness of the current image. /// /// /// The percentage by which to alter the images brightness. /// Any integer between -100 and 100. /// /// /// The current instance of the class. /// public ImageFactory Brightness(int percentage) { if (this.ShouldProcess) { // Sanitize the input. if (percentage > 100 || percentage < -100) { percentage = 0; } Brightness brightness = new Brightness { DynamicParameter = percentage }; this.Image = brightness.ProcessImage(this); } return this; } /// /// Changes the contrast of the current image. /// /// /// The percentage by which to alter the images contrast. /// Any integer between -100 and 100. /// /// /// The current instance of the class. /// public ImageFactory Contrast(int percentage) { if (this.ShouldProcess) { // Sanitize the input. if (percentage > 100 || percentage < -100) { percentage = 0; } Contrast contrast = new Contrast { DynamicParameter = percentage }; this.Image = contrast.ProcessImage(this); } return this; } /// /// Crops the current image to the given location and size. /// /// /// 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 the current 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; } /// /// Flips the current image either horizontally or vertically. /// /// /// Whether to flip the image vertically. /// /// /// The current instance of the class. /// public ImageFactory Flip(bool flipVertically) { if (this.ShouldProcess) { RotateFlipType rotateFlipType = flipVertically == false ? RotateFlipType.RotateNoneFlipX : RotateFlipType.RotateNoneFlipY; Flip flip = new Flip { DynamicParameter = rotateFlipType }; this.Image = flip.ProcessImage(this); } return this; } /// /// Sets the output format of the current 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; } /// /// Alters the output quality of the current image. /// /// This method will only effect the output quality of jpeg images /// /// /// 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 the current image to the given dimensions. /// /// /// The containing the width and height to set the image to. /// /// /// The current instance of the class. /// public ImageFactory Resize(Size size) { if (this.ShouldProcess) { int width = size.Width; int height = size.Height; 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 containing the properties to rotate the image. /// /// /// The current instance of the class. /// public ImageFactory Rotate(RotateLayer rotateLayer) { if (this.ShouldProcess) { // Sanitize the input. if (rotateLayer.Angle > 360 || rotateLayer.Angle < 0) { rotateLayer.Angle = 0; } Rotate rotate = new Rotate { DynamicParameter = rotateLayer }; this.Image = rotate.ProcessImage(this); } return this; } /// /// Adds rounded corners to the current image. /// /// /// The containing the properties to round corners on the image. /// /// /// The current instance of the class. /// public ImageFactory RoundedCorners(RoundedCornerLayer roundedCornerLayer) { if (this.ShouldProcess) { if (roundedCornerLayer.Radius < 0) { roundedCornerLayer.Radius = 0; } RoundedCorners roundedCorners = new RoundedCorners { DynamicParameter = roundedCornerLayer }; this.Image = roundedCorners.ProcessImage(this); } return this; } /// /// Changes the saturation of the current image. /// /// /// The percentage by which to alter the images saturation. /// Any integer between -100 and 100. /// /// /// The current instance of the class. /// public ImageFactory Saturation(int percentage) { if (this.ShouldProcess) { // Sanitize the input. if (percentage > 100 || percentage < -100) { percentage = 0; } Saturation saturate = new Saturation { DynamicParameter = percentage }; this.Image = saturate.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 current image. /// /// /// The 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. /// /// The current instance of the class. /// public ImageFactory 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 (this.ImageFormat.Equals(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)); // ReSharper disable AssignNullToNotNullAttribute this.Image.Save(filePath, imageCodecInfo, encoderParameters); // ReSharper restore AssignNullToNotNullAttribute } } else { this.Image.Save(filePath, this.ImageFormat); } } return this; } /// /// Saves the current image to the specified output stream. /// /// /// The to save the image information to. /// /// /// The current instance of the class. /// public ImageFactory Save(MemoryStream memoryStream) { if (this.ShouldProcess) { // Fix the colour palette of gif images. this.FixGifs(); if (this.ImageFormat.Equals(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); } } return this; } #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 memory stream 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 color 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 } }