Skip to content

Instantly share code, notes, and snippets.

@smonn
Last active December 14, 2015 15:29
Show Gist options
  • Save smonn/5108257 to your computer and use it in GitHub Desktop.
Save smonn/5108257 to your computer and use it in GitHub Desktop.
A class that resizes an image.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Linq;
public sealed class ImageBuilder : IDisposable
{
public const long DefaultQuality = 90;
public const string DefaultMimeType = @"image/jpeg";
public const long QualityLowerBounds = 0;
public const long QualityUpperBounds = 100;
public const int BufferMinimumLength = 1;
public const float MinimumMaxWidth = 0;
public const float MinimumMaxHeight = 0;
private Stream _stream;
private bool _streamOwner;
private bool _enabledCrop;
private bool _cropCenteredSquare;
private RectangleF _cropArea;
private SizeF _bounds;
private SizeF _originalSize;
private SizeF _newSize;
private long _quality;
private Image _image;
private string _mimeType;
private EncoderParameters _encoderParameters;
private ImageCodecInfo _encoder;
private ImageBuilder()
{
_bounds = new SizeF();
_quality = DefaultQuality;
_mimeType = DefaultMimeType;
}
public static ImageBuilder Create()
{
return new ImageBuilder();
}
#region Builder members
public ImageBuilder MaxSize(float width, float height)
{
if (width < MinimumMaxWidth)
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Width was less than {0:0}.", MinimumMaxWidth), "width");
if (height < MinimumMaxHeight)
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Height was less than {0:0}.", MinimumMaxHeight), "height");
_bounds.Width = width;
_bounds.Height = height;
return this;
}
public ImageBuilder MaxSize(SizeF size)
{
return MaxSize(size.Width, size.Height);
}
public ImageBuilder MaxSize(Size size)
{
return MaxSize(size.Width, size.Height);
}
public ImageBuilder Source(Stream stream)
{
if (stream == null)
throw new ArgumentException("Stream must be set.", "stream");
DisposeStream();
_stream = stream;
_streamOwner = false;
return this;
}
public ImageBuilder Source(byte[] buffer)
{
if (buffer == null || buffer.Length < BufferMinimumLength)
throw new ArgumentException("Buffer must be set, or the buffer was empty.", "buffer");
DisposeStream();
_stream = new MemoryStream(buffer);
_streamOwner = true;
return this;
}
public ImageBuilder Source(string path)
{
if (string.IsNullOrEmpty(path) || !File.Exists(path))
throw new ArgumentException("Path must be set, or the file does not exist.", "path");
DisposeStream();
_stream = File.OpenRead(path);
_streamOwner = true;
return this;
}
public ImageBuilder Quality(long imageQuality)
{
if (_quality < QualityLowerBounds || imageQuality > QualityUpperBounds)
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Quality must be between {0} and {1} (inclusive).", QualityLowerBounds, QualityUpperBounds));
_quality = imageQuality;
return this;
}
public ImageBuilder MimeType(string imageMimeType)
{
if (string.IsNullOrEmpty(imageMimeType))
throw new ArgumentException("MIME type must be set. ", imageMimeType);
_mimeType = imageMimeType;
return this;
}
public ImageBuilder Crop(RectangleF cropArea)
{
if (cropArea == null)
throw new ArgumentException("Crop area must be set.", "cropArea");
_cropArea = cropArea;
_enabledCrop = true;
return this;
}
public ImageBuilder CropCenteredSquare()
{
_cropCenteredSquare = true;
_enabledCrop = true;
return this;
}
#endregion
public Image Image
{
get { Render(); return _image; }
}
public byte[] GetBytes()
{
Render();
using (var stream = new MemoryStream())
{
_image.Save(stream, _encoder, _encoderParameters);
return stream.ToArray();
}
}
public void SaveToFile(string path)
{
Render();
_image.Save(path, _encoder, _encoderParameters);
}
#region Private rendering members
private void CreateEncoderParameters()
{
DisposeEncoderParameters();
_encoderParameters = new EncoderParameters(1);
_encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, _quality);
}
private void CreateEncoder()
{
_encoder = ImageCodecInfo.GetImageEncoders().FirstOrDefault(c => c.MimeType.Equals(_mimeType));
if (_encoder == null) throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, @"Invalid MIME type: {0}", _mimeType));
}
private void Render()
{
if (_image != null || _stream == null || _bounds.IsEmpty) return;
using (var original = Image.FromStream(_stream))
{
_originalSize = original.Size;
CalculateNewSize();
CreateResizedImage(original);
}
CreateEncoder();
CreateEncoderParameters();
}
private void CreateResizedImage(Image original)
{
_image = GetTargetImage();
using (var graphics = Graphics.FromImage(_image))
{
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
DrawImage(graphics, original);
}
}
private Bitmap GetTargetImage()
{
return new Bitmap((int)_newSize.Width, (int)_newSize.Height);
}
private void DrawImage(Graphics graphics, Image original)
{
graphics.DrawImage(original, 0, 0, _newSize.Width, _newSize.Height);
if (!_enabledCrop || _image == null) return;
var cropRectangle = GetCropRectangle();
Bitmap croppedImage;
using (var tempImage = new Bitmap(_image))
croppedImage = tempImage.Clone(cropRectangle, tempImage.PixelFormat);
_image.Dispose();
_image = croppedImage;
}
private RectangleF GetCropRectangle()
{
if (!_cropArea.IsEmpty)
return _cropArea;
if (!_cropCenteredSquare)
throw new InvalidOperationException("Were not able to calculate a crop rectangle.");
var origin = new PointF();
var size = new SizeF();
if (_newSize.Width > _newSize.Height)
{
origin.Y = 0;
origin.X = _newSize.Width / 2 - _newSize.Height / 2;
size.Width = _newSize.Height;
size.Height = _newSize.Height;
}
else
{
origin.Y = _newSize.Height / 2 - _newSize.Width / 2;
origin.X = 0;
size.Width = _newSize.Width;
size.Height = _newSize.Width;
}
_cropArea = new RectangleF(origin, size);
return _cropArea;
}
private void CalculateNewSize()
{
var aspectRatio = _originalSize.Width / _originalSize.Height;
var boundsRatio = _bounds.Width / _bounds.Height;
var scaleFactor = _cropCenteredSquare ? GetReverseScaleFactor(aspectRatio, boundsRatio) : GetScaleFactor(aspectRatio, boundsRatio);
var newWidth = _originalSize.Width * scaleFactor;
var newHeight = _originalSize.Height * scaleFactor;
_newSize = new SizeF(newWidth, newHeight);
}
private float GetScaleFactor(float aspectRatio, float boundsRatio)
{
return boundsRatio > aspectRatio ? _bounds.Height / _originalSize.Height : _bounds.Width / _originalSize.Width;
}
private float GetReverseScaleFactor(float aspectRatio, float boundsRatio)
{
return GetScaleFactor(boundsRatio, aspectRatio);
}
#endregion
#region IDisposable Members
public void Dispose()
{
DisposeStream();
DisposeImage();
DisposeEncoderParameters();
}
private void DisposeEncoderParameters()
{
if (_encoderParameters != null)
_encoderParameters.Dispose();
}
private void DisposeImage()
{
if (_image != null)
_image.Dispose();
}
private void DisposeStream()
{
if (_stream != null && _streamOwner)
_stream.Dispose();
}
#endregion
}
// Load from stream, specify quality, MIME, size,
// and crop to center of image. Save a stream to a file
using (var builder = ImageBuilder.Create())
{
builder.Source(stream)
.Quality(imageQuality)
.MimeType(mimeType)
.MaxSize(bounds)
.CropCenteredSquare()
.SaveToFile(location);
}
// Load image from a location, use default quality
// and MIME, then get the bytes.
using (var builder = ImageBuilder.Create())
{
bytes = builder.Source(location)
.MaxSize(bounds)
.GetBytes();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment