0

I need a function in C# that returns merged bitmaps vertically. I want the resulting image to contain two bitmaps set one above the other. I don't have much experience with processing images in unsafe mode. I have a method that merges images vertically using GDI+, but there are distortions in the pixels.

Please help.

public static Bitmap merge_Two_Bitmaps32bppArgb_Vertically(Bitmap imageSrc1, Bitmap imageSrc2) {
  Bitmap returnMapOut = null;
  int maxW = (
    new List < int > () {
      imageSrc1.Width,
        imageSrc2.Width
    }
  ).Max();

  returnMapOut = new Bitmap(
    maxW,
    (imageSrc1.Height + imageSrc2.Height), //sum !!!
    PixelFormat.Format32bppArgb
  );

  //src 1:
  BitmapData bitmapDataSrc1 = imageSrc1.LockBits(
    new Rectangle(0, 0, imageSrc1.Width, imageSrc1.Height),
    ImageLockMode.ReadOnly,
    PixelFormat.Format32bppArgb
  );

  //output:
  BitmapData bitmapDataOut = returnMapOut.LockBits(
    new Rectangle(0, 0, returnMapOut.Width, returnMapOut.Height),
    ImageLockMode.ReadOnly,
    PixelFormat.Format32bppArgb
  );

  //TODO:

}
4
  • I´d not recommend going "the unmanaged way" if you don´t understand the underlying Bitmap data format properly. See en.wikipedia.org/wiki/Bitmap for some insights into the format specification. Commented Jul 13, 2023 at 9:41
  • 1
    What does merge vertically mean in the first place? Merge suggests you want to combine two images but vertically suggests you want to append one below the other. Which is easy - create a new bitmap with twice the height and draw the existing ones one after the other. Commented Jul 13, 2023 at 10:38
  • I want to append one bitmap below another bitmap with unmanaged code in C#. Commented Jul 13, 2023 at 10:40
  • @pl.lepko I added a modified version of the MergeBitmaps example method to my answer that demos how you can combine the images by copying raw bitmap buffers. As mentioned, I would not recommend doing this, but you asked for it (-: Commented Jul 13, 2023 at 11:58

1 Answer 1

0

Since you did not specify what platform you´re targeting, I assume (for simplicity) the platform is Windows (because GDI+ and the System.Drawing API is only fully available on that platform). Further, I assume you target one of the more recent versions of .NET, for instance, .NET 6.0 LTS.

I provide you with some sample code that avoids usage of the functionality that deals with the Stride pointer and raw image data. I use the System.Drawing.Common package in my example. If you´re working on an older version of the .NET Framework, adding a reference to System.Drawing should also work.

The following method accepts an array of Image objects. It takes the width and the pixel format of the first image and calculates the height of the resulting image by just summing up the heights of all images. It renders all images unscaled, so you need to tweak it to your needs; maybe you want to specify the width of the resulting image as a parameter and scale all images accordingly.

private static Image MergeBitmaps(params Image[] bitmaps)
{
    Image first = bitmaps.FirstOrDefault() ?? throw new InvalidOperationException();
    int width = first.Width;
    int resultImageHeight = bitmaps.Sum(bitmap => bitmap.Height);

    var offset = 0;
    var resultImage = new Bitmap(width, resultImageHeight, first.PixelFormat);
    using Graphics g = Graphics.FromImage(resultImage);
    foreach (Image bitmap in bitmaps)
    {
        g.DrawImageUnscaled(bitmap, 0, offset, bitmap.Width, bitmap.Height);
        offset += bitmap.Height;
    }

    return resultImage;
}

Also, I would use the Image type rather than Bitmap. You could use the provided example method as follows:

using Image bitmap1 = Image.FromStream(...);
using Image bitmap2 = Image.FromStream(...);
using Image mergedImage = MergeBitmaps(bitmap1, bitmap2);
mergedImage.Save(Path.Combine(path, "merged.bmp"));

As mentioned in my comment, you should only work the raw bitmap data if you developed an understanding of how the pixel information in the buffer is laid out. Your implementation may require calculations and pixel conversions to support images of different pixel formats. If performance is not an issue, having code, as provided in my example, that renders the target image using a Graphics object may be less complex and easier to work with.

Update

If you want to combine the images by copying the raw bitmap buffers into the resulting image, ensure all input images have the same width and pixel format, and the pixel format also matches the format of the resulting image (which is 32bppArgb in that case). Then you could try something like this (this needs proper testing and additional checks):

private static Image MergeBitmaps(params Bitmap[] bitmaps)
{
    int width = bitmaps.Max(bitmap => bitmap.Width);
    int resultImageHeight = bitmaps.Sum(bitmap => bitmap.Height);

    var offset = 0;
    var pixelFormat = PixelFormat.Format32bppArgb;
    var resultImage = new Bitmap(width, resultImageHeight, pixelFormat);

    foreach (Bitmap bitmap in bitmaps)
    {
        int bitmapHeight = bitmap.Height;
        var rectangle = new Rectangle(0, offset, width, bitmapHeight);

        CopyBitmap(resultImage, rectangle, bitmap);

        offset += bitmapHeight;
    }

    return resultImage;
}

private static void CopyBitmap(
    Bitmap destBitmap, 
    Rectangle destinationRectange, 
    Bitmap sourceBitmap)
{
    BitmapData resultBitmapData = destBitmap.LockBits(destinationRectange, 
        ImageLockMode.WriteOnly, 
        destBitmap.PixelFormat);
    try
    {
        var bytes = new byte[resultBitmapData.Stride * resultBitmapData.Height];
        var rect = new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height);
        BitmapData bitmapData = sourceBitmap.LockBits(rect, 
            ImageLockMode.ReadOnly, 
            sourceBitmap.PixelFormat);
        try
        {
            var array = new byte[bitmapData.Stride * bitmapData.Height];
            Marshal.Copy(bitmapData.Scan0, array, 0, array.Length);
            Array.Copy(array, bytes, array.Length);
        }
        finally
        {
            sourceBitmap.UnlockBits(bitmapData);
        }

        Marshal.Copy(bytes, 0, resultBitmapData.Scan0, bytes.Length);
    }
    finally
    {
        destBitmap.UnlockBits(resultBitmapData);
    }
}
Sign up to request clarification or add additional context in comments.

10 Comments

I have such an implementation but I am not satisfied with its results.
Maybe I can give you some more tips if you could describe what makes the results you received not as good as expected.
Blue tint has nothing to do with drawing images. Neither do grids. Perhaps you're using a format like GIF which has a limited palette? Perhaps the original images are GIFs and their palettes are different? After printing if you want to print, why not use a PrintDocument and draw to the document's Graphics class?
Don't use Image.FromFile() like that, you're discarding ICM information. Use instead Image.FromStream(new MemoryStream(File.ReadAllBytes([File Path])), true, false) -- This: mergedImage.Save(Path.Combine(path, "merged.bmp")); generates a PNG not a BMP -- As noted, won't work with paletted images (if that's the case here)
In this case, as input for the LockBits on the source images, it's better to force PixelFormat.Format32bppArgb instead of using sourceBitmap.PixelFormat. The LockBits function is perfectly capable of converting the input data to the desired format if it happens to be different, but if you don't do that and they don't match, and you just blindly try to copy the data as if it were 32bpp, you're going to have corrupted output and/or crashes.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.