In .Net is System.Drawing.Image.Save deterministic?

本文关键字:Save deterministic Image Drawing Net is System In | 更新日期: 2023-09-27 18:01:45

我试图通过他们的字节内容比较两个图像。但是,它们不匹配。

两幅图像是由同一源图像生成的,使用相同的方法和相同的参数。我猜,在图像生成或我转换成字节数组的方式是不确定的。有谁知道不确定性行为发生在哪里,以及我是否可以轻易地为我的单元测试强制执行确定性行为?

这个方法在我的测试类转换图像到字节数组-是image.Save确定性?memStream.ToArray()是确定性的吗?

private static byte[] ImageToByteArray(Image image)
{
    byte[] actualBytes;
    using (MemoryStream memStream = new MemoryStream())
    {
        image.Save(memStream, ImageFormat.Bmp);
        actualBytes = memStream.ToArray();
    }
    return actualBytes;
}

这是失败的单元测试- TestImageLandscapeDesertResized_300_300是使用ImageHelper.ResizeImage(testImageLandscape, 300, 300)TestImageLandscapeDesert生成的,然后在加载到项目的资源文件之前保存到一个文件。如果代码中的所有调用都是基于输入参数的确定性调用,则该测试应该通过。

public void ResizeImage_Landscape_SmallerLandscape()
{
    Image testImageLandscape = Resources.TestImageLandscapeDesert;
    Image expectedImage = Resources.TestImageLandscapeDesertResized_300_300;
    byte[] expectedBytes = ImageToByteArray(expectedImage);
    byte[] actualBytes;
    using (Image resizedImage = ImageHelper.ResizeImage(testImageLandscape, 300, 300))
    {
        actualBytes = ImageToByteArray(resizedImage);
    }
    Assert.IsTrue(expectedBytes.SequenceEqual(actualBytes));
}

测试中的方法-该方法将收缩输入图像,使其高度和宽度小于maxHeightmaxWidth,保留现有的长宽比。有些图形调用可能是不确定的,我无法从微软有限的文档中看出。

public static Image ResizeImage(Image image, int maxWidth, int maxHeight)
{
    decimal width = image.Width;
    decimal height = image.Height;
    decimal newWidth;
    decimal newHeight;
    //Calculate new width and height
    if (width > maxWidth || height > maxHeight)
    {
        // need to preserve the original aspect ratio
        decimal originalAspectRatio = width / height;
        decimal widthReductionFactor = maxWidth / width;
        decimal heightReductionFactor = maxHeight / height;
        if (widthReductionFactor < heightReductionFactor)
        {
            newWidth = maxWidth;
            newHeight = newWidth / originalAspectRatio;
        }
        else
        {
            newHeight = maxHeight;
            newWidth = newHeight * originalAspectRatio;
        }
    }
    else
        //Return a copy of the image if smaller than allowed width and height
        return new Bitmap(image);
    //Resize image
    Bitmap bitmap = new Bitmap((int)newWidth, (int)newHeight, PixelFormat.Format48bppRgb);
    Graphics graphic = Graphics.FromImage(bitmap);
    graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
    graphic.DrawImage(image, 0, 0, (int)newWidth, (int)newHeight);
    graphic.Dispose();
    return bitmap;
}

In .Net is System.Drawing.Image.Save deterministic?

这最终成功了。我不知道这对单元测试来说是不是一个好主意,但是GDI+逻辑是不确定的(或者我的逻辑与它接口),这似乎是最好的方法。

我使用MS Fakes Shimming功能来加载依赖的调用,并验证期望值被传递给被调用的方法。然后,我调用本机方法来获得测试方法其余部分所需的功能。最后验证返回图像的几个属性。

尽管如此,我还是倾向于直接比较预期输出和实际输出…

[TestMethod]
[TestCategory("ImageHelper")]
[TestCategory("ResizeImage")]
public void ResizeImage_LandscapeTooLarge_SmallerLandscape()
{
    Image testImageLandscape = Resources.TestImageLandscapeDesert;
    const int HEIGHT = 300;
    const int WIDTH = 300;
    const int EXPECTED_WIDTH = WIDTH;
    const int EXPECTED_HEIGHT = (int)(EXPECTED_WIDTH / (1024m / 768m));
    const PixelFormat EXPECTED_FORMAT = PixelFormat.Format48bppRgb;
    bool calledBitMapConstructor = false;
    bool calledGraphicsFromImage = false;
    bool calledGraphicsDrawImage = false;
    using (ShimsContext.Create())
    {
        ShimBitmap.ConstructorInt32Int32PixelFormat = (instance, w, h, f) => {
            calledBitMapConstructor = true;
            Assert.AreEqual(EXPECTED_WIDTH, w);
            Assert.AreEqual(EXPECTED_HEIGHT, h);
            Assert.AreEqual(EXPECTED_FORMAT, f);
            ShimsContext.ExecuteWithoutShims(() => {
                ConstructorInfo constructor = typeof(Bitmap).GetConstructor(new[] { typeof(int), typeof(int), typeof(PixelFormat) });
                Assert.IsNotNull(constructor);
                constructor.Invoke(instance, new object[] { w, h, f });
            });
        };
        ShimGraphics.FromImageImage = i => {
            calledGraphicsFromImage = true;
            Assert.IsNotNull(i);
            return ShimsContext.ExecuteWithoutShims(() => Graphics.FromImage(i));
        };
        ShimGraphics.AllInstances.DrawImageImageInt32Int32Int32Int32 = (instance, i, x, y, w, h) => {
            calledGraphicsDrawImage = true;
            Assert.IsNotNull(i);
            Assert.AreEqual(0, x);
            Assert.AreEqual(0, y);
            Assert.AreEqual(EXPECTED_WIDTH, w);
            Assert.AreEqual(EXPECTED_HEIGHT, h);
            ShimsContext.ExecuteWithoutShims(() => instance.DrawImage(i, x, y, w, h));
        };
        using (Image resizedImage = ImageHelper.ResizeImage(testImageLandscape, HEIGHT, WIDTH))
        {
            Assert.IsNotNull(resizedImage);
            Assert.AreEqual(EXPECTED_WIDTH, resizedImage.Size.Width);
            Assert.AreEqual(EXPECTED_HEIGHT, resizedImage.Size.Height);
            Assert.AreEqual(EXPECTED_FORMAT, resizedImage.PixelFormat);
        }
    }
    Assert.IsTrue(calledBitMapConstructor);
    Assert.IsTrue(calledGraphicsFromImage);
    Assert.IsTrue(calledGraphicsDrawImage);
}

这个有点晚了,但是添加这个以防它对任何人都有帮助。在我的单元测试中,这可靠地比较了我使用GDI+动态生成的图像。

private static bool CompareImages(string source, string expected)
{
    var image1 = new Bitmap($".''{source}");
    var image2 = new Bitmap($".''Expected''{expected}");
    var converter = new ImageConverter();
    var image1Bytes = (byte[])converter.ConvertTo(image1, typeof(byte[]));
    var image2Bytes = (byte[])converter.ConvertTo(image2, typeof(byte[]));
    // ReSharper disable AssignNullToNotNullAttribute
    var same = image1Bytes.SequenceEqual(image2Bytes);
    // ReSharper enable AssignNullToNotNullAttribute
    return same;
}