Applying a color filter to a Bitmap object
Asked Answered
L

1

5

I found this code on how to apply a color filter to a Bitmap object in C#. The problem with it is it uses unsafe code to accomplish this. Is there a managed, safe way to do this same thing? I know I could use a library like AForge.NET or something similar, but I'm hoping there is a simple way to just apply a color filter. All I need is simple color replacement, replacing white pixels with yellow. Any suggestions?

Lily answered 25/3, 2012 at 5:53 Comment(0)
T
4

You could always use the safe GetPixel and SetPixel methods, but they are slow when used on many pixels, which is the reason you use unsafe method to use pointers to the bitmap's memory. Unsafe is the way to do it.

If your bitmap is very small and you don't care about performance all that much, use the GetPixel and SetPixel methods.

    private void ReplaceColor(Bitmap bitmap, Color originalColor, Color replacementColor)
    {
        for (var y = 0; y < bitmap.Height; y++)
        {
            for (var x = 0; x < bitmap.Width; x++)
            {
                if (bitmap.GetPixel(x, y) == originalColor)
                {
                    bitmap.SetPixel(x, y, replacementColor);
                }
            }
        }
    }

    private unsafe void ReplaceColorUnsafe(Bitmap bitmap, byte[] originalColor, byte[] replacementColor)
    {
        if (originalColor.Length != replacementColor.Length)
        {
            throw new ArgumentException("Original and Replacement arguments are in different pixel formats.");
        }

        if (originalColor.SequenceEqual(replacementColor))
        {
            return;
        }

        var data = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size),
                                   ImageLockMode.ReadWrite,
                                   bitmap.PixelFormat);

        var bpp = Image.GetPixelFormatSize(data.PixelFormat);

        if (originalColor.Length != bpp)
        {
            throw new ArgumentException("Original and Replacement arguments and the bitmap are in different pixel format.");
        }

        var start = (byte*)data.Scan0;
        var end = start + data.Stride;

        for (var px = start; px < end; px += bpp)
        {
            var match = true;

            for (var bit = 0; bit < bpp; bit++)
            {
                if (px[bit] != originalColor[bit])
                {
                    match = false;
                    break;
                }
            }

            if (!match)
            {
                continue;
            }

            for (var bit = 0; bit < bpp; bit++)
            {
                px[bit] = replacementColor[bit];
            }
        }

        bitmap.UnlockBits(data);
    }

Usage would be:

        this.ReplaceColor(myBitmap, Color.White, Color.Yellow); // SLOW

OR

        var orgRGB = new byte[] { 255, 255, 255 }; // White (in RGB format)
        var repRGB = new byte[] { 255, 255, 0 }; // Yellow (in RGB format)

        var orgARGB = new byte[] { 255, 255, 255, 255 }; // White (in ARGB format)
        var repARGB = new byte[] { 255, 255, 255, 0 }; // Yellow (in ARGB format)

        var orgRGBA = new byte[] { 255, 255, 255, 255 }; // White (in RGBA format)
        var repRGBA = new byte[] { 255, 255, 0, 255 }; // Yellow (in RGBA format)

        var orgBytes = orgRGB; // or ARGB or RGBA, depending on bitmap's pixel format
        var repBytes = repRGB; // or ARGB or RGBA, depending on bitmap's pixel format

        this.ReplaceColorUnsafe(myBitmap, orgBytes, repBytes); // FAST
Transpontine answered 25/3, 2012 at 7:52 Comment(8)
I was a little apprehensive about the speed of this method, but I tried it out and it actually was quite fast. At least for what I am doing! Thanks for this solution!Lily
Bitmap bitlocking is the common way to do it fast. Glad to help.Transpontine
Is this something I could use parallel extensions for in order to make it work twice as fast?Lily
@icemanind Only do if you know what you're doing. Wouldn't suggest it. In general, parallel is for big collections iterations and interpolations, plinqing... For other things you better of use threading or bg workers. In this case I would choose none.Transpontine
@VirtualBlackFox No need to call .ToArgb() there.Transpontine
@Transpontine Please run this code gist.github.com/vbfox/c0b6a3a497512d5aa29f and tell me the color you see. Then add a call to ToArgb().Calvano
@Transpontine The overload compare more than just the colors, see the note on the corresponding MSDN page : msdn.microsoft.com/en-us/library/… the names are also compared for example making Color.Red != Color.FromArgb(255, 0, 0) return True. The WCF equivalent class don't have the same problem but they didn't change this in WinForms (for compatiblity reasons I guess).Calvano
For me this line did not work: var bpp = Image.GetPixelFormatSize(data.PixelFormat); It returns bits, when the rest of the algorithm expect bytes. So I had to split it by 8Thrust

© 2022 - 2024 — McMap. All rights reserved.