新位图中组合图像的位置和大小

Position and size of combined images inside a new Bitmap

我正在尝试合并两个半透明的 PNG 图像并在 pictureBox1 中显示结果,其中 SizeMode 属性 设置为 Zoom:

  pictureBox1.Image = Image.FromFile(imgPath + "/myImg1.png");

如果我直接显示单个图像,它位于中心并且现有边框受到尊重(如下面示例图像的右侧所示),但如果我组合这两张相同大小的图像:

private void button2_Click(object sender, EventArgs e)
{
    source1 = (Bitmap)Image.FromFile(imgPath + "/myImg1.png");
    source2 = (Bitmap)Image.FromFile(imgPath + "/myImg2.png");

    var target = new Bitmap(source1.Width, source1.Height, PixelFormat.Format32bppArgb);
    var graphics = Graphics.FromImage(target);
    graphics.CompositingMode = CompositingMode.SourceOver; 

    graphics.DrawImage(source1, 0, 0);
    graphics.DrawImage(source2, 0, 0);

    pictureBox1.Image = target;
}

实际结果显示裁剪后的两张图片没有居中(如左图):

我想弄清楚如何控制两个组合图像的位置和大小,所以它们被绘制成右图所示。

问题:
OP 试图将两个图像居中,在这种情况下大小相同,在一个新的 Bitmap 容器中。目标位图的大小设置为源图像之一的大小。新位图应显示在 PictureBox 中。控件的 SizeMode 属性 设置为 SizeMode.Zoom.

意外结果如左图所示,预期结果如右图所示:

会发生什么:
目标位图的大小与源图像之一的大小相同。
然后在新容器中绘制两个源图像 Point(0, 0)
预计 - 因为两个半透明图像具有相同的大小 - 两者都将绘制在原始位置。源图像似乎被放大并移向新容器的右下角。

似乎 并非如此,这正是发生的事情。

两个源图像的 DPI 描述符设置为 ~72 DPI。
标准 PC 屏幕的分辨率至少为 96 DPI。
当一个新的位图被创建为:

var newImage = new Bitmap(source1.Width, source1.Height, PixelFormat.Format32bppArgb);

新位图的分辨率设置为屏幕分辨率,因此至少为 96 DPI。

使用此方法加载的图像:

var image = Image.FromFile([Image Path]);

使用原始 DPI 描述符和像素格式创建。

在 96 DPI 容器中绘制 72 DPI 图像时,会考虑不同的分辨率:放大较低分辨率的图像。
结果,在相同大小的 96 DPI 容器内绘制的两个 72 DPI 图像不再居中,但看起来它们 移动 向下和向右(因此也剪掉了)。

另请参阅:

如何解决:
当处理分辨率、像素格式和大小很可能不同的图像时,最好创建源图像的副本并使用具有相同定义的新容器。 因此,我们可以将源图像复制到标准的 32 位 ARGB 位图(默认格式,硬件对齐),将分辨率设置为当前屏幕分辨率或为特定用途指定不同的分辨率(例如用于打印)。
或者用户可以指定的值。

如前所述,当一个新的 Bitmap 容器被创建为:

var bitmap = new Bitmap([width], [height]);

.Net 实现生成位图 PixelFormat.Format32bppArgb,分辨率设置为应用程序 认为 的屏幕 DPI。 Control.DeviceDpi.
返回的值 非 DpiAware 应用程序通常会认为它是 96 DPI。

我们仍然可以使用 Bitmap.SetResolution() 方法指定图像分辨率(浮点值,DPI 始终以浮点值表示)。

此位图构造函数:

var bitmap = new Bitmap([Stream], true, false))

生成一个 Format32bppArgb 位图,加载 ICM 设置(颜色定义) - 如果有的话 - 并跳过原始位图的验证(通常在从磁盘读取大量图像文件时使用,它相当更快。跳过 ICM 映射也更快,但会产生错误的颜色)。

我们还应该假设在新容器内居中的两个图像可能具有不同的确切尺寸(或根本不同的尺寸)。
因此我们需要一个新的位图,它可以包含两个图像,评估两个图像的最大尺寸。

综上所述,可以按照CenterImages()方法中的描述更改原始代码。

PictureBox Image 可以设置为:

string image1Path = Path.Combine(imgPath, "myImg1.png");
string image2Path = Path.Combine(imgPath, "myImg2.png");

pictureBox1.Image?.Dispose();
pictureBox1.Image = CenterImages(image1Path, image2Path);

使用默认的 96 DPI 分辨率生成新的组合位图。或如:

pictureBox1.Image?.Dispose();
pictureBox1.Image = CenterImages(image1Path, image2Path, 300.0f);

生成分辨率为 300 DPI 的新位图。


private Bitmap CenterImages(string sourcePath1, string sourcePath2, float dpi = 96.0f)
{
    using (var image1 = new Bitmap(Image.FromStream(
        new MemoryStream(File.ReadAllBytes(sourcePath1)), true, false)))
    using (var image2 = new Bitmap(Image.FromStream(
        new MemoryStream(File.ReadAllBytes(sourcePath2)), true, false))) {

        image1.SetResolution(dpi, dpi);
        image2.SetResolution(dpi, dpi);

        var rect = new Rectangle(0, 0, 
            Math.Max(image1.Width, image2.Width), Math.Max(image1.Height, image2.Height));

        var combinedImage = new Bitmap(rect.Width, rect.Height);
        combinedImage.SetResolution(dpi, dpi);

        using (var graphics = Graphics.FromImage(combinedImage)) {
            graphics.DrawImage(image1, (rect.Width - image1.Width) / 2, (rect.Height - image1.Height) / 2);
            graphics.DrawImage(image2, (rect.Width - image2.Width) / 2, (rect.Height - image2.Height) / 2);
        }
        return combinedImage;
    }
}