调整大小时,某些图像正在旋转

时间:2015-10-23 20:05:44

标签: c# image image-processing image-rotation

简而言之,以下代码的目的是根据目标大小和乘数(1x,2x,3x)调整图像大小。这工作正常,除了某些原因我还没有确定一些图像正在旋转。

public void ResizeImage(TargetSize targetSize, ResizeMultiplier multiplier, Stream input, Stream output)
{
    using (var image = Image.FromStream(input))
    {
        // Calculate the resize factor
        var scaleFactor = targetSize.CalculateScaleFactor(image.Width, image.Height);
        scaleFactor /= (int)multiplier; // Enum is effectively named constant with a value of 1, 2, or 3

        var newWidth = (int)Math.Floor(image.Width / scaleFactor);
        var newHeight = (int)Math.Floor(image.Height / scaleFactor);
        using (var newBitmap = new Bitmap(newWidth, newHeight))
        {
            using (var imageScaler = Graphics.FromImage(newBitmap))
            {
                imageScaler.CompositingQuality = CompositingQuality.HighQuality;
                imageScaler.SmoothingMode = SmoothingMode.HighQuality;
                imageScaler.InterpolationMode = InterpolationMode.HighQualityBicubic;

                var imageRectangle = new Rectangle(0, 0, newWidth, newHeight);
                imageScaler.DrawImage(image, imageRectangle);

                newBitmap.Save(output, image.RawFormat);
            }
        }
    }
}

// Class definition for the class used in the method above
public class TargetSize
{
    /// <summary>
    /// The _width
    /// </summary>
    private readonly int _width;

    /// <summary>
    /// The _height
    /// </summary>
    private readonly int _height;

    /// <summary>
    /// Initializes a new instance of the <see cref="TargetSize"/> class.
    /// </summary>
    /// <param name="width">The width.</param>
    /// <param name="height">The height.</param>
    public TargetSize(int width, int height)
    {
        _height = height;
        _width = width;
    }

    /// <summary>
    /// Calculates the scale factor.
    /// </summary>
    /// <param name="width">The width.</param>
    /// <param name="height">The height.</param>
    /// <returns></returns>
    public decimal CalculateScaleFactor(int width, int height)
    {
        // Scale proportinately
        var heightScaleFactor = decimal.Divide(height, _height);
        var widthScaleFactor = decimal.Divide(width, _width);

        // Use the smaller of the two as the final scale factor so the image is never undersized.
        return widthScaleFactor > heightScaleFactor ? heightScaleFactor : widthScaleFactor;
    }
}

// NUnit integration test case I'm using to exercise the above code
[Test]
public void ResizeImage_Persistant_Single()
{
    // Read the image from disk
    using (var fileStream = File.OpenRead(@"TestData\dog.jpg"))
    {
        using (var outputStream = new MemoryStream())
        {
            // Call the resize image method detailed above. ResizeMultiplier.Medium casts to 2.
            _sut.ResizeImage(new TargetSize(200, 200), ResizeMultiplier.Medium, fileStream, outputStream);
            using (var newImage = Image.FromStream(outputStream))
            {
                // Save the resized image to disk
                newImage.Save(@"TestData\ImageResizerTests.ResizeImage_Persistant_Single.jpg");
            }
        }
    }
}

例如这张图片:

Good image

是适当缩放的,但是这张图片:

bad image

翻转过来。值得一提的是,当图像在预览窗格中上传到此站点时,图像似乎也是颠倒的。那个事实(我很明显刚刚发现)强烈地让我觉得这个形象很有趣。无论我的代码需要处理它。

Imgur “已修复”上面的文件(因为我现在通过我的代码运行时它不会旋转)所以我将其上传到Google Drive 。如果右键单击图像(在FireFox中我没有测试过其他浏览器)并单击将图像另存为... ,则图像不会随上面的代码一起旋转。如果你单击标题中的下载按钮,那么图像会随我的代码旋转....这是狗图像的another copy,它与我的代码翻转180度。所有这一切都很奇怪,我不知道我做错了什么......

要明确我的目标是在不旋转图像的情况下调整图像大小。

根据评论进行编辑:

旋转/翻转的图像将以相同的方式一致地执行。例如,这张狗图片将始终翻转180度。有些照片会放在一边(旋转90度或270度)。我已经确认,当狗图片翻转180时,newWidthnewHeightscaleFactortargetSize(私有变量)和image.Height/image.Width变量都是正数度。

我不相信这是特定工具的神器。我看到旋转通过; Windows资源管理器,Windows图像查看器,Macintosh图像查看器,FireFox等中的预览实际上我的公司的iOS开发人员在我们的应用程序中看到了这个问题。我认为太多的工具都会看到它成为观众的问题。

1 个答案:

答案 0 :(得分:18)

感谢Chris Farmer 1 Mark Ransom 2 的出色帮助,我能够回答我自己的问题。

核心问题是某些图像上的EXIF数据正在改变图像的方向。当我调整图像大小时,所有EXIF数据都被删除了。由于EXIF数据被剥离,观众并不知道图像应该以不同方式定向。这让我觉得缩放器代码正在旋转一些图像。值得注意的是,当您在Windows资源管理器中右键单击图像时,此方向信息不会显示在详细信息视图中。您需要在图像属性对象中搜索它,或使用this one等在线视图。

使用data from here 3 我能够创建以下命名常量:

private const int OrientationKey = 0x0112;
private const int NotSpecified = 0;
private const int NormalOrientation = 1;
private const int MirrorHorizontal = 2;
private const int UpsideDown = 3;
private const int MirrorVertical = 4;
private const int MirrorHorizontalAndRotateRight = 5;
private const int RotateLeft = 6;
private const int MirorHorizontalAndRotateLeft = 7;
private const int RotateRight = 8;

使用那些命名常量我将ResizeImage方法扩展为:

public void ResizeImage(TargetSize targetSize, ResizeMultiplier multiplier, Stream input, Stream output)
{
    using (var image = Image.FromStream(input))
    {
        // Calculate the resize factor
        var scaleFactor = targetSize.CalculateScaleFactor(image.Width, image.Height);
        scaleFactor /= (int)multiplier; 

        var newWidth = (int)Math.Floor(image.Width / scaleFactor);
        var newHeight = (int)Math.Floor(image.Height / scaleFactor);
        using (var newBitmap = new Bitmap(newWidth, newHeight))
        {
            using (var imageScaler = Graphics.FromImage(newBitmap))
            {
                imageScaler.CompositingQuality = CompositingQuality.HighQuality;
                imageScaler.SmoothingMode = SmoothingMode.HighQuality;
                imageScaler.InterpolationMode = InterpolationMode.HighQualityBicubic;

                var imageRectangle = new Rectangle(0, 0, newWidth, newHeight);
                imageScaler.DrawImage(image, imageRectangle);

                // Fix orientation if needed.
                if (image.PropertyIdList.Contains(OrientationKey))
                {
                    var orientation = (int)image.GetPropertyItem(OrientationKey).Value[0];
                    switch (orientation)
                    {
                        case NotSpecified: // Assume it is good.
                        case NormalOrientation:
                            // No rotation required.
                            break;
                        case MirrorHorizontal:
                            newBitmap.RotateFlip(RotateFlipType.RotateNoneFlipX);
                            break;
                        case UpsideDown:
                            newBitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
                            break;
                        case MirrorVertical:
                            newBitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
                            break;
                        case MirrorHorizontalAndRotateRight:
                            newBitmap.RotateFlip(RotateFlipType.Rotate90FlipX);
                            break;
                        case RotateLeft:
                            newBitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
                            break;
                        case MirorHorizontalAndRotateLeft:
                            newBitmap.RotateFlip(RotateFlipType.Rotate270FlipX);
                            break;
                        case RotateRight:
                            newBitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
                            break;
                        default:
                            throw new NotImplementedException("An orientation of " + orientation + " isn't implemented.");
                    }
                }
                newBitmap.Save(output, image.RawFormat);
            }
        }
    }
}

敏锐的观察者会注意到,大多数其他代码都被from this answer提取到相关问题。

1 谁是对的钱,但我不明白他在开什么车。

2 谁向我展示了Chris Farmer试图说的话。

3 Orientation键值为confirmed here