5

I need to perform a Graphics.DrawImage from a source to a target image, ideally from a smaller tile to a larger poster, but it doesn't matter in the specific scenario.

I write into an empty target image, and I set the g.CompositingMode = CompositingMode.SourceCopy; however, after the writing process, I get wrong values if the source image has an alpha channel.

I already checked for similar question and answers, but none could solve this situation.

There is not a single alpha value for all the source image; the expected result is an exact copy of the source image at the specified location of the target one.

The sample I assembled in order to test this is the following:

// Create a new bitmap.
const int bitmapSize = 5;
Bitmap 
    bmp = new Bitmap(bitmapSize, bitmapSize, PixelFormat.Format32bppArgb), 
    bmpDestDrawImage = new Bitmap(bitmapSize, bitmapSize, PixelFormat.Format32bppArgb);

#region create source image

// Lock the bitmap's bits.  
Rectangle rect = new Rectangle(0, 0, bitmapSize, bitmapSize);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bmp.PixelFormat);

// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;

// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];

Random rnd= new Random(0);

for (int counter = 0; counter < rgbValues.Length; /**/)
{
    rgbValues[counter++] = (byte)rnd.Next(0,255);
    rgbValues[counter++] = (byte)rnd.Next(0, 255);
    rgbValues[counter++] = (byte)rnd.Next(0, 255);
    rgbValues[counter++] = (byte)rnd.Next(0, 255);
}

// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);

// Unlock the bits.
bmp.UnlockBits(bmpData);

#endregion

using (Graphics g = Graphics.FromImage(bmpDestDrawImage))
{
    // From the Graphics.CompositingMode Property documentation page
    g.CompositingMode = CompositingMode.SourceCopy;
    g.TextRenderingHint = TextRenderingHint.SingleBitPerPixel;
    
    g.DrawImage(bmp, new Rectangle(0, 0, bitmapSize, bitmapSize), 0, 0, bitmapSize, bitmapSize, GraphicsUnit.Pixel);
}

for (var i = 0; i < bitmapSize; i++)
{
    for (var j = 0; j < bitmapSize; j++)
    {
        Color orig = bmp.GetPixel(i, j);
        Color cDrawImage = bmpDestDrawImage.GetPixel(i, j);

        Color examined = cDrawImage;

        if (orig.R != examined.R || orig.G != examined.G || orig.B != examined.B || orig.A != examined.A)
        {
            // My expected result is an exact copy of the source image, so I'm not expecting this
            throw new Exception("Unexpected.");
        }
    }
}
Fux
  • 111
  • 5
  • What do you mean you get the wrong values? Can you provide a code that reproduces the issue? We now only see how you generate the source bitmap. Can you maybe check if [this](https://github.com/koszeggy/KGySoft.Drawing) library (disclaimer: the author is me) solves the issue? It has some [`CopyTo`](https://docs.kgysoft.net/drawing/html/Overload_KGySoft_Drawing_Imaging_BitmapDataExtensions_CopyTo.htm) methods that should work the same way as the `SourceCopy` compositing mode. At the examples you can find how to use it for GDI+ bitmaps. – György Kőszeg Jun 21 '23 at 15:34
  • You won't get an exact copy, DrawItem() optimizes blits which causes small deviations from the programmed value. Subtract the color channels and only yell when the difference is too big. – Hans Passant Jun 21 '23 at 16:27
  • @GyörgyKőszeg with the code I added in the question I created a source image (`bmp`) and draw it into a target one (`bmpDestDrawImage`) via `DrawImage` method - By "wrong value" I mean that the input pixels seem always to be blended in the output, but I need them to be copied without any change. @HansPassant thanks, so it is impossible to get the exact original pixel by using native calls only, there always will be some blending? – Fux Jun 22 '23 at 06:18
  • I didn't know about the "forced" blending but as I also had several things I didn't like in `DrawImage` I created my own version in the aforementioned library. It's `CopyTo` has more advantages: works between any pixel formats (even indexed ones with palette), it can use quantizing and dithering, it is faster as can utilize all CPUs, supports async (see `CopyToAsync`), [unlike](https://stackoverflow.com/q/3719748/5114784) `DrawImage`, it does not use a process-wide lock that blocks parallel drawing operations, etc. – György Kőszeg Jun 22 '23 at 08:27
  • @GyörgyKőszeg, so is there is no way to make `DrawImage` work as I described? As per your knowledge, should one implement its own logic in order to achieve the wanted behavior? – Fux Jun 22 '23 at 14:50
  • @Fux: _"should one implement its own logic"_ - it depends on what your goal is. Maybe the difference is not visible by eyes but but it still cannot perfectly reproduce the source. `System.Drawing.Graphics` is just a thin wrapper around [GDI+ Graphics](https://learn.microsoft.com/en-us/windows/win32/api/gdiplusgraphics/nl-gdiplusgraphics-graphics) and there can be differences even between Windows versions, not mentioning Linux (powered by libgdiplus) or ReactOS. – György Kőszeg Jun 22 '23 at 15:08
  • 1
    One idea though: try to use `Format32bppPArgb` instead. I suspect the difference appears because the drawing operation uses the premultiplied 32bpp format internally. And of course, some of the original information can be lost when ARGB values are converted, even though it's not noticeable by human eye. Anyhow, the drawing operation will be faster for sure. – György Kőszeg Jun 22 '23 at 15:13
  • @GyörgyKőszeg it seems that you last suggestion is doing the trick for my use cases! Thanks! – Fux Jun 23 '23 at 06:32

0 Answers0