使用.NET旋转JPEG图像并最小化质量损失

9
我试图在ASP.NET MVC中支持旋转JPEG图片(以90度为单位)。我尝试使用System.Drawing(GDI +)来实现,但是遇到了问题。
我尝试使用Image.RotateFlip,它可以旋转图像,但会导致质量损失。即使编码器质量为100,旋转后的图像上仍然有可见的伪影,这些伪影在原始图像上不存在,使用其他程序(如Gimp等)旋转时也不会出现。
using (Image image = Image.FromFile("C:\\source.jpg")) {
    ImageFormat sourceFormat = image.RawFormat;
    image.RotateFlip(RotateFlipType.Rotate90FlipNone);
    EncoderParameters encoderParams = null;
    try {
        if (sourceFormat == ImageFormat.Jpeg) {
            encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
        }
        image.Save("C:\\target.jpg", GetEncoder(sourceFormat), encoderParams);
    } finally {
        if (encoderParams != null)
            encoderParams.Dispose();
    }
}

我找到了一篇有关于如何无损转换JPEG的文章(链接)。看起来在.NET中使用Encoder.Transformation是一个选项,但是无论图像的尺寸是否是16的倍数,我都无法使它旋转我的JPEG测试图片。

using (Image image = Image.FromFile("C:\\source.jpg")) {
    ImageFormat sourceFormat = image.RawFormat;
    EncoderParameters encoderParams = null;
    try {
        if (sourceFormat == ImageFormat.Jpeg) {
            encoderParams = new EncoderParameters(1);
            encoderParams.Param[0] = new EncoderParameter(Encoder.Transformation, 
                (long)EncoderValue.TransformRotate90);
        }
        image.Save("C:\\target.jpg", GetEncoder(sourceFormat), encoderParams);
    } finally {
        if (encoderParams != null)
            encoderParams.Dispose();
    }
}

有人知道如何在.NET中使用上述方法或其他方法成功地将JPEG以90度为单位旋转,而最小化或不损失质量吗?谢谢。

此外,这是我实现的GetEncoder

private ImageCodecInfo GetEncoder(ImageFormat format) {
    foreach (var info in ImageCodecInfo.GetImageEncoders())
        if (info.FormatID == format.Guid)
            return info;
    return null;
}

编辑:

我更新了上面的代码以更好地匹配我的实际代码。错误在以下行中:

if (sourceFormat == ImageFormat.Jpeg) {

它应该是:

if (sourceFormat.Guid == ImageFormat.Jpeg.Guid) {

2
你的代码对我来说是有效的。你确定你的数据返回了一个ImageCodecInfo吗? - Paul van Brenk
谢谢@pb,我得到了一个ImageCodecInfo但没有encoderParams,因为我的实际代码在设置它之前有一个错误的额外检查。 - Mike Henry
3个回答

4
感谢您确认我的发布的代码可以工作。这帮助我隔离了我的问题。现在我感觉很愚蠢。我的实际代码在设置encoderParams之前进行了图像格式检查,但存在一个错误:
if (sourceFormat == ImageFormat.Jpeg) {
    // set encoderParams here

我发现上述条件语句总是为假,因此encoderParams没有被设置。修复方法很简单:

if (sourceFormat.Guid == ImageFormat.Jpeg.Guid) {

2
任何一种解压有损压缩图像、旋转它并再次有损压缩(即使使用相同的参数),都会导致图像质量下降。在有损解压缩期间,您只能得到有损压缩图像的近似值(请记住,压缩是以PSNR为单位定义的,因此另一种实现可能会稍微不同地解压相同的图像)。重新压缩也是如此,除非您采用完全相同的有损压缩器和相同的参数,否则无法重新压缩图像导致相同的量化DCT值。
JPEG格式通过获取平均颜色来表示4个像素中的所有颜色信息,并将其压缩成2x2像素的正方形,因此,如果您的图像宽度和高度可被2整除,则失去的大部分信息是在解压缩时插值的信息,因此您会减少失真。
同样,亮度信息按8x8像素的正方形压缩,因此,如果您的宽度和高度可被8整除,则网格在旋转图像后将对齐,您会失去较少的实际信息。
要进行无损旋转,必须使用完全不同的方法,读取JPEG文件并重新排列和旋转每个信息正方形(量化DCT值),以便形成旋转后的图像,而无需解压缩和重新压缩它(熵编码是一种无损机制,可以安全地进行往返操作)。

OP明确要求无损转换和适用于JPEG的90度增量。因此,很清楚要做什么才能完全保留输入质量。 - malat
@malat:是的,我同意这很清楚。这就是我在这个答案中写的内容。你有什么不同意的吗? - Guffa
这是我写的方式,但我认为当使用4:2:0色度子采样时旋转何时能够实现仍不清楚,用户可能会对你提到的8x8块参考感到困惑。 - malat

1

这是我根据Mike Henry的答案进行修改后适应自己需求的版本,希望对需要的人有所帮助。

public MemoryStream RotateImage(Stream stream, RotateFlipType rotationFlipType)
{
    try
    {
        if (stream != null)
        {
            using (Image image = Image.FromStream(stream))
            {
                ImageFormat sourceFormat = image.RawFormat;
                image.RotateFlip(rotationFlipType);
                EncoderParameters encoderParams = null;
                try
                {
                    if (sourceFormat.Guid == ImageFormat.Jpeg.Guid)
                    {
                        encoderParams = new EncoderParameters(1);
                        encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
                    }
                    var ms = new MemoryStream();
                    image.Save(ms,
                        ImageCodecInfo.GetImageEncoders().FirstOrDefault(e => e.FormatID == sourceFormat.Guid),
                        encoderParams);
                    ms.Position = 0;
                    return ms;
                }
                finally
                {
                    if (encoderParams != null)
                        encoderParams.Dispose();
                }
            }

        }
    }
    finally
    {
        if (stream != null)
        {
            stream.Dispose();
        }
    }
    return null;
}

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