High Quality Image Scaling Library

High Quality Image Scaling Library

Here's a nicely commented Image Manipulation helper class that you can look at and use. I wrote it as an example of how to perform certain image manipulation tasks in C#. You'll be interested in the ResizeImage function that takes a System.Drawing.Image, the width and the height as the arguments.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;

namespace DoctaJonez.Drawing.Imaging
{
/// <summary>
/// Provides various image untilities, such as high quality resizing and the ability to save a JPEG.
/// </summary>
public static class ImageUtilities
{
/// <summary>
/// A quick lookup for getting image encoders
/// </summary>
private static Dictionary<string, ImageCodecInfo> encoders = null;

/// <summary>
/// A lock to prevent concurrency issues loading the encoders.
/// </summary>
private static object encodersLock = new object();

/// <summary>
/// A quick lookup for getting image encoders
/// </summary>
public static Dictionary<string, ImageCodecInfo> Encoders
{
//get accessor that creates the dictionary on demand
get
{
//if the quick lookup isn't initialised, initialise it
if (encoders == null)
{
//protect against concurrency issues
lock (encodersLock)
{
//check again, we might not have been the first person to acquire the lock (see the double checked lock pattern)
if (encoders == null)
{
encoders = new Dictionary<string, ImageCodecInfo>();

//get all the codecs
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
{
//add each codec to the quick lookup
encoders.Add(codec.MimeType.ToLower(), codec);
}
}
}
}

//return the lookup
return encoders;
}
}

/// <summary>
/// Resize the image to the specified width and height.
/// </summary>
/// <param name="image">The image to resize.</param>
/// <param name="width">The width to resize to.</param>
/// <param name="height">The height to resize to.</param>
/// <returns>The resized image.</returns>
public static System.Drawing.Bitmap ResizeImage(System.Drawing.Image image, int width, int height)
{
//a holder for the result
Bitmap result = new Bitmap(width, height);
//set the resolutions the same to avoid cropping due to resolution differences
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);

//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}

//return the resulting bitmap
return result;
}

/// <summary>
/// Saves an image as a jpeg image, with the given quality
/// </summary>
/// <param name="path">Path to which the image would be saved.</param>
/// <param name="quality">An integer from 0 to 100, with 100 being the
/// highest quality</param>
/// <exception cref="ArgumentOutOfRangeException">
/// An invalid value was entered for image quality.
/// </exception>
public static void SaveJpeg(string path, Image image, int quality)
{
//ensure the quality is within the correct range
if ((quality < 0) || (quality > 100))
{
//create the error message
string error = string.Format("Jpeg image quality must be between 0 and 100, with 100 being the highest quality. A value of {0} was specified.", quality);
//throw a helpful exception
throw new ArgumentOutOfRangeException(error);
}

//create an encoder parameter for the image quality
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
//get the jpeg codec
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");

//create a collection of all parameters that we will pass to the encoder
EncoderParameters encoderParams = new EncoderParameters(1);
//set the quality parameter for the codec
encoderParams.Param[0] = qualityParam;
//save the image using the codec and the parameters
image.Save(path, jpegCodec, encoderParams);
}

/// <summary>
/// Returns the image codec with the given mime type
/// </summary>
public static ImageCodecInfo GetEncoderInfo(string mimeType)
{
//do a case insensitive search for the mime type
string lookupKey = mimeType.ToLower();

//the codec to return, default to null
ImageCodecInfo foundCodec = null;

//if we have the encoder, get it to return
if (Encoders.ContainsKey(lookupKey))
{
//pull the codec from the lookup
foundCodec = Encoders[lookupKey];
}

return foundCodec;
}
}
}

Update

A few people have been asking in the comments for samples of how to consume the ImageUtilities class, so here you go.

//resize the image to the specified height and width
using (var resized = ImageUtilities.ResizeImage(image, 50, 100))
{
//save the resized image as a jpeg with a quality of 90
ImageUtilities.SaveJpeg(@"C:\myimage.jpeg", resized, 90);
}

Note

Remember that images are disposable, so you need to assign the result of your resize to a using declaration (or you could use a try finally and make sure you call dispose in your finally).

Best Image Scaling Library

In my private projects, I don't use any specific library, the functionality provided for Java gives decent results for me. If you just want to do image scaling, then a complete image processing library would be too heavyweighted.

I use the code snippets given in http://www.mkyong.com/java/how-to-resize-an-image-in-java/ which works quite well.

Resize image, need a good library

You can use the HTML5 Canvas element to blit your image on it and then resize it appropriately

You create a canvas on-the-fly and perform pixel operations on it - so don't worry it's not visible anywhere on the DOM - think of it like a virtual canvas

Here is a little script I've written a while ago(with some help from another S.O question which I don't really remember) - that allows you to do this - whilst keeping the aspect ratios of the image:

Arguments

  • You provide an image directly into it
  • You provide a maxWidth/maxHeight and it will preserve at least one of the 2
  • It allows you to also rotate the image by defining the degrees parameter(which would be really useful on mobile devices since different devices provide arbitrarily rotated images - you will find out about this the hard way soon if you are dealing with mobile devices)

Returns

  • It returns a Base64 string of the resized image

function resizeImg(img, maxWidth, maxHeight, degrees) {
var imgWidth = img.width,
imgHeight = img.height;

var ratio = 1,
ratio1 = 1,
ratio2 = 1;
ratio1 = maxWidth / imgWidth;
ratio2 = maxHeight / imgHeight;

// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
if (ratio1 < ratio2) {
ratio = ratio1;
} else {
ratio = ratio2;
}
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
var canvasCopy2 = document.createElement("canvas");
var copyContext2 = canvasCopy2.getContext("2d");
canvasCopy.width = imgWidth;
canvasCopy.height = imgHeight;
copyContext.drawImage(img, 0, 0);

// init
canvasCopy2.width = imgWidth;
canvasCopy2.height = imgHeight;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


var rounds = 1;
var roundRatio = ratio * rounds;
for (var i = 1; i <= rounds; i++) {


// tmp
canvasCopy.width = imgWidth * roundRatio / i;
canvasCopy.height = imgHeight * roundRatio / i;

copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

// copy back
canvasCopy2.width = imgWidth * roundRatio / i;
canvasCopy2.height = imgHeight * roundRatio / i;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

} // end for

canvas.width = imgWidth * roundRatio / rounds;
canvas.height = imgHeight * roundRatio / rounds;
canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


if (degrees == 90 || degrees == 270) {
canvas.width = canvasCopy2.height;
canvas.height = canvasCopy2.width;
} else {
canvas.width = canvasCopy2.width;
canvas.height = canvasCopy2.height;
}

canvasContext.clearRect(0, 0, canvas.width, canvas.height);
if (degrees == 90 || degrees == 270) {
canvasContext.translate(canvasCopy2.height / 2, canvasCopy2.width / 2);
} else {
canvasContext.translate(canvasCopy2.width / 2, canvasCopy2.height / 2);
}
canvasContext.rotate(degrees * Math.PI / 180);
canvasContext.drawImage(canvasCopy2, -canvasCopy2.width / 2, -canvasCopy2.height / 2);


var dataURL = canvas.toDataURL();
return dataURL;
}

And here's a working code snippet that allows you to upload from your filesystem and resize on the fly:





/*

-------------------------------

-------HANDLE FILE UPLOAD------

-------------------------------

*/


var input = document.getElementById('input');

input.addEventListener('change', handleFiles);


function handleFiles(e) {

var img = new Image;

img.src = URL.createObjectURL(e.target.files[0]);

img.onload = function() {

var base64String = resizeImg(img, 300, 300, 0); //HERE IS WHERE THE FUNCTION RESIZE IS CALLED!!!!

alert(base64String);

document.getElementById('previewImg').src = base64String;

}

}




/*

-------------------------------

-------RESIZING FUNCTION-------

-------------------------------

*/



function resizeImg(img, maxWidth, maxHeight, degrees) {

var imgWidth = img.width,

imgHeight = img.height;


var ratio = 1,

ratio1 = 1,

ratio2 = 1;

ratio1 = maxWidth / imgWidth;

ratio2 = maxHeight / imgHeight;


// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.

if (ratio1 < ratio2) {

ratio = ratio1;

} else {

ratio = ratio2;

}

var canvas = document.createElement("canvas");

var canvasContext = canvas.getContext("2d");

var canvasCopy = document.createElement("canvas");

var copyContext = canvasCopy.getContext("2d");

var canvasCopy2 = document.createElement("canvas");

var copyContext2 = canvasCopy2.getContext("2d");

canvasCopy.width = imgWidth;

canvasCopy.height = imgHeight;

copyContext.drawImage(img, 0, 0);


// init

canvasCopy2.width = imgWidth;

canvasCopy2.height = imgHeight;

copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);



var rounds = 1;

var roundRatio = ratio * rounds;

for (var i = 1; i <= rounds; i++) {



// tmp

canvasCopy.width = imgWidth * roundRatio / i;

canvasCopy.height = imgHeight * roundRatio / i;


copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);


// copy back

canvasCopy2.width = imgWidth * roundRatio / i;

canvasCopy2.height = imgHeight * roundRatio / i;

copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


} // end for


canvas.width = imgWidth * roundRatio / rounds;

canvas.height = imgHeight * roundRatio / rounds;

canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);



if (degrees == 90 || degrees == 270) {

canvas.width = canvasCopy2.height;

canvas.height = canvasCopy2.width;

} else {

canvas.width = canvasCopy2.width;

canvas.height = canvasCopy2.height;

}


canvasContext.clearRect(0, 0, canvas.width, canvas.height);

if (degrees == 90 || degrees == 270) {

canvasContext.translate(canvasCopy2.height / 2, canvasCopy2.width / 2);

} else {

canvasContext.translate(canvasCopy2.width / 2, canvasCopy2.height / 2);

}

canvasContext.rotate(degrees * Math.PI / 180);

canvasContext.drawImage(canvasCopy2, -canvasCopy2.width / 2, -canvasCopy2.height / 2);



var dataURL = canvas.toDataURL();

return dataURL;

}
/*

-------------------------------

-------UNNECESSARY CSS---------

-------------------------------

*/


@import url(http://fonts.googleapis.com/css?family=Lato);

.container {

margin: 0 auto;

width: 400px;

height: 400px;

box-shadow: 1px 1px 1px 1px gray;

}

h3,

h4,

h5,

h6 {

margin: 4px !important;

}

.container,

.container * {

display: block;

margin: 12px auto;

font-family: 'Lato';

}

.header {

background-color: #2196F3;

padding: 12px;

color: #fff;

}

.container input {

width: 128px;

height: 32px;

cursor: pointer;

}

.container img {

display: block;

margin: 12px auto;

}
<div class="container">

<div class="header">

<h3>Choose a file</h3>

<h6>and I will alert back to you the base64 string of it's resized version</h6>

</div>

<input type="file" id="input" />

<hr>

<h5>Image Preview:</h5>

<img id="previewImg" src="" />


</div>

Java - Good and free library to resize images

You should try out imgscalr:

https://github.com/thebuzzmedia/imgscalr

http://www.htmlgoodies.com/beyond/java/create-high-quality-thumbnails-using-the-imgscalr-library.html

Image quality issues using Java library imgsclr to RESIZE

I don't think the core issue is with imgsclr, but with the choice of jpg. Remember, jpg uses a loss-based algorithm, dumping parts of the image to reduce it's size.

For comparison, the two images below were produced using the same methods, but the left was output using jpg and the right using png (using the ImageIO API)

Compare

So the top image is the same master image.

The second row is imgsclr using ULTRA_QUALITY, AUTOMATIC, BALANCED and QUALITY methods. The last image on the second row is using SPEED

The last row is a serious of "other" scaling methods. The first two use a divide and conquer approach, dividing the image by 2 until it reaches it's desired size, demonstrated here. The first is a "to fill" and the other is "to fit" (one will overfill the available space, one will fit within). The third image is using Image#getScaledInstance and I seem to have introduced a alpha channel somewhere, hence the reason the jpg version is red, but I was comparing quality ;)

The reason for the size increase may come down to imgsclr attempting to maintain the aspect ratio of the image

And finally...

Sample Image

This compares BufferedImageOp, using OP_BRIGHTER, OP_ANTIALIAS and OP_BRIGHTER, OP_ANTIALIAS, none from right to left...

These are output as PNGs



Related Topics



Leave a reply



Submit