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

Some images are being rotated when resized

简而言之,以下代码的目的是根据目标大小和乘数(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");
            }
        }
    }
}

例如这张图片:

已适当缩放,但这张图片:

颠倒了。值得一提的是,图片在预览区上传到本站时,也出现了上下颠倒的现象。这个事实(我显然刚刚发现)强烈地让我觉得这张照片有些有趣。不管我的代码需要处理它。

Imgur "fixed" 上面的文件(因为当我通过我的代码 运行 它现在不旋转)所以我用我的代码将它上传到翻转 180 度的狗图像的 Google Drive. If you right click on the image (in FireFox I haven't tested other browsers) and click Save Image As... then the image doesn't rotate with my code above. If you click the download button in the header then the image does rotate with my code.... Here is another copy。这一切都非常奇怪,我不知道我做错了什么......

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


根据评论进行的编辑:

rotates/flips 将以相同方式始终如一地执行此操作的图像。例如,这张狗图片将始终翻转 180 度。有些图片会侧放(旋转 90 或 270 度)。我验证了newWidthnewHeightscaleFactortargetSize(私有变量)和image.Height/image.Width变量在狗图翻转180时都是正的度。

我认为这不是特定工具的产物。我看到旋转通过; Windows Explorer、Windows Image Viewer、stock Macintosh image viewer、FireFox 等中的预览。实际上,我公司的一位 iOS 开发人员发现了这个问题,引起了我的注意在我们的应用程序中。我认为有太多工具将其视为查看器问题。

感谢 Chris Farmer1 and Mark Ransom2 的大力帮助,我得以回答我自己的问题。

核心问题是某些图像上的 EXIF 数据改变了图像的方向。当我调整图像大小时,所有 EXIF 数据都被剥离了。由于 EXIF 数据被剥离,观看者不知道图像的方向应该不同。这使我认为调整大小的代码正在旋转一些图像。值得注意的是,当您在 Windows Explorer 中右键单击图像时,此方向信息不会显示在详细信息视图中。您需要在图像 属性 对象中搜索它或使用像 this one.

这样的在线视图

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

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:方向键值为 confirmed here