10

I'm scaling images down in c#, and I've compared my methods with the best method in Photoshop cs5 and cannot replicate it.

In PS i'm using bicubic sharper, which looks really good. However, when trying to do the same in c# I don't get as high quality results. I've tried bicubic interpolation as well as HQ bicubic, smoothing mode HQ/None/AA. Composition modes, I've tried about 50 different variations and each one comes out pretty close to the image on the right.

You'll notice the pixelation on her back and around the title, as well as the authors name not coming out too well.

(Left is PS, right is c#.)

enter image description here

It seems that c# bicubic does too much smoothing even with smoothing set to none. I've been playing around with many variations of the following code:

 g.CompositingQuality = CompositingQuality.HighQuality;
 g.InterpolationMode = InterpolationMode.HighQualityBicubic;
 g.PixelOffsetMode = PixelOffsetMode.None;
 g.SmoothingMode = SmoothingMode.None;

Edit: As requested here is the starting image (1mb). enter image description here

The Muffin Man
  • 19,585
  • 30
  • 119
  • 191

3 Answers3

12

Perhaps I am missing something, but I have typically used the following code below to resize/compress JPEG Images. Personally, I think the result turned out pretty well based on your source image. The code doesn't handle a few edge cases concerning input parameters, but overall gets the job done (I have additional extension methods for Cropping, and Combining image transformations if interested).

Image Scaled to 25% original size and using 90% Compression. (~30KB output file)

SampleImage

Image Scaling Extension Methods:

public static Image Resize(this Image image, Single scale)
{
  if (image == null)
    return null;

  scale = Math.Max(0.0F, scale);

  Int32 scaledWidth = Convert.ToInt32(image.Width * scale);
  Int32 scaledHeight = Convert.ToInt32(image.Height * scale);

  return image.Resize(new Size(scaledWidth, scaledHeight));
}

public static Image Resize(this Image image, Size size)
{
  if (image == null || size.IsEmpty)
    return null;

  var resizedImage = new Bitmap(size.Width, size.Height, image.PixelFormat);
  resizedImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

  using (var g = Graphics.FromImage(resizedImage))
  {
    var location = new Point(0, 0);
    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
    g.DrawImage(image, new Rectangle(location, size), new Rectangle(location, image.Size), GraphicsUnit.Pixel);
  }

  return resizedImage;
}

Compression Extension Method:

public static Image Compress(this Image image, Int32 quality)
{
  if (image == null)
    return null;

  quality = Math.Max(0, Math.Min(100, quality));

  using (var encoderParameters = new EncoderParameters(1))
  {
    var imageCodecInfo = ImageCodecInfo.GetImageEncoders().First(encoder => String.Compare(encoder.MimeType, "image/jpeg", StringComparison.OrdinalIgnoreCase) == 0);
    var memoryStream = new MemoryStream();

    encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, Convert.ToInt64(quality));

    image.Save(memoryStream, imageCodecInfo, encoderParameters);

    return Image.FromStream(memoryStream);
  }
}

Usage:

  using(var source = Image.FromFile(@"C:\~\Source.jpg"))
  using(var resized = source.Resize(0.25F))
  using(var compressed = resized.Compress(90))
    compressed.Save(@"C:\~\Output.jpg");

NOTE: For anyone who may comment, you cannot dispose the MemoryStream created in the Compress method until after the image is disposed. If you reflect in to the implementation of Dispose on MemoryStream, it is actually save to not explicitly call dispose. The only alternative would be to wrap the image/memory stream in a custom implementation of a class that implements Image/IDisposable.

Chris Baxter
  • 16,083
  • 9
  • 51
  • 72
  • @Calgary, I'll go with this answer as it addresses setting the quality level, which results in exactly what I was looking for. – The Muffin Man May 29 '11 at 23:47
  • I've not tried it but am always looking for a better way to process images. But my question, how good is this for lower resolution images. My method does a terrible job with them. stackoverflow.com/q/2025240/90764 – Tim Meers May 30 '11 at 00:27
  • I'll be giving this method a try later tonight for sure though. – Tim Meers May 30 '11 at 00:28
  • @Tim - Your link appears to be broken. As for your question, you are always going to be constrained by your source image (obviously), the above approach shouldn't result in any further degradation unless you drop the quality of the image when calling compress. – Chris Baxter May 30 '11 at 00:31
  • Yea thats because I failed at making it a correct link after 2 edits I gave up and just made it a copy paste required link. I'm not reducing quality at all I thought, just the size. I'll see about editing the link. edit, oops took to long [i'll try this link again](http://stackoverflow.com/q/2025240/90764) (Got it!) – Tim Meers May 30 '11 at 00:37
  • 1
    @Tim - Code above works fine with your sample image... if you are having specific issues, best bet it to open a new question. – Chris Baxter May 30 '11 at 00:49
  • @CalgaryCoder, do you have this extension method posted in it's entirety anywhere? – Tim Meers Oct 04 '11 at 17:48
  • 1
    @Tim, no; the above code is the entire compress/resize functionality. The only additional item that I have available is basic crop support (and some hybrid extensions to combine steps). Are you looking for something in particular? – Chris Baxter Oct 04 '11 at 19:14
  • @Calgary Coder, I have followed ur code as it is but am getting a blackish wired image. could u say what went wrong.size i gave is 100,50 – Anish Karunakaran Jun 14 '13 at 12:03
  • @Anish I am not sure what you mean, I would suggest asking a new question with additional details. – Chris Baxter Jun 14 '13 at 13:02
  • @ Calgary Coder: I am working with a web api and am getting an image as base64 string in the request i have to resize the image to 100by50. I have tried many code and found urs as an elegant solution but am getting the image as a black screen i havn't changed any code that of yours. only the difference is that am getting a png image. – Anish Karunakaran Jun 17 '13 at 04:27
  • @ Calgary Coder: I have asked a Separate question as u suggested. http://stackoverflow.com/questions/17140710/re-size-image-not-working-c-sharp – Anish Karunakaran Jun 17 '13 at 11:39
3

Looking at the amount of JPEG artifacts, especially at the top of the image, I think you set the jpg compression to high. That results in a smaller (filesize) file, but reduces image quality and seems to add more blur.

Can you try saving it in a higher quality? I assume the line containing CompositingQuality.HighQuality does this already, but maybe you can find an even higher quality mode. What are the differences in file size between Photoshop and C#? And how does the Photoshop image look after you saved it and reopened it? Just resizing in Photoshop doesn't introduce any jpg data loss. You will only notice that after you've saved the image as jpg and then closed and reopened it.

GolezTrol
  • 114,394
  • 18
  • 182
  • 210
  • PS is 34kb vs 21kb. The `Graphics` class has an exhaustive list of properties and methods, but I believe the 4 above are the main ones. I don't know any of getting more high quality than the HighQuality setting. – The Muffin Man May 29 '11 at 23:20
  • I don't know either, but the resulting image you pasted here doesn't seem to have a very high quality. In Photoshop, you got High, but also Very High. Each of this 'named' levels represent a range of actual values, where Very High means qualities of 80 to 100 and High meaning 60. I don't know how these figures translate to c#, but as you can tell, the Photoshop image is almost twice the size, so it presumably is stored with a greater quality. – GolezTrol May 29 '11 at 23:41
  • @Nick - the quality is a numeric value between 0 and 100. The higher the value the less the compression and the higher the quality of the image. – ChrisF May 29 '11 at 23:42
  • I stumbled on this Q&A. Maybe it's of use to you too: http://stackoverflow.com/questions/249587/high-quality-image-scaling-c – GolezTrol May 29 '11 at 23:42
  • The Drawing2D class has a QualityMode proeprty too, but since it is a generic drawing class, I don't know if these properties will actually affect Jpeg compression when storing the file. It seems to me these are unrelated and the properties mentioned mainly affect on-screen rendering quality. – GolezTrol May 29 '11 at 23:47
1

I stumbled upon this question.

I used this code to use no compression of the jpeg and it comes out like the PS version:

ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders(); 
    ImageCodecInfo ici = null; 
    foreach (ImageCodecInfo codec in codecs)
    { 
        if (codec.MimeType == "image/jpeg") 
            ici = codec; 
    } 

    EncoderParameters ep = new EncoderParameters(); 
    ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)100);
Community
  • 1
  • 1
The Muffin Man
  • 19,585
  • 30
  • 119
  • 191
  • Yup, pretty much same approach as in the Compress extension method shown above. You can actually reduce image quality to 90/95 with out any notable loss in quality to reduce image size. – Chris Baxter May 29 '11 at 23:48
  • 1
    You can use 100L instead of (long)100 or even just 100 as the parameter. C# converts ints to long if needed and 100L is a long literal. – Kim Johansson May 30 '11 at 02:41
  • @Kim, Yes I knew that, do you know why it was casted to long in the first place? If it expects a number from 0-100 then int16(short) would work. – The Muffin Man May 30 '11 at 03:09