Last active
December 14, 2015 15:29
-
-
Save smonn/5108257 to your computer and use it in GitHub Desktop.
A class that resizes an image.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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