Skip to content

Instantly share code, notes, and snippets.

@stevecooperorg
Created February 20, 2017 23:49
Show Gist options
  • Save stevecooperorg/9b5108904001d6c1225d1209ae0d4bc8 to your computer and use it in GitHub Desktop.
Save stevecooperorg/9b5108904001d6c1225d1209ae0d4bc8 to your computer and use it in GitHub Desktop.
Compressor.cs -- resize a bitmap to within 5% of a target size
using System;
using System.Collections.Generic;
using System.Drawing; // include System.Drawing.dll
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// Compresses bitmaps to an approximate size
/// </summary>
public class Compressor
{
/// <summary>
/// How big will this file be as PNG bytes?
/// </summary>
/// <param name="bitmap"></param>
/// <returns></returns>
public long SizeOf(Bitmap bitmap, ImageFormat format)
{
using (var memoryStream = new MemoryStream())
{
bitmap.Save(memoryStream, format);
return memoryStream.Length;
}
}
/// <summary>
/// Return a bitmap of about the right size (to within 5%)
/// </summary>
/// <param name="original">the bitmap to scale</param>
/// <param name="targetSize">the target size, in bytes</param>
/// <param name="format">PNG, GIF, JPEG, etc</param>
/// <returns>the resized image</returns>
public Bitmap AutoScale(Bitmap original, long targetSize, ImageFormat format)
{
Console.WriteLine($"Auto-scaling to {targetSize:n0}b");
return AutoScale(original, targetSize, format, 1, 2);
}
/// <summary>
/// recursive function to seek the optimal size
/// </summary>
private Bitmap AutoScale(Bitmap original, long targetSize, ImageFormat format, int numerator, int divisor)
{
var currentScaleFactor = 1.0d * numerator / divisor;
var current = Scale(original, currentScaleFactor);
var currentFileSize = SizeOf(current, format);
Console.WriteLine($"Auto-scaling to {targetSize:n0}b using scale factor {numerator}/{divisor} - got {currentFileSize:n0} bytes");
var diff = currentFileSize - targetSize;
var abs = Math.Abs(diff);
var allowedError = targetSize * 0.05; // 5% error allowed
var sign = Math.Sign(diff);
if (abs < allowedError)
{
return current;
}
else if (sign == 1)
{
// too big;
return AutoScale(original, targetSize, format, numerator * 2 - 1, divisor * 2);
}
else
{
// too small;
return AutoScale(original, targetSize, format, numerator * 2 + 1, divisor * 2);
}
}
/// <summary>
/// high-quality resize function
/// </summary>
/// <param name="image"></param>
/// <param name="scaleFactor"></param>
/// <returns></returns>
private Bitmap Scale(Bitmap image, double scaleFactor)
{
var width = (int)(image.Width * scaleFactor);
var height = (int)(image.Height * scaleFactor);
var destRect = new Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment