I am stitching several images together vertically and horizontally to create one bigger image (in which the total width and height are the sum of the widths and heights of the individual images) using Bitmap
and System.Drawing.Graphics
in C#. The size of the individual images are 256 px by 256 px.
When I use DrawImage
from System.Drawing.Graphics
, why do I get a scaled and/or zoomed in version of the original image?
Here is the original image:
When I retrieve the image programmatically and write to file in code, I get the following:
var result = httpClient.GetStreamAsync(/* url */);
var bitmap = new Bitmap(await result);
...
using (var memory = new MemoryStream())
{
using (var fs = new FileStream(/* path */, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
bitmap.Save(memory, ImageFormat.Png); // I save to .png if that makes a difference
var bytes = memory.ToArray();
await fs.WriteAsync(bytes, 0, bytes.Length);
}
}
I see no difference. So far, so good.
However, when I attempt to stitch images horizontally, I get the following:
Note: For reference, the image above is at the far-right in the stitched image below.
It looks scaled, zoomed in, and definitely not like the original or programmatically retrieved individual image above.
Here is the code I use to stitch the images together:
Note: If byWidth
is true
, I stitch images horizontally.
private Bitmap MergeImages(IEnumerable<Bitmap> images, bool byWidth)
{
var enumerable = images as IList<Bitmap> ?? images.ToList();
var width = 0;
var height = 0;
foreach (var image in enumerable)
{
if (byWidth)
{
width += image.Width;
height = image.Height;
}
else
{
width = image.Width;
height += image.Height;
}
}
var bitmap = new Bitmap(width, height);
using (var g = Graphics.FromImage(bitmap))
{
var localWidth = 0;
var localHeight = 0;
foreach (var image in enumerable)
{
if (byWidth)
{
g.DrawImage(image, localWidth, 0);
localWidth += image.Width;
}
else
{
g.DrawImage(image, 0, localHeight);
localHeight += image.Height;
}
}
}
return bitmap;
}
Yes,
DrawImage
scales the image output based on the DPI setting of the image being drawn. A bitmap image saved at 72 DPI will be scaled to match the 96 DPI of the screen you're drawing it to (or whatever DPI your screen is actually set to). The idea here is that the source resolution (DPI) should be made to match the destination resolution (DPI), in order to maintain resolution-independence.Confusingly, even the
DrawImageUnscaled
method uses this DPI-based scaling logic. Practically, this is because theDrawImageUnscaled
method is just a simple wrapper around one of the overloads ofDrawImage
. Conceptually, this seems like a bug to me.Either way, the solution is simple: explicitly pass the desired size of the image to the
DrawImage
call. Essentially, you are forcing it to scale to the desired size:This is quasi-documented in KB317174.