This function is not fully working

EFileTahi-A

Contributor
Joined
Aug 8, 2004
Messages
623
Location
Portugal / Barreiro
Can someone please try to figure out what is happening here? I'm trying to apply the Multiply filter, similar to the one Photoshop uses by crossing the RGB values between two images. I'm not a pointer* guy at all and most of the code was taken from internet so I can't really say what is wrong.

The function is currently working partially, that is, it applies the filter only over a third of the image from top to bottom on the left side.

Code:
private void button2_Click(object sender, EventArgs e)
        {
            Bitmap srcA = (Bitmap)this.lbl_map.Image;
            Bitmap srcB = (Bitmap)this.lbl_paper.Image;
            BitmapData dataA = SetImageToProcess(srcA, new Rectangle(0, 0, 1280, 800));
            BitmapData dataB = SetImageToProcess(srcB, new Rectangle(0, 0, 1280, 800));

            int width = dataA.Width;
            int height = dataA.Height;
            int offset = dataA.Stride - width;

            unsafe
            {
                byte* ptrA = (byte*)dataA.Scan0;
                byte* ptrB = (byte*)dataB.Scan0;

                for (int y = 0; y < height; y++)
                {
                    for (int x = 0; x < width; x++, ++ptrA, ++ptrB)
                    {
                        int result = (ptrB[0] * ptrA[0]) / 255;
                        ptrA[0] = (byte)result > (byte)255 ? (byte)255 : (byte)result;
                    }
                    ptrA += offset;
                    ptrB += offset;
                }
            }
            srcA.UnlockBits(dataA);
            srcB.UnlockBits(dataB);

            this.BackgroundImage = srcA;
        }

        static public BitmapData SetImageToProcess(Bitmap image, Rectangle roi)
        {
            if (image != null)
                return image.LockBits(
                    roi,
                    ImageLockMode.ReadWrite,
                    image.PixelFormat);

            return null;
        }

I really wish I could understand a bit more of how this function actually works. The filter seems to play instantaneously against the 10sec it takes doing it with GDI+.

Thanks
 
It looks like there might be a couple of problems with your pointer math. I don't play with pointers every day, so I can't promise I'll get it 100% either, but here's what I'm noticing:

First, I'm a little confused by this:
Code:
int offset = dataA.Stride - width;
It looks like the value of "offset" is meant to make up the difference between the length of a row's data and the row's stride (i.e. offset specifies the row's padding). That seems like a confusing way to deal with stride.

What I would do is have two pointer variables per image. In addition to ptrA and ptrB, I would have what I'll call rowStartA and rowStartB. You would initalize rowStartA to dataA.Scan0, and rowStartB likewise. Then, after you process each row, add dataA.Stride to rowStartA to get the starting address of the next row. Before you loop over the pixels in each row, you would initialize prtA to the current value of rowStartA, and then increment ptrA after each byte is processed. So, something like this...
Code:
rowStartA = dataA.Scan0
rowStartB = dataB.Scan0

loop over Y
    ptrA = rowStartA
    prtB = rowStartB

    loop over X
        process byte
        ptrA ++
        ptrB ++
    end loop

    rowStartA += dataA.Stride
    rowStartB += dataB.Stride
end loop

Secondly, your pointers walk through bytes. If I understand correctly, you are processing one byte per pixel. With a 24-bit format, it would be three bytes per pixel. With 32-bit, it would be four bytes per pixel, but you would want to skip over the alpha byte. This is what I imagine code would typically look like for an ARGB image, not taking alpha blending into account (hopefully I've got the byte order correct):
Code:
for (int x = 0; x < width; x++)
{
    // Blue
    int result = (ptrB[0] * ptrA[0]) / 255;
    ptrA[0] = (byte)result > (byte)255 ? (byte)255 : (byte)result;
    prtA++; ptrB++;

    // Green
    int result = (ptrB[0] * ptrA[0]) / 255;
    ptrA[0] = (byte)result > (byte)255 ? (byte)255 : (byte)result;
    prtA++; ptrB++;

    // Red
    int result = (ptrB[0] * ptrA[0]) / 255;
    ptrA[0] = (byte)result > (byte)255 ? (byte)255 : (byte)result;
    prtA++; ptrB++;

    // Alpha (ignored) (or you can just copy the alpha channel from image A to B)
    prtA++; ptrB++;
}
For a 24-bit format, just get rid of the last pair of increments.

Hopefully that helps.
 
Towards understanding (and working with) Scan0 and stride

snarfblam said:
It looks like the value of "offset" is meant to make up the difference between the length of a row's data and the row's stride (i.e. offset specifies the row's padding). That seems like a confusing way to deal with stride.
I had a lot of trouble understanding Scan0 and Stride also,
but this Bob Powell article about accessing image data helped me.

It has a good diagram.
 
Thanks to you both, I finally managed to understand how the whole thing works! This will be so cool in future! I think this way I can actually replace most of my GDI+ rendering procedures with pointers*.

I'm still amazed by its speed! It is instantaneously! Too bad I still need to use the DrawImageUnscaled() to draw the backbuffer to avoid flickering. Nonetheless it's still be awesome! :D
 
Last edited:
Wow, at has been a long time!

For some reason, I cannot make this function work properly when image B is a PNG -- I was experimenting my PNG's transparency (a black gradient with 100% black opacity on the left reaching 0% on the right most) over a BMP. Any ideas?
 
Back
Top