2

I use a black and white pictures as a mask to generate nice contours after applying a rectangle. Unfortunately to get rid of the black color I use the MakeTransparent method, unfortunately it is very slow, in my code I have to perform such a procedure twice, which in the case of 20 images takes about 5 seconds. Is there any other solution to speed up this procedure?

Bitmap contour = new Bitmap(
    imageMaskCountour.Width, imageMaskCountour.Height, PixelFormat.Format32bppArgb);

using (Graphics g = Graphics.FromImage(contour))
{
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
    g.FillRectangle(ContourBrush, 0, 0, contour.Width, contour.Height);
    g.DrawImage(
        imageMaskCountour,
        new Rectangle(0, 0, contour.Width, contour.Height),
        new Rectangle(0, 0, imageMaskCountour.Width, imageMaskCountour.Height),
        GraphicsUnit.Pixel);
}

contour.MakeTransparent(Color.Black);

Edit:

I try add LockBitmap and add the following method:

public void MakeTransparent(Color color)
{
    for (int y = 0; y < this.Height; y++)
    {
        for (int x = 0; x < this.Width; x++)
        {
            if (this.GetPixel(x, y) == color)
            {
                this.SetPixel(x, y, Color.Transparent);
            }
        }
    }
}

But it is much slower.

Andre Kampling
  • 5,476
  • 2
  • 20
  • 47
Drakoo
  • 307
  • 1
  • 3
  • 15
  • My first question would be why you have to call it twice per image – BugFinder Mar 31 '17 at 06:33
  • Generates water level in the tank, in the first photo the rectangular simulate the current water level and the mask generates the tank interior shape, and on the second photo generates the tank contour in the correct color. At the end, I combine both bitmaps. – Drakoo Mar 31 '17 at 06:52
  • Can you not run then both detection's concurrently, and merge the 2 at the end? – BugFinder Mar 31 '17 at 06:56
  • Well GDI+ isn't particularly fast at the best of times. Maybe you should consider WPF - at least it is _hardware-accelerated_ http://stackoverflow.com/a/12171698/585968. –  Mar 31 '17 at 07:02
  • @BugFinder Unfortunately, this is not possible because the black color of one of the photos will always cover the other part. DrawImage is not too fast either. – Drakoo Mar 31 '17 at 07:12
  • How did you implemenrt the full lockbitmap code? - Also: Did you try to use a direct method that uses lockbits? – TaW Mar 31 '17 at 07:19
  • Regarding edit with x-y loops: yes performing image manipulation at the (x,y) level is always going to be considerably slower than operating on the underlying raster buffer via _pointers_ –  Mar 31 '17 at 07:25
  • 1
    `GetPixel`/``SetPixel` is too slow.. Using lockbitmap lock and get/set pixel is useless – Jeroen van Langen Mar 31 '17 at 07:28
  • 1
    Example: `Size s = bmp.Size; PixelFormat fmt = bmp.PixelFormat; Rectangle rect = new Rectangle(Point.Empty, s); BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, fmt); int size1 = bmpData.Stride * bmpData.Height; byte[] data = new byte[size1]; System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, data, 0, size1);`... – TaW Mar 31 '17 at 07:28
  • 1
    `for (int y = 0; y < s.Height; y++) { for (int x = 0; x < s.Width; x++) { int index = y * bmpData.Stride + x * 4; if ( data[index + 2] + data[index + 1] + data[index + 0] <= 0 ) data[index + 3] = 0; } } System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length); bmp.UnlockBits(bmpData)` for a given Bitmap bmp. You can use a larger value than `0` to allow some tolerance.. – TaW Mar 31 '17 at 07:29
  • @Jeroen: The Get/SetPixel are methods of the LockBitmap class, not the usual Bitmap.GetPixel..! – TaW Mar 31 '17 at 07:31
  • 1
    Also: Sometimes one can hear that using PixelFormat.Format32bppPArgb is a lot faster. – TaW Mar 31 '17 at 07:33
  • @TaW why post it as comment? Kinda unreadable this way.. – Jeroen van Langen Mar 31 '17 at 07:39
  • @Joergen A) the link you gave is the link from the OP! b) a comment just to test if it even is faster than the lockbitmap. – TaW Mar 31 '17 at 07:48
  • @FaW A, lol I'll remove it. – Jeroen van Langen Mar 31 '17 at 07:55
  • 1
    @TaW Your method gets very similar results to MakeTransparent, But it helped me with another problem, greatly speeding up my previous solution, so thank you anyway! – Drakoo Mar 31 '17 at 08:56
  • _it helped me with another problem, greatly speeding up my previous solution_ lol, you make me curious.. – TaW Mar 31 '17 at 09:05
  • In another place I also used Lockbit with the link I gave, But turning on both loops to get GetPixel () to find the color takes about 1 second, your code copes with that below 10 milliseconds! ;) – Drakoo Mar 31 '17 at 09:23
  • Can I do what I need using OpenGL oraz Direct2D ? – Drakoo Mar 31 '17 at 10:01

1 Answers1

2

Every operation you are doing on the Bitmap requires locking and unlocking of the bits. That makes it very slow. See this answer how to access the bitmap data directly by locking once, manipulating all data and finally unlocking it.

The following code is an extension method for a regular Bitmap using the direct access just mentioned. It implements the logic of your method. Note that I save the Height and Width to local variables. Iam doing that because I recognized that it is much slower if used multiple times like in a loop condition.

Caution:

The underlying code only works for PixelFormat.Format32bppArgb, see PixelFormat reference. The code gives you an idea how to access the pixel data directly and can be adapted to other pixel formats too.

The following points must be respected if adapting to other pixel formats.

  • Byte size of one destination pixel to correctly write the color value.
  • The correct row addressing through the Bitmap.Stride property, see Stride for reference. In case of PixelFormat.Format32bppArgb the stride matches the width by: Bitmap.Stride == Bitmap.Width * 4 and therefore no row address adjustments are needed.

public static void FastMakeTransparent(this Bitmap bitmap, Color color)
{
    BitmapData bitmapData = bitmap.LockBits(
        new Rectangle(0, 0, bitmap.Width, bitmap.Height),
        ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

    unsafe
    {
        int* pixelPointer = (int*)bitmapData.Scan0;

        int bitmapHeight = bitmap.Height;
        int bitmapWidth = bitmap.Width;
        int colorInt = color.ToArgb();
        int transparentInt = Color.Transparent.ToArgb();

        for (int i = 0; i < bitmapHeight; ++i)
        {
            for (int j = 0; j < bitmapWidth; ++j)
            {
                if (*pixelPointer == colorInt)
                    *pixelPointer = transparentInt;
                ++pixelPointer;
            }
        }
    }

    bitmap.UnlockBits(bitmapData);
}

Benchmark with Stopwatch class on my Intel Core2Duo P8400 (2.26 GHz) CPU.

Bitmap size 1000x1000 random filled accumulated time of 100 runs
Target AnyCPU .NetFramework 4.5.2

Release build

MakeTransparent      Ticks: 24224801 | Time: 2422 ms
FastMakeTransparent  Ticks: 1332730  | Time: 133 ms


Debug build

MakeTransparent      Ticks: 24681178 | Time: 2468 ms
FastMakeTransparent  Ticks: 5976810  | Time: 597 ms
Andre Kampling
  • 5,476
  • 2
  • 20
  • 47