I'm resizing some images to the screen resolution of the user; if the aspect ratio is wrong, the image should be cut.
My code looks like this:
protected void ConvertToBitmap(string filename)
{
var origImg = System.Drawing.Image.FromFile(filename);
var widthDivisor = (double)origImg.Width / (double)System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
var heightDivisor = (double)origImg.Height / (double)System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
int newWidth, newHeight;
if (widthDivisor < heightDivisor)
{
newWidth = (int)((double)origImg.Width / widthDivisor);
newHeight = (int)((double)origImg.Height / widthDivisor);
}
else
{
newWidth = (int)((double)origImg.Width / heightDivisor);
newHeight = (int)((double)origImg.Height / heightDivisor);
}
var newImg = origImg.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero);
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
}
In most cases, this works fine. But for some images, the result has an extremely poor quality. It looks like the would have been resized to something very small (thumbnail size) and enlarged again.. But the resolution of the image is correct. What can I do?
Example orig image:
alt text http://img523.imageshack.us/img523/1430/naturaerowoods.jpg
Example resized image:
Note: I have a WPF application but I use the WinForms function for resizing because it's easier and because I already need a reference to System.Windows.Forms for a tray icon.
Change the last two lines of your method to this:
var newImg = new Bitmap(newWidth, newHeight);
Graphics g = Graphics.FromImage(newImg);
g.DrawImage(origImg, new Rectangle(0,0,newWidth,newHeight));
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
g.Dispose();
I cannot peek into the .NET source at the moment, but most likely the problem is in the Image.GetThumbnailImage method. Even MSDN says that "it works well when the requested thumbnail image has a size of about 120 x 120 pixels, but it you request a large thumbnail image (for example, 300 x 300) from an Image that has an embedded thumbnail, there could be a noticeable loss of quality in the thumbnail image". For true resizing (i.e. not thumbnailing), you should use the Graphics.DrawImage method. You may also need to play with the Graphics.InterpolationMode to get a better quality if needed.
If you're not creating a thumbnail, using a method called GetThumbnailImage probably isn't a good idea...
For other options, have a look at this CodeProject article. In particular, it creates a new image, creates a Graphics for it and sets the interpolation mode to HighQualityBicubic and draws the original image onto the graphics. Worth a try, at least.
As indicated on MSDN, GetThumbnailImage() is not designed to do arbitrary image scaling. Anything over 120x120 should be scaled manually. Try this instead:
using(var newImg = new Bitmap(origImg, newWidth, newHeight))
{
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
}
Edit
As a point of clarification, this overload of the Bitmap constructor calls Graphics.DrawImage, though you do not have any control over the interpolation.
instead of this code:
newImg.Save(this.GetBitmapPath(filename), System.Drawing.Imaging.ImageFormat.Bmp);
use this one :
System.Drawing.Imaging.ImageCodecInfo[] info = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
System.Drawing.Imaging.EncoderParameters param = new System.Drawing.Imaging.EncoderParameters(1);
param.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
newImg.Save(dest_img, info[1], param);
For examples, the original image is JPG and the resized image is PNG. Are you converting between formats on purpose? Switching between different lossey compression schemes can cause quality loss.
Are you increasing or decreasing the size of the image when you resize it? If you are creating a larger image from a smaller one, this sort of degradation is to be expected.
Images will definitely be degraded if you enlarge them.
Some camera's put a resized thumbnail into the file itself presumably for preview purposes on the device itself.
The GetThumbnail method actually gets this Thumbnail image which is embedded within the image file instead of getting the higher res method.
The easy solution is to trick .Net into throwing away that thumbnail information before doing your resize or other operation. like so....
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
//removes thumbnails from digital camera shots
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
If you are attempting to resize constraining proportions I wrote an extension method on System.Drawing.Image that you might find handy.
/// <summary>
///
/// </summary>
/// <param name="img"></param>
/// <param name="size">Size of the constraining proportion</param>
/// <param name="constrainOnWidth"></param>
/// <returns></returns>
public static System.Drawing.Image ResizeConstrainProportions(this System.Drawing.Image img,
int size, bool constrainOnWidth, bool dontResizeIfSmaller)
{
if (dontResizeIfSmaller && (img.Width < size))
return img;
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
img.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
float ratio = 0;
ratio = (float)img.Width / (float)img.Height;
int height, width = 0;
if (constrainOnWidth)
{
height = (int)(size / ratio);
width = size;
}
else
{
width = (int)(size * ratio);
height = size;
}
return img.GetThumbnailImage(width, height, null, (new System.IntPtr(0)));
}
This is going to vary widely based on the following factors:
How closely the destination resolution matches a "natural" scale of the original resolution
The source image color depth
The image type(s) - some are more lossy than others
Related
Problem:
I have a watermark that I want to print on an Image. The Image varies in size so sometimes the watermark is too big and sometimes it's too small. In order to fix this I calculate the size of the image and resize the watermark. However, after resizing the Image, black borders appear around its margins.
Code
I am on a Mac using .NET Core3.1 and I am using two NuGet packages that helps to draw images / bitmaps. One is System.Drawing.Common and the other one, because I am on macOS is, runtime.osx.10.10x64.CoreCompat.System.Drawing.
The code that I use to resize the watermark founded here:
Bitmap watermarkNew = new Bitmap(watermark, new Size(image.Width / 10 * 3, image.Height / 10 * 3));
I have to use / 10 * 3 because the Bitmap constructor doesn't accept floats values, so I cannot multiply by * 0.3.
Results:
watermark before watermark after
To superimpose an Image on another, it's preferable to use an unscaled Image than generate a new Bitmap based on the desired size beforehand.
▶ The two Image are meant to blend, thus the scaling of one of the Images, in this case the Watermark Image, should be performed while the Image to scale is painted over the other with a SourceOver operation.
This way, the internal GDI+ (well, the GDI+ replica here) functions have means to calculate the blending procedure correctly.
This also prevents the copy to show imperfect semi-transparent pixels (similar to a dark halo) generated when a smaller Image is created using the new Bitmap() method.
▶ Also, we need to be sure that all operations are performed on a 32BitArgb Bitmaps.
It's better to create a 32BitArgb copy of the destination Image and draw the watermark on this copy. This can also ensure a better result. GDI+ function work better on this kind of Images.
Here, the CopyToArgb32() method takes care of this aspect, also applying the DPI resolution of the original Image to the copy.
▶ Furthermore, this produces a distorted Image (unless that's the expected result, that is):
Bitmap watermarkNew = new Bitmap(watermark, new Size(image.Width / 10 * 3, image.Height / 10 * 3));
The watermark Image dimensions should be resized calculating a scale factor that is a desired fraction (a percentage or a fixed measure) or the destination Image.
For example, to occupy a maximum size equals to one third of the destination Bitmap minimum dimension.
In other words, if the destination Bitmap size is 1500x600 px, the watermark Bitmap will be scaled proportionally to have a maximum Height of 200px:
float scale = (Math.Min(original.Width, original.Height) * .33f) /
Math.Min(watermark.Width, watermark.Height);
SizeF watermarkSize = new SizeF(watermark.Width * scale, watermark.Height * scale);
To further improve the blending, the Watermark could be made less opaque (or, more transparent, as you want to see it).
This can be simply achieved using as ColorMatrix as shown here:
How to apply a fade transition effect to Images
All combined in a class object that exposes a Watermark([Bitmap], [Bitmap], [Imageformat]) static method.
In the sample code, the Watermark is scaled to 1/3 of the maximum dimension of destination image and centered (just a generic placement, since the position of the watermark is not specified):
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
public class BitmapOperations
{
public static Bitmap Watermark(Bitmap watermark, Bitmap original, ImageFormat format)
{
var units = GraphicsUnit.Pixel;
float scale = (Math.Max(original.Width, original.Height) * .33f) /
Math.Max(watermark.Width, watermark.Height);
var watermarkSize = new SizeF(watermark.Width * scale, watermark.Height * scale);
var watermarkBounds = CenterRectangleOnRectangle(
new RectangleF(PointF.Empty, watermarkSize), original.GetBounds(ref units));
var workImage = CopyToArgb32(original);
// Using the SetOpacity() extension method described in the linked question
// watermark = watermark.SetOpacity(.5f, 1.05f);
using (var g = Graphics.FromImage(workImage)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(watermark, watermarkBounds);
return workImage;
}
}
private static Bitmap CopyToArgb32(Bitmap source)
{
var bitmap = new Bitmap(source.Width, source.Height, PixelFormat.Format32bppArgb);
bitmap.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using (var g = Graphics.FromImage(bitmap)) {
g.DrawImage(source, new Rectangle(0, 0, bitmap.Width, bitmap.Height),
new Rectangle(0, 0, bitmap.Width, bitmap.Height), GraphicsUnit.Pixel);
g.Flush();
}
return bitmap;
}
private static RectangleF CenterRectangleOnRectangle(RectangleF source, RectangleF destination)
{
source.Location = new PointF((destination.Width - source.Width) / 2,
(destination.Height - source.Height) / 2);
return source;
}
}
Results:
Applying an opacity level of 50% and small correction in gamma:
I'm trying to create a level editor using Windows Forms for my monogame project and need to draw small pixel based images to a picture box with no quality loss when scaled. In monogame when I need to do this I can just set the draw type to PointClamp and then each pixel is drawn as is instead of being pixelated when zoomed; I was hoping for something like this via a picturebox. Right now it looks like this But I'd prefer a more crisp and clean image like this (The second is how it'll appear in monogame). I haven't uploaded any code for this, but just assume I grabbed an image from the filestream and used the bitmap constructor to scale it up (don't think that's relevent but I'll just put it out there).
Image croppedImage, image = tileMap.tileBox.Image;
var brush = new SolidBrush(Color.Black);
try { croppedImage = CropImage(image, tileMap.highlightedRect); } catch {
return; // If crop target is outside bounds of image then return
}
float scale = Math.Min(higlightedTileBox.Width / croppedImage.Width, higlightedTileBox.Height / image.Height);
var scaleWidth = (int)(higlightedTileBox.Width * scale);
var scaleHeight = (int)(higlightedTileBox.Height * scale);
try { higlightedTileBox.Image = new Bitmap(croppedImage, new Size(scaleWidth, scaleHeight)); } catch {
return; // Image couldn't be scaled or highlighted tileBox couldn't be set to desired image
}
CropImage:
private static Image CropImage(Bitmap img, Rectangle cropArea) {
return img.Clone(cropArea, img.PixelFormat);
}
private static Image CropImage(Image img, Rectangle cropArea) {
return CropImage(new Bitmap(img), cropArea);
}
The code above is my current method in it's entirety. tileMap is a form and tilebox is the picturebox within that form.image is the full spritesheet texture before being cropped to what the user has highlighted. After being cropped I attempt to set the current picturebox (highlightedTileBox's) image to a scaled up version of the cropped image.
So I got a solution by trying around a bit.
It looks like scaling images directly by size is using some sort of interpolation.
To try different interpolation modes supported by Winforms, I created a little demo.
As you can see, every label contains the name of the InterpolationMode and is followed by its resulting image. The original bitmap I used is the small one at the top.
From your question, it looks like you would like to achieve something like NearestNeighbour.
Following code scales bmp and the result is stored in bmp2. Try if that's what you want. Consider building a proper implementation if you're using this as solution (disposing unused bitmaps etc.).
I hope it helps.
Bitmap bmp = new Bitmap("test.bmp");
Bitmap bmp2;
Graphics g = Graphics.FromImage(bmp2=new Bitmap(bmp.Width * 2, bmp.Height * 2));
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(bmp, 0, 0, bmp.Width * 2, bmp.Height * 2);
g.Dispose();
I've searched a bit around the discussions\forums/StackOverflow/Official documentation, but i couldn't find much information about how to achieve what i'm trying. Most of the official documentation covers the command-line version of ImageMagick.
I'll describe what i'm trying to do:
I have a image loaded that i would like to paste into a larger one.
Ex: the image i loaded has 9920 width, 7085 height. I would like to place it in the middle of a larger one (10594 width, 7387 height). I do have all border calculation ready ([larger width - original width / 2] , same goes for height).
But i don't know how to do it using MagickImage. Here's the max i got:
private void drawInkzone(MagickImage loadedImage, List<string>inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation);
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI);
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);
using (MagickImage image = new MagickImage(MagickColor.FromRgb(255, 255, 255), Convert.ToInt32(zoneAreaWidth_Pixels), Convert.ToInt32(zoneAreaHeight_Pixels)))
{
//first: defining the larger image, with a white background (must be transparent, but for now its okay)
using (MagickImage original = loadedImage.Clone())
{
//Cloned the original image (already passed as parameter)
}
}
Here's the max i got. In order to achieve this, i used the following post:
How to process only one part of image by ImageMagick?
And i'm not using GDI+ because i'll be always working with larger TIFF files (big resolutions), and GDI+ tends to throw exceptions (Parameter not valid, out of memory) when it can't handle everything (i loaded three images with an resolution like that, and got out of memory).
Any help will be kindly appreciate, thanks.
Pablo.
You could either Composite the image on top of a new image with the required background or you could Clone and Extent if with the required background. In the answer from #Pablo Costa there is an example for Compositing the image so here is an example on how you could extent the image:
private void drawInkzone(MagickImage loadedImage, List<string> inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation);
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI);
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);
using (MagickImage image = loadedImage.Clone())
{
MagickColor background = MagickColors.Black;
int width = (int)zoneAreaWidth_Pixels;
int height = (int)zoneAreaHeight_Pixels;
image.Extent(width, height, Gravity.Center, background);
image.Write(#"C:\DI_PLOT\whatever.png");
}
}
I managed to accomplish what i needed.
Cool that i didn't had to calculate borders.
Here's the code:
private void drawInkzone(MagickImage loadedImage, List<string>inkzoneAreaInformation, string filePath)
{
unitConversion converter = new unitConversion();
List<double> inkZoneInfo = inkZoneListFill(inkzoneAreaInformation); //Larger image information
float DPI = getImageDPI(filePath);
double zoneAreaWidth_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(4), DPI); //Width and height for the larger image are in mm , converted them to pixel
double zoneAreaHeight_Pixels = converter.mmToPixel(inkZoneInfo.ElementAt(5), DPI);//Formula (is: mm * imageDPI) / 25.4
using (MagickImage image = new MagickImage(MagickColor.FromRgb(0, 0, 0), Convert.ToInt32(zoneAreaWidth_Pixels), Convert.ToInt32(zoneAreaHeight_Pixels)))
{
//first: defining the larger image, with a white background (must be transparent, but for now its okay)
using (MagickImage original = loadedImage.Clone())
{
//Cloned the original image (already passed as parameter)
image.Composite(loadedImage, Gravity.Center);
image.Write(#"C:\DI_PLOT\whatever.png");
}
}
Hope this helps someone :)
Is there a way to resize an image using GPU (graphic card) that is consumable through a .NET application?
I am looking for an extremely performant way to resize images and have heard that the GPU could do it much quicker than CPU (GDI+ using C#). Are there known implementations or sample code using the GPU to resize images that I could consume in .NET?
Have you thought about using XNA to resize your images? Here you can find out how to use XNA to save image as a png/jpeg to a MemoryStream and later reuse it a Bitmap object:
EDIT: I will post an example here (taken from the link above) on how you can possibly use XNA.
public static Image Texture2Image(Texture2D texture)
{
Image img;
using (MemoryStream MS = new MemoryStream())
{
texture.SaveAsPng(MS, texture.Width, texture.Height);
//Go To the beginning of the stream.
MS.Seek(0, SeekOrigin.Begin);
//Create the image based on the stream.
img = Bitmap.FromStream(MS);
}
return img;
}
I also found out today that you can OpenCV to use GPU/multicore CPUs. You can for example choose to use a .NET wrapper such as Emgu and and use its Image class to manipulate with your picture and return a .NET Bitmap class:
public static Bitmap ResizeBitmap(Bitmap sourceBM, int width, int height)
{
// Initialize Emgu Image object
Image<Bgr, Byte> img = new Image<Bgr, Byte>(sourceBM);
// Resize using liniear interpolation
img.Resize(width, height, INTER.CV_INTER_LINEAR);
// Return .NET Bitmap object
return img.ToBitmap();
}
I wrote a quick spike to check performance using WPF, though I cannot for sure say that its using the GPU.
Still, see below. This scales an image to 33.5 (or whatever) times its original size.
public void Resize()
{
double scaleFactor = 33.5;
var originalFileStream = System.IO.File.OpenRead(#"D:\SkyDrive\Pictures\Random\Misc\DoIt.jpg");
var originalBitmapDecoder = JpegBitmapDecoder.Create(originalFileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapFrame originalBitmapFrame = originalBitmapDecoder.Frames.First();
var originalPixelFormat = originalBitmapFrame.Format;
TransformedBitmap transformedBitmap =
new TransformedBitmap(originalBitmapFrame, new System.Windows.Media.ScaleTransform()
{
ScaleX = scaleFactor,
ScaleY = scaleFactor
});
int stride = ((transformedBitmap.PixelWidth * transformedBitmap.Format.BitsPerPixel) + 7) / 8;
int pixelCount = (stride * (transformedBitmap.PixelHeight - 1)) + stride;
byte[] buffer = new byte[pixelCount];
transformedBitmap.CopyPixels(buffer, stride, 0);
WriteableBitmap transformedWriteableBitmap = new WriteableBitmap(transformedBitmap.PixelWidth, transformedBitmap.PixelHeight, transformedBitmap.DpiX, transformedBitmap.DpiY, transformedBitmap.Format, transformedBitmap.Palette);
transformedWriteableBitmap.WritePixels(new Int32Rect(0, 0, transformedBitmap.PixelWidth, transformedBitmap.PixelHeight), buffer, stride, 0);
BitmapFrame transformedFrame = BitmapFrame.Create(transformedWriteableBitmap);
var jpegEncoder = new JpegBitmapEncoder();
jpegEncoder.Frames.Add(transformedFrame);
using (var outputFileStream = System.IO.File.OpenWrite(#"C:\DATA\Scrap\WPF.jpg"))
{
jpegEncoder.Save(outputFileStream);
}
}
The image I was testing was 495 x 360. It resized it to over 16k x 12k in a couple of seconds, including save out.
It resizes to 1.5x around 165 times a second in a single-core run. On an i7 and the GPU seemingly doing nothing, CPU at 20% I'd expect to get 5x more when multithreaded.
Performance profiling shows a hot path to wpfgfx_v0400.dll which is the native WPF graphics library and is close to DirectX (look-up 'milcore' in Google).
So it might be accelerated, I don't know.
Luke
Yes, it is possible to use GPU to resize your images. This can be done using DirectX Surfaces (for example using SlimDx in C#). You should create a surface and move your image to it, and then you can stretch this surface to another target surface of your desired size using only GPU, and finally get back the resized image from the target surface. In these scenario, pixel format of the surfaces can be different and the GPU automatically handles it. But here there are things that can affect the performance of this operation. Moving data between GPU and CPU is a time consuming process. You can apply some techniques to boost performance based on your situation, and avoiding extra data transfer between CPU and GPU memory.
Does anyone have the secret formula to resizing transparent images (mainly GIFs) without ANY quality loss - what so ever?
I've tried a bunch of stuff, the closest I get is not good enough.
Take a look at my main image:
http://www.thewallcompany.dk/test/main.gif
And then the scaled image:
http://www.thewallcompany.dk/test/ScaledImage.gif
//Internal resize for indexed colored images
void IndexedRezise(int xSize, int ySize)
{
BitmapData sourceData;
BitmapData targetData;
AdjustSizes(ref xSize, ref ySize);
scaledBitmap = new Bitmap(xSize, ySize, bitmap.PixelFormat);
scaledBitmap.Palette = bitmap.Palette;
sourceData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly, bitmap.PixelFormat);
try
{
targetData = scaledBitmap.LockBits(new Rectangle(0, 0, xSize, ySize),
ImageLockMode.WriteOnly, scaledBitmap.PixelFormat);
try
{
xFactor = (Double)bitmap.Width / (Double)scaledBitmap.Width;
yFactor = (Double)bitmap.Height / (Double)scaledBitmap.Height;
sourceStride = sourceData.Stride;
sourceScan0 = sourceData.Scan0;
int targetStride = targetData.Stride;
System.IntPtr targetScan0 = targetData.Scan0;
unsafe
{
byte* p = (byte*)(void*)targetScan0;
int nOffset = targetStride - scaledBitmap.Width;
int nWidth = scaledBitmap.Width;
for (int y = 0; y < scaledBitmap.Height; ++y)
{
for (int x = 0; x < nWidth; ++x)
{
p[0] = GetSourceByteAt(x, y);
++p;
}
p += nOffset;
}
}
}
finally
{
scaledBitmap.UnlockBits(targetData);
}
}
finally
{
bitmap.UnlockBits(sourceData);
}
}
I'm using the above code, to do the indexed resizing.
Does anyone have improvement ideas?
If there's no requirement on preserving file type after scaling I'd recommend the following approach.
using (Image src = Image.FromFile("main.gif"))
using (Bitmap dst = new Bitmap(100, 129))
using (Graphics g = Graphics.FromImage(dst))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(src, 0, 0, dst.Width, dst.Height);
dst.Save("scale.png", ImageFormat.Png);
}
The result will have really nice anti aliased edges
removed image shack image that had been replaced by an advert
If you must export the image in gif you're in for a ride; GDI+ doesn't play well with gif. See this blog post about it for more information
Edit: I forgot to dispose of the bitmaps in the example; it's been corrected
This is a basic resize function I've used for a few of my applications that leverages GDI+
/// <summary>
/// Resize image with GDI+ so that image is nice and clear with required size.
/// </summary>
/// <param name="SourceImage">Image to resize</param>
/// <param name="NewHeight">New height to resize to.</param>
/// <param name="NewWidth">New width to resize to.</param>
/// <returns>Image object resized to new dimensions.</returns>
/// <remarks></remarks>
public static Image ImageResize(Image SourceImage, Int32 NewHeight, Int32 NewWidth)
{
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(NewWidth, NewHeight, SourceImage.PixelFormat);
if (bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format1bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format4bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format8bppIndexed | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Undefined | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.DontCare | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format16bppArgb1555 | bitmap.PixelFormat == Drawing.Imaging.PixelFormat.Format16bppGrayScale)
{
throw new NotSupportedException("Pixel format of the image is not supported.");
}
System.Drawing.Graphics graphicsImage = System.Drawing.Graphics.FromImage(bitmap);
graphicsImage.SmoothingMode = Drawing.Drawing2D.SmoothingMode.HighQuality;
graphicsImage.InterpolationMode = Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphicsImage.DrawImage(SourceImage, 0, 0, bitmap.Width, bitmap.Height);
graphicsImage.Dispose();
return bitmap;
}
I don't remember off the top of my head if it will work with GIFs, but you can give it a try.
Note: I can't take full credit for this function. I pieced a few things together from some other samples online and made it work to my needs 8^D
I think the problem is that you're doing a scan line-based resize, which is going to lead to jaggies no matter how hard you tweak it. Good image resize quality requires you to do some more work to figure out the average color of the pre-resized pixels that your resized pixel covers.
The guy who runs this website has a blog post that discusses a few image resizing algorithms. You probably want a bicubic image scaling algorithm.
Better Image Resizing
For anyone that may be trying to use Markus Olsson's solution to dynamically resize images and write them out to the Response Stream.
This will not work:
Response.ContentType = "image/png";
dst.Save( Response.OutputStream, ImageFormat.Png );
But this will:
Response.ContentType = "image/png";
using (MemoryStream stream = new MemoryStream())
{
dst.Save( stream, ImageFormat.Png );
stream.WriteTo( Response.OutputStream );
}
While PNG is definitely better that GIF, occasionally there is a use case for needing to stay in GIF format.
With GIF or 8-bit PNG, you have to address the problem of quantization.
Quantization is where you choose which 256 (or fewer) colors will best preserve and represent the image, and then turn the RGB values back into indexes. When you perform a resize operation, the ideal color palette changes, as you are mixing colors and changing balances.
For slight resizes, like 10-30%, you may be OK preserving the original color palette.
However, in most instances you'll need to re-quantize.
The primary two algorithms to pick from are Octree and nQuant. Octree is very fast and does a very good job, especially if you can overlay a smart dithering algorithm. nQuant requires at least 80MB of RAM to perform an encode (it builds a complete histogram), and is typically 20-30X slower (1-5 seconds per encode on an average image). However, it sometimes produces higher image quality that Octree since it doesn't 'round' values to maintain consistent performance.
When implementing transparent GIF and animated GIF support in the imageresizing.net project, I chose Octree. Transparency support isn't hard once you have control of the image palette.