using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web;
using System.Drawing.Drawing2D;
using SimplePaletteQuantizer.Quantizers;
namespace CssSpriteGenerator
{
public class ImageUtils
{
private static ImageCodecInfo _jpgCodec = null;
private static bool? _hasFullTrust = null;
/// <summary>
/// Saves a jpg image with the given quality
/// </summary>
/// <param name="bitmap">
/// Bitmap to be saved to disk
/// </param>
/// <param name="fileSystemPath">
/// Location where the image to be saved.
/// </param>
/// <param name="jpegQuality">
/// Quality reduction of the image. 25 means that jpg image will be written with its quality reduced to 25%.
/// </param>
public static void SaveJpgImage(Bitmap bitmap, string fileSystemPath, int jpegQuality)
{
ImageCodecInfo jpgCodec = GetJpgCodec();
if ((jpegQuality < 0) || (jpegQuality > 100) || (jpgCodec == null))
{
// Something went wrong. If we're in release mode, recover. Otherwise throw an exception.
if (HttpContext.Current.IsDebuggingEnabled)
{
throw new ArgumentOutOfRangeException(
string.Format("SaveJpgImage error - jpgEncoder={0}, jpegQuality={1}",
((jpgCodec == null) ? "null" : "not null"), jpegQuality));
}
else
{
bitmap.Save(fileSystemPath, ImageFormat.Jpeg);
return;
}
}
// Encoder parameter for image quality
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, jpegQuality);
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
// Save the image
bitmap.Save(fileSystemPath, jpgCodec, encoderParams);
}
/// <summary>
/// Returns the image codec for jpg images
/// </summary>
private static ImageCodecInfo GetJpgCodec()
{
if (_jpgCodec != null) { return _jpgCodec; }
// Get image codecs for all image formats
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
// Find the correct image codec
for (int i = 0; i < codecs.Length; i++)
{
if (codecs[i].MimeType == "image/jpeg")
{
_jpgCodec = codecs[i];
return _jpgCodec;
}
}
return null;
}
/// <summary>
/// Resizes a bitmap to a required size.
///
/// Resizing is an involved subject. See:
///
///http://nathanaeljones.com/163/20-image-resizing-pitfalls/
///http://msdn.microsoft.com/en-us/library/k0fsyd4e.aspx]
///http://glennjones.net/2005/10/high-quality-dynamically-resized-images-with-dot-net/
///http://www.peterprovost.org/blog/post/Resize-Image-in-C.aspx
///http://www.switchonthecode.com/tutorials/csharp-tutorial-image-editing-saving-cropping-and-resizing
///http://stackoverflow.com/questions/30569/resize-transparent-images-using-c
///
/// </summary>
/// <param name="bitmap">
/// Pass the bitmap to be resized here. This parameter will be updated to a bitmap with the new size.
/// </param>
/// <param name="newSize"></param>
public static void ResizeBitmap(ref Bitmap bitmap, Size newSize)
{
if (bitmap.Size == newSize) { return; }
// Note that if you try to create a Graphics object from a bitmap that has an indexed PixelFormat,
// you get an exception. So, give newBitmap an indexed PixelFormat, unless bitmap has a higher
// PixelFormat than the default.
PixelFormat newPixelFormat = PixelFormatUtils.HigherPixelFormat(PixelFormat.Format24bppRgb, bitmap.PixelFormat);
Bitmap newBitmap = new Bitmap(newSize.Width, newSize.Height, newPixelFormat);
newBitmap.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
Graphics graphics = Graphics.FromImage(newBitmap);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.DrawImage(bitmap, 0, 0, newSize.Width, newSize.Height);
// At this point, you could set the PixelFormat of the bitmap back to what it used to be, with something like:
// newBitmap.Clone(newBitmapRect, bitmap.PixelFormat);
// However, when you resize an image, it may need additional colors for anti aliasing. If you give it a low
// bits per pixel (such as one of the indexed formats), you get a lot of pixellation and a very ugly
// resized image. So, keep the higher PixelFormat you've got now and let the user reduce it (via group property
// PixelFormat) if they want to.
// Dispose the old bitmap
bitmap.Dispose();
bitmap = newBitmap;
}
/// <summary>
/// Returns true if the image with the given bitmap is animated.
/// </summary>
/// <param name="bitmap"></param>
/// <returns></returns>
public static bool IsAnimated(Bitmap bitmap)
{
int nbrFrames = bitmap.GetFrameCount(System.Drawing.Imaging.FrameDimension.Time);
return (nbrFrames > 1);
}
/// <summary>
/// Changes the pixel format of a bitmap and returns the new bitmap.
/// Does not change its dimensions.
/// </summary>
/// <param name="bitmap">
/// Input bitmap.
/// </param>
/// <param name="newPixelFormat">
/// New pixel format.
///
/// If we're running with medium trust level, if new pixel format is indexed, use
/// the lowest guaranteed non-indexed pixel format: Format24bppRgb.
///
/// This because if you run under a version of Windows before Windows 7,
/// you get an OutOfMemory exception if you try to use the CloneBitmap or the Bitmap constructor
/// to create a version of the bitmap with the new pixel format. And this uses the Windows palette
/// anyway, which is really shitty.
///
/// This means you have to use your own code to create a new Bitmap with the new indexed pixel format,
/// using eg. the Octree algorithm to find an optimal palette. But to do this quickly, you need
/// Bitmap.Lockbits to access the actual bytes making up the Bitmap - and this give you a security exception
/// if you're not in full trust.
///
/// TODO 1: If the source image has transparancy (either on/off or full alpha),
/// try to ensure that the output image has transparancy too.
///
/// TODO 2: Implement the latest release of
/// http://www.codeproject.com/KB/recipes/SimplePaletteQuantizer.aspx
/// </param>
/// <param name="paletteAlgorithm">
/// Only relevant if the new pixel format is an index format. Used to calculate the palette.
/// This can be PaletteAlgorithm.DontCare, in which case the method makes its own choice.
/// </param>
/// <returns>
/// Bitmap with the changed pixel format.
/// </returns>
public static Bitmap ChangePixelFormat(Bitmap bitmap, PixelFormat newPixelFormat, PaletteAlgorithm paletteAlgorithm)
{
// If bitmap already has the desired pixel format, just return a clone
if (bitmap.PixelFormat == newPixelFormat)
{
Bitmap bitmapClone = new Bitmap(bitmap);
return bitmapClone;
}
// If the nex pixel format is not indexed, simply use the .Net functionality
if (!PixelFormatUtils.IsIndexedPixelFormat(newPixelFormat))
{
return ChangePixelFormatWithDotNet(bitmap, newPixelFormat);
}
// At this stage, you know newPixelFormat is indexed.
// If we don't have full trust (that is, running in Medium trust shared hosting account),
// use Format24bppRgb instead of indexed format.
if (!HasFullTrust())
{
return ChangePixelFormatWithDotNet(bitmap, PixelFormat.Format24bppRgb);
}
// When not running under Windows 7, you consistently get an OutOfMemory exception when trying
// to clone a bitmap changing the pixel format to an indexed pixel format.
// This is a known issue:
// http://social.msdn.microsoft.com/Forums/en/vbgeneral/thread/90f6f014-26e1-46d4-bb0b-ec0632dda321
//
// So if new pixel format is indexed, don't rely on .Net.
// -----------------------------
// Adjust the algorithm if needed
PaletteAlgorithm paletteAlgorithmToUse = paletteAlgorithm;
if (newPixelFormat == PixelFormat.Format1bppIndexed)
{
paletteAlgorithmToUse = PaletteAlgorithm.Windows;
}
else if (newPixelFormat == PixelFormat.Format4bppIndexed)
{
// Uniform quantizer only supports 8bpp
if (paletteAlgorithm == PaletteAlgorithm.Uniform)
{
paletteAlgorithmToUse = PaletteAlgorithm.HSB;
}
}
if (paletteAlgorithm == PaletteAlgorithm.DontCare)
{
paletteAlgorithmToUse = PaletteAlgorithm.HSB;
}
// ------------------------
// If we're using the Windows algorithm, use CopyToBpp which is nice and fast.
if (paletteAlgorithmToUse == PaletteAlgorithm.Windows)
{
int bpp = PixelFormatUtils.BitsPerPixel(newPixelFormat);
Bitmap clonedSpriteBitmap = CopyToBpp.IndexedBitmap(bitmap, bpp);
return clonedSpriteBitmap;
}
// ----------------------------
// Otherwise use a Quantizer algo
IColorQuantizer colorQuantizer = null;
switch (paletteAlgorithmToUse)
{
case PaletteAlgorithm.HSB:
colorQuantizer = new SimplePaletteQuantizer.Quantizers.HSB.PaletteQuantizer();
break;
case PaletteAlgorithm.MedianCut:
colorQuantizer = new SimplePaletteQuantizer.Quantizers.Median.MedianCutQuantizer();
break;
case PaletteAlgorithm.Octree:
colorQuantizer = new SimplePaletteQuantizer.Quantizers.Octree.OctreeQuantizer();
break;
case PaletteAlgorithm.Popularity:
colorQuantizer = new SimplePaletteQuantizer.Quantizers.Popularity.PopularityQuantizer();
break;
case PaletteAlgorithm.Uniform:
colorQuantizer = new SimplePaletteQuantizer.Quantizers.Uniform.UniformQuantizer();
break;
default:
throw new Exception(
string.Format("ChangePixelFormat - unexpected paletteAlgorithmToUse: {0}", paletteAlgorithmToUse));
}
Bitmap newBitmap =
SimplePaletteQuantizer.Helpers.QuantizationImageHelper.GetQuantizedImage(
bitmap, colorQuantizer, newPixelFormat);
return newBitmap;
}
/// <summary>
/// Version of ChangePixelFormat that uses .Net functionality.
/// Don't use to change to an indexed pixel format.
/// </summary>
/// <param name="bitmap"></param>
/// <param name="newPixelFormat"></param>
/// <returns></returns>
private static Bitmap ChangePixelFormatWithDotNet(Bitmap bitmap, PixelFormat newPixelFormat)
{
// Bitmap.Clone does not reliably give the clonedSpriteBitmap the newPixelFormat. (bug in GDI+)
// Need to do the following little dance (from http://stackoverflow.com/questions/2016406/converting-bitmap-pixelformats-in-c)
Bitmap clonedSpriteBitmap = new Bitmap(bitmap.Width, bitmap.Height, newPixelFormat);
Rectangle imageRect = new Rectangle(0, 0, clonedSpriteBitmap.Width, clonedSpriteBitmap.Height);
using (Graphics gr = Graphics.FromImage(clonedSpriteBitmap))
{
gr.DrawImage(bitmap, imageRect);
}
return clonedSpriteBitmap;
}
public static bool DenotesSpecificNumberOfBitsPerPixel(PixelFormat? pixelFormat)
{
return
((pixelFormat == PixelFormat.Alpha) ||
(pixelFormat == PixelFormat.Extended) ||
(pixelFormat == PixelFormat.Gdi) ||
(pixelFormat == PixelFormat.Indexed) ||
(pixelFormat == PixelFormat.Max) ||
(pixelFormat == PixelFormat.PAlpha) ||
(pixelFormat == PixelFormat.Canonical));
}
/// <summary>
/// Returns true if the code runs in a full trust environment.
/// Returns false otherwise - this normally indicates a medium trust environment.
///
/// TODO: If you don't make ImageUtils.cs into a separate project, move the trust stuff to
/// a separate TrustUtils.cs file.
/// </summary>
/// <returns></returns>
private static bool HasFullTrust()
{
// If another task in the same app pool has already figured out the trust level,
// return that.
if (_hasFullTrust != null) { return _hasFullTrust ?? false; }
// Try to get it from cache
_hasFullTrust = CacheUtils.Entry<bool?>(CacheUtils.CacheEntryId.hasFullTrust, "");
if (_hasFullTrust == null)
{
AspNetHostingPermissionLevel currentTrustLevel = GetCurrentTrustLevel();
// Note that a "High" permission level (which is between Unrestricted and Medium)
// is not enough to run the image manipulation code in QuantizationImageHelper.
_hasFullTrust =
(currentTrustLevel == AspNetHostingPermissionLevel.Unrestricted);
CacheUtils.InsertEntry<bool?>(CacheUtils.CacheEntryId.hasFullTrust, "", _hasFullTrust, null);
}
return _hasFullTrust ?? false;
}
/// <summary>
/// Returns the current trust level, assuming this code (or the calling code up the stack),
/// is not loaded from GAC.
///
/// Based on
/// http://blogs.msdn.com/b/dmitryr/archive/2007/01/23/finding-out-the-current-trust-level-in-asp-net.aspx
/// </summary>
/// <returns></returns>
private static AspNetHostingPermissionLevel GetCurrentTrustLevel()
{
foreach (AspNetHostingPermissionLevel trustLevel in
new AspNetHostingPermissionLevel[] {
AspNetHostingPermissionLevel.Unrestricted,
AspNetHostingPermissionLevel.High,
AspNetHostingPermissionLevel.Medium,
AspNetHostingPermissionLevel.Low,
AspNetHostingPermissionLevel.Minimal
})
{
try
{
new AspNetHostingPermission(trustLevel).Demand();
}
catch (System.Security.SecurityException)
{
continue;
}
return trustLevel;
}
return AspNetHostingPermissionLevel.None;
}
}
}