C#中保存JPG图像的质量问题

76

我创建了一个小的C#应用程序以生成.jpg格式的图像。

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

图片已成功创建。我输入一个原始图片,对它进行了一些处理并保存了下来。然而,这张新图片的质量比原始图片低。

有没有办法设置所需的质量?

7个回答

94
以下代码示例演示了如何使用EncoderParameter构造函数创建EncoderParameter。要运行此示例,请粘贴代码并调用VaryQualityLevel方法。
此示例需要一个名为TestPhoto.jpg的图像文件,位于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;
}

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


4
可以。如果没有这些,似乎会使用标准的50升品质。 - KdgDev
1
EncoderParameter 可能使用非托管资源,需要进行处理。Msdn 文档在这个问题上有点不足。应该说明“Param”数组是用 null 元素初始化的(所以在每个元素的第一次分配之前没有什么可以处理的),以及“EncoderParameters”将在其自己处理时释放其当前参数。 - Frédéric
1
这看起来不错,但有没有办法获得原始图像质量(50L或60L)?然后我们可以将其设置回去。 - Rajendra Tripathy
1
@Jordan - 这回答了一个不同于Rajendra所问的问题。Rajendra问是否有一种方法可以找出原始图像的质量设置,以便在输出时使用该质量。例如,如果文件最初的质量为100,则保存为100的质量。如果文件是Q 50,则保存为50。 - ToolmakerSteve
2
这是一个老问题,多年来已经吸引了一些高质量的答案。我推荐 epox 的回答 或者 bytecode77 的回答 - ToolmakerSteve
显示剩余5条评论

59

这是一个更加简洁的代码块,可以用于以特定质量保存为JPEG格式:

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);

或者,如果每行120个字符对你来说太长了:

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);

请确保质量是long,否则会触发ArgumentException异常!


直接进入我的代码库,非常感谢! - JustJohn
2
还可以参考bytecode77的答案,其中使用using确保在结束时及时发生Dispose - ToolmakerSteve

15

这是一个旧的线程,但我已经重写了微软(根据Dustin Getz的答案)使其更加实用 - 缩小了GetEncoderInfo并在Image上进行了扩展。总之没有什么新东西,但可能会有用:

    /// <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 );
    }

14

社区维基的答案被接受,它引用了微软的一个例子。

然而,为了节省您的时间,我概括了其要点并

  • 将其打包成适当的方法
  • 实现IDisposable。我在其他任何答案中都没有看到using (...) {。为了避免内存泄漏,最佳做法是处理所有实现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);
    }
}

1
小注:对于品质,显示为"95L"是一个好的默认值,因为它接近于最大值"100L",但在非常详细的图像上可以节省一些文件大小。我通常使用"90L"到"100L"进行高质量保存,使用"70L"到"85L"进行相对合理的文件大小但品质不错。这也取决于您是否正在进行"重复编辑"的文件。如果是这样,请使用"100L"直到最后一次编辑(或以.png编辑以无损),然后使用所需的任何品质进行最终保存。 - ToolmakerSteve

7

使用无类型的GDI+样式(https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms533845(v=vs.85).aspx)属性来设置JPEG质量显得有些过度。

直接的方式应该是这样的:

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);

参考:https://msdn.microsoft.com/zh-cn/library/system.windows.media.imaging.jpegbitmapencoder.rotation(v=vs.110).aspx#Anchor_2

该文档介绍了JpegBitmapEncoder类的Rotation属性,该属性用于设置图像的旋转角度。旋转角度可以是0度、90度、180度或270度。


小提示:由于原始问题是关于低质量的投诉,为了解决这个问题,请使用最大质量:encoder.QualityLevel = 100 - ToolmakerSteve

4

2
如果您正在使用.NET Compact Framework,另一种选择可能是使用PNG无损格式,即:
image.Save(filename, ImageFormat.Png);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接