Question

I wrote a version of this function which takes a grayscale bitmap as a mask and a source bitmap and outputs a bitmap with the mask applied using SetPixel and GetPixel but it was very slow, so instead I tried to write one using BitmapData and pointer arithmetic but I get an access violation and I'm not sure why.

I get an AccessViolationException when writing to the pixel in the result bitmap - I think this is caused by incorrect indexing, but I can't see where I've gone wrong.

public static Bitmap ApplyAlphaMask2(Bitmap source, Bitmap mask)
{
    if (source.Size != mask.Size)
    {
        throw new NotImplementedException("Applying a mask of a different size to the source image is not yet implemented");
    }
    Bitmap result = new Bitmap(source.Width, source.Height);
    unsafe
    {
        BitmapData source_data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
        BitmapData mask_data = mask.LockBits(new Rectangle(0, 0, mask.Width, mask.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
        BitmapData result_data = result.LockBits(new Rectangle(0, 0, result.Width, result.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
        for(int column = 0; column < source.Height; column++)
        {
            Int32* source_column_ptr = (Int32*)source_data.Scan0 + (column * Math.Abs(source_data.Stride));
            Int32* mask_column_ptr = (Int32*)mask_data.Scan0 + (column * Math.Abs(mask_data.Stride));
            Int32* result_column_ptr = (Int32*)result_data.Scan0 + (column * Math.Abs(mask_data.Stride));
            for (int row = 0; row < source.Width; row++)
            {
                Color source_color = Color.FromArgb(source_column_ptr[row]);
                Color mask_color = Color.FromArgb(mask_column_ptr[row]);
                Int32* result_pixel_ptr = &result_column_ptr[row];
                Color result_color = Color.FromArgb((int)(255.0f * mask_color.GetBrightness()), source_color);
                *result_pixel_ptr = result_color.ToArgb(); //Access violation!
            }
        }
        source.UnlockBits(source_data);
        mask.UnlockBits(mask_data);
        result.UnlockBits(result_data);
    }
    return result;
}

Any help would be appreciated.

EDIT: This doesn't always occur on the same column, although the row appears always to be 0

Was it helpful?

Solution

You're using mask_data.Stride twice (copy-n-paste error).

Why are you using Math.Abs on the stride? Is the stride negative? This might mean that the image is encoded bottom-to-top and taking the abs value would destroy this information.


Edit: The problem is here:

(Int32*)source_data.Scan0 + (column * Math.Abs(source_data.Stride))

You add (column * Math.Abs(source_data.Stride)) to an int* which means that you get 4 times the offset you needed. You probably intended:

(Int32*)((byte*)source_data.Scan0 + (column * Math.Abs(source_data.Stride)))

OTHER TIPS

By the way, your variable names column and row are wrong...need to swap them!
Try with the following modifications. Not tested!:

    public static Bitmap ApplyAlphaMask2(Bitmap source, Bitmap mask)
    {
        int x, y;
        Color mask_color;

        if (source.Size != mask.Size)
        {
            throw new NotImplementedException("Applying a mask of a different size to the source image is not yet implemented");
        }
        Bitmap result = new Bitmap(source.Width, source.Height, PixelFormat.Format32bppArgb);
        BitmapData source_data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        BitmapData mask_data = mask.LockBits(new Rectangle(0, 0, mask.Width, mask.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        BitmapData result_data = result.LockBits(new Rectangle(0, 0, result.Width, result.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
        unsafe
        {
            Int32* source_line_ptr = (Int32*)source_data.Scan0;
            Int32* mask_line_ptr = (Int32*)mask_data.Scan0;
            Int32* result_line_ptr = (Int32*)result_data.Scan0;
            for (y = 0; y < source.Height; y++)
            {
                for (x = 0; x < source.Width; x++)
                {
                    mask_color = Color.FromArgb(mask_line_ptr[x]);
                    result_line_ptr[x] = ((int)(mask_color.GetBrightness() * 255.0f) << 24) | (source_line_ptr[x] & 0xFFFFFF);
                }
                source_line_ptr += source_data.Stride;
                mask_line_ptr += mask_data.Stride;
                result_line_ptr += result_data.Stride;
            }
            source.UnlockBits(source_data);
            mask.UnlockBits(mask_data);
            result.UnlockBits(result_data);
        }
        return result;
    }

I'm not sure you're gonna get the expected results anyway, since GDI+ has problems with greyscale images (if I remember correctly, if you apply LockBits to a greyscale image it returns an image with 16 greys, not 256).
The other thing is if you want to optimize your code, you shouldn't declare local variables inside loops.
Finally, I think the returned value of Color.GetBrightness is not accurate (not sure about this).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top