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));
}
测试中的方法-该方法将收缩输入图像,使其高度和宽度小于maxHeight
和maxWidth
,保留现有的长宽比。有些图形调用可能是不确定的,我无法从微软有限的文档中看出。
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;
}
这最终成功了。我不知道这对单元测试来说是不是一个好主意,但是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;
}