26

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?

Juha Syrjälä
  • 33,425
  • 31
  • 131
  • 183
MartinHN
  • 19,542
  • 19
  • 89
  • 131
  • I suggest using a library that fixes GDI's terrible GIF support. Like mine, http://imageresizing.net It's been around for 4 years and is well maintained and supported. – Lilith River Jul 16 '11 at 18:37

5 Answers5

51

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

ChrisF
  • 134,786
  • 31
  • 255
  • 325
Markus Olsson
  • 22,402
  • 9
  • 55
  • 62
  • it only works with square like images and doesn't resize proportionally – eKek0 Apr 01 '09 at 03:00
  • 1
    Here's a list of TODO items when resizing images: http://nathanaeljones.com/163/20-image-resizing-pitfalls/ Also, I've implemented GIF quantization with transparency support if anyone needs it (Google "ASP.NET image resizing module") – Lilith River Jan 04 '10 at 19:16
  • Wow, Markus - that's very nice. I put a sample online: [http://www.thewallcompany.dk/test/](http://www.thewallcompany.dk/test/) As far as I can see - there's absolutely no loss compared to the manually resized gif image. Thanks a lot. – MartinHN Aug 27 '08 at 16:48
  • For easy to read, you can group using statement in one curly brace like using()\n using()\n { // your statement. } –  Nov 07 '12 at 07:50
  • Your image link seems to have rotted. If you still have the original image, please reupload it to stack.imgur. – Ilmari Karonen Jul 27 '15 at 08:17
5

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

Dillie-O
  • 29,277
  • 14
  • 101
  • 140
3

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

Jonathan
  • 61
  • 2
1

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 );
}
Bela
  • 3,437
  • 1
  • 20
  • 12
0

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.

Lilith River
  • 16,204
  • 2
  • 44
  • 76