76

I made a small C# app to create an image in .jpg format.

pictureBox.Image.Save(name,ImageFormat.Jpeg);

The image is succesfully created. I input an original pic, do some stuff with it and save it. The quality of this new pic however, is lower than that of the original.

Is there any way to set the desired quality?

KdgDev
  • 14,299
  • 46
  • 120
  • 156

7 Answers7

94

The following code example demonstrates how to create a EncoderParameter using the EncoderParameter constructor. To run this example, paste the code and call the VaryQualityLevel method.

This example requires an image file named TestPhoto.jpg located at c:.

private void VaryQualityLevel()
{
    // Get a bitmap.
    Bitmap bmp1 = new Bitmap(@"c:\TestPhoto.jpg");
    ImageCodecInfo jgpEncoder = GetEncoder(ImageFormat.Jpeg);

    // Create an Encoder object based on the GUID
    // for the Quality parameter category.
    System.Drawing.Imaging.Encoder myEncoder =
        System.Drawing.Imaging.Encoder.Quality;

    // Create an EncoderParameters object.
    // An EncoderParameters object has an array of EncoderParameter
    // objects. In this case, there is only one
    // EncoderParameter object in the array.
    EncoderParameters myEncoderParameters = new EncoderParameters(1);

    EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 
        50L);
    myEncoderParameters.Param[0] = myEncoderParameter;
    bmp1.Save(@"c:\TestPhotoQualityFifty.jpg", jgpEncoder, 
        myEncoderParameters);

    myEncoderParameter = new EncoderParameter(myEncoder, 100L);
    myEncoderParameters.Param[0] = myEncoderParameter;
    bmp1.Save(@"c:\TestPhotoQualityHundred.jpg", jgpEncoder, 
        myEncoderParameters);

    // Save the bitmap as a JPG file with zero quality level compression.
    myEncoderParameter = new EncoderParameter(myEncoder, 0L);
    myEncoderParameters.Param[0] = myEncoderParameter;
    bmp1.Save(@"c:\TestPhotoQualityZero.jpg", jgpEncoder, 
        myEncoderParameters);

}

private ImageCodecInfo GetEncoder(ImageFormat format)
{
    ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
    foreach (ImageCodecInfo codec in codecs)
    {
        if (codec.FormatID == format.Guid)
        {
            return codec;
        }
    }
    return null;
}

Ref: http://msdn.microsoft.com/en-us/library/system.drawing.imaging.encoderparameter.aspx

Irvin Dominin
  • 30,819
  • 9
  • 77
  • 111
Dustin Getz
  • 21,282
  • 15
  • 82
  • 131
  • 4
    That works. It would seems that without all this, a standard quality of 50L is used. – KdgDev Sep 28 '09 at 00:39
  • 1
    EncoderParameter may use unmanaged resources and have to be disposed. Msdn documentation is a bit lacking on this subject. It should state that the `Param` array is initialized with null elements (so nothing to dispose before first assignment to each elements), and that `EncoderParameters` disposes its current parameters on its own dispose. – Frédéric Jul 11 '14 at 10:34
  • 1
    This looks fine, but is there a way to get the original image quality (either 50L, or 60 L..) ? Then we can set it back.. – Rajendra Tripathy Oct 06 '14 at 14:08
  • @RajendraTripathy `img.Save(srcPath, img.RawFormat);` – Jordan Dec 21 '15 at 21:14
  • how I can get this working with png and gif. I tested it with png and it increasing the size of image – carpics Jan 26 '16 at 19:07
  • 1
    @Jordan - that answers a different question than Rajendra asked. Rajendra asked if there is a way to find out what quality setting the original image had, so as to use that quality when outputting. E.g. if file was originally at quality 100, then save at quality 100. If file was at Q 50, save at 50. – ToolmakerSteve Aug 05 '17 at 11:07
  • 2
    This is an old Q, and has attracted some high quality answers over the years. I recommend [epox answer](https://stackoverflow.com/a/33763081/199364) or [bytecode77's answer](https://stackoverflow.com/a/39493346/199364). – ToolmakerSteve Aug 05 '17 at 12:11
  • @carpics: png and gif are lossless image formats. There's no need to specify a quality parameter there. – PMF Jul 04 '18 at 07:32
  • @carpics, there are better PNG encoders than what is built into .NET, e.g. PNGCRUSH. You'll need to use a separate library or program to reduce the size of PNGs. – snarf Feb 13 '22 at 23:37
  • `EncoderParameters` and `EncoderParameter` are disposable, so they should be wrapped in `using` blocks to avoid leaking memory. – vgru Sep 20 '22 at 16:24
59

Here's an even more compact chunk of code for saving as JPEG with a specific quality:

var encoder = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid);
var encParams = new EncoderParameters() { Param = new[] { new EncoderParameter(Encoder.Quality, 90L) } };
image.Save(path, encoder, encParams);

Or, if 120 character wide lines are too long for you:

var encoder = ImageCodecInfo.GetImageEncoders()
                            .First(c => c.FormatID == ImageFormat.Jpeg.Guid);
var encParams = new EncoderParameters(1);
encParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
image.Save(path, encoder, encParams);

Make sure the quality is a long or you will get an ArgumentException!

Roman Starkov
  • 59,298
  • 38
  • 251
  • 324
15

This is an old thread, but I have rewritten the Microsoft (as per Dustin Getz answer) to be a little more useful - shrinking GetEncoderInfo and making an extension on Image. Anyway nothing really new, but may be of use:

    /// <summary>
    /// Retrieves the Encoder Information for a given MimeType
    /// </summary>
    /// <param name="mimeType">String: Mimetype</param>
    /// <returns>ImageCodecInfo: Mime info or null if not found</returns>
    private static ImageCodecInfo GetEncoderInfo(String mimeType)
    {
        var encoders = ImageCodecInfo.GetImageEncoders();
        return encoders.FirstOrDefault( t => t.MimeType == mimeType );
    }

    /// <summary>
    /// Save an Image as a JPeg with a given compression
    ///  Note: Filename suffix will not affect mime type which will be Jpeg.
    /// </summary>
    /// <param name="image">Image: Image to save</param>
    /// <param name="fileName">String: File name to save the image as. Note: suffix will not affect mime type which will be Jpeg.</param>
    /// <param name="compression">Long: Value between 0 and 100.</param>
    private static void SaveJpegWithCompressionSetting(Image image, string fileName, long compression)
    {
        var eps = new EncoderParameters(1);
        eps.Param[0] = new EncoderParameter(Encoder.Quality, compression);
        var ici = GetEncoderInfo("image/jpeg");
        image.Save(fileName, ici, eps);
    }

    /// <summary>
    /// Save an Image as a JPeg with a given compression
    /// Note: Filename suffix will not affect mime type which will be Jpeg.
    /// </summary>
    /// <param name="image">Image: This image</param>
    /// <param name="fileName">String: File name to save the image as. Note: suffix will not affect mime type which will be Jpeg.</param>
    /// <param name="compression">Long: Value between 0 and 100.</param>
    public static void SaveJpegWithCompression(this Image image, string fileName, long compression)
    {
        SaveJpegWithCompressionSetting( image, fileName, compression );
    }
Matteo Migliore
  • 925
  • 8
  • 22
Wolf5370
  • 1,374
  • 11
  • 12
14

The community wiki answer, which is accepted, referrs to an example from Microsoft.

However, in order to save some of you time, I boiled it down to an essence and

  • Packed it into a proper method
  • Implemented IDisposable. I haven't seen using (...) { in any other answers. In order to avoid memory leakage, it is best practice to dispose everything that implements IDisposable.

public static void SaveJpeg(string path, Bitmap image)
{
    SaveJpeg(path, image, 95L);
}
public static void SaveJpeg(string path, Bitmap image, long quality)
{
    using (EncoderParameters encoderParameters = new EncoderParameters(1))
    using (EncoderParameter encoderParameter = new EncoderParameter(Encoder.Quality, quality))
    {
        ImageCodecInfo codecInfo = ImageCodecInfo.GetImageDecoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid);
        encoderParameters.Param[0] = encoderParameter;
        image.Save(path, codecInfo, encoderParameters);
    }
}
bytecode77
  • 14,163
  • 30
  • 110
  • 141
  • 1
    Minor note: "95L" as shown for quality is a good default value because it is close to the maximum of "100L", yet will save a little on file size, for very detailed images. I typically use "90L" to "100L" for hiqh quality preservation, "70L" to "85L" for decent quality, but more reasonable file size. It depends also on whether you are doing "repeated editing" of a file. If so, use "100L" until the last edit (or edit in .png to be lossless), then do the final save with whatever quality you need. – ToolmakerSteve Aug 05 '17 at 12:05
7

Using typeless GDI+ style ( https://msdn.microsoft.com/en-us/library/windows/desktop/ms533845(v=vs.85).aspx ) attributes for setting JPEG Quality looks overkilling.

A direct way should look like this:

FileStream stream = new FileStream("new.jpg", FileMode.Create);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 100;   // "100" for maximum quality (largest file size).
encoder.Frames.Add(BitmapFrame.Create(image));
encoder.Save(stream);

Ref: https://msdn.microsoft.com/en-us/library/system.windows.media.imaging.jpegbitmapencoder.rotation(v=vs.110).aspx#Anchor_2

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196
epox
  • 9,236
  • 1
  • 55
  • 38
  • Minor note: Since the original question was a complaint about low quality, to solve that use maximum quality: `encoder.QualityLevel = 100`. – ToolmakerSteve Aug 05 '17 at 11:14
4

Check out MSDN's article on how to set JPEG Compression level.

You need to use the other Save() overload that takes an ImageEncoder and its parameters.

JoshJordan
  • 12,676
  • 10
  • 53
  • 63
2

If you are using the .NET Compact Framework, an alternative might be to use the PNG lossless format ie:

image.Save(filename, ImageFormat.Png);
Soner Gönül
  • 97,193
  • 102
  • 206
  • 364
AlainD
  • 5,413
  • 6
  • 45
  • 99