2

I'm trying to do some image processing with C# using that same old GDI techniques, iterating through every pixel with a nested for-loop, then using the GetPixel and SetPixel methods on that (Bitmap) image.

I have already got the same results with the pointers approach (using unsafe context) but I'm trying now to do the old-school Get/Set-Pixel Methods to play with my Bitmaps ...

    Bitmap ToGrayscale(Bitmap source)
    {
        for (int y = 0; y < source.Height;y++ )
        {
            for (int x = 0; x < source.Width; x++)
            {
                Color current = source.GetPixel(x, y);
                int avg = (current.R + current.B + current.G) / 3;
                Color output = Color.FromArgb(avg, avg, avg);
                source.SetPixel(x, y, output);
            }
        }
        return source;
    }

considering performance with the code above ... it takes just tooooo much to finish while stressing the user out waiting for his 1800x1600 image to finish processing.

So i thought that i could use the technique that we use working with HLSL, running a seperate function for each pixel (Pixel Shader engine (as i was tought) copies the function returning the float4 (Color) thousands of times on GPU to do the processing parallel).

So I tried to run a separate Task (function) for each pixel, putting these Task variables into a List and the 'await' for List.ToArray(). But I failed doing that as every new Task 'awaits' to be finished before the next one runs.

I wanted to call a new Task for each pixel to run this :

                Color current = source.GetPixel(x, y);
                int avg = (current.R + current.B + current.G) / 3;
                Color output = Color.FromArgb(avg, avg, avg);
                source.SetPixel(x, y, output);

At the end of the day I got my self an async non-blocking code but not parallel ... Any suggestions guys?

svick
  • 236,525
  • 50
  • 385
  • 514
Zaid Ajaj
  • 680
  • 8
  • 16
  • You REALLY REALLY shouldn't be using GetPixel/SetPixel in multithreaded code, no matter the library you use. GetPixel/SetPixel take a lock on the bitmap. The `old-school` way IS to use LockBits, then do whatever you want on the data. – Panagiotis Kanavos Aug 23 '13 at 09:21

3 Answers3

3

GetPixel and SetPixel are likely the main bottleneck here.

Instead of trying to parallelize this, I would recommend using Bitmap.LockBits to handle the parsing much more efficiently.

That being said, you can parallelize your current version via:

Bitmap ToGrayscale(Bitmap source)
{
    Parallel.For(0, source.Height, y =>
    {
        for (int x = 0; x < source.Width; x++)
        {
            Color current = source.GetPixel(x, y);
            int avg = (current.R + current.B + current.G) / 3;
            Color output = Color.FromArgb(avg, avg, avg);
            source.SetPixel(x, y, output);
        }
    });

    return source;
}

However, the Bitmap class is not thread safe, so this will likely cause issues.

A better approach would be to use LockBits, then parallelize working on the raw data directly (as above). Note that I'm only parallelizing the outer loop (on purpose) as this will prevent over saturation of the cores with work items.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
1

Using tasks will just set up multiple threads on the CPU - it won't use the graphics processor. Also, I'm pretty sure that the Bitmap objects you are working with are not thread safe, so you won't be able to use multiple threads to access them anyway.

If all you are trying to do is convert an image to grayscale, I would look at built-in functionality first. In general, something built into the .NET framework can use lower level 'unsafe' code to do things more efficiently than would be possible otherwise, without being unsafe. Try How to: Convert an Image to Greyscale.

If you really want to use multiple threads for your custom bitmap processing, I think you will have to make a byte array, modify it in a multithreaded way, then create a bitmap object at the end from the byte array. Take a look at https://stackoverflow.com/a/15290190/1453269 for some pointers on how to do that.

Community
  • 1
  • 1
Stephen Hewlett
  • 2,415
  • 1
  • 18
  • 31
  • Like I said, I have already tried pointers and unsafe context, I was wondering if I can parallelize such computaions . . . – Zaid Ajaj Aug 22 '13 at 00:50
  • I meant that the functions of .NET could be implemented internally using native code that would be considered unsafe if you did it, but that is not a problem as Microsoft have done it and included it in the framework. I assume you are using .NET 4 as you are talking about tasks, in which case you have access to the FormatConvertedBitmap class mentioned in the article, which will do this for you. – Stephen Hewlett Aug 22 '13 at 00:54
  • I'm not really sure, I'm new to this whole parallel thing so I was reading a couple articles on async and await and I got my self writing some Task methods on the go . . . – Zaid Ajaj Aug 22 '13 at 01:02
  • That will not help you much. For example, if there is only one processor in your machine with four cores, you will at most be able to spread the execution over those four cores, so any more than four threads will not improve the execution time. GPU multithreading is different and not part of the Tasks API. Also, the Bitmap class is not thread safe, so you wouldn't be able to use more than one thread at a time anyway. – Stephen Hewlett Aug 22 '13 at 01:05
1

A good way to parallelize your work is not to dispatch a task per pixel, but to dispatch as many threads as your processor cores.
You also say you are able to manipulate your pixels through pointers, so if you take that route, here goes another important advice: Have each thread work on neighboring pixels.
A valid scenario would be thread 1 working with the first 25% pixels, thread 2 with the next 25% and so on until thread 4.
The above is very important to avoid False Sharing, in which you are effectively dismissing your cache's services, making your algorithm a lot slower.
Other than this, you could probably work with your graphics card, but that is totally out of my league.

EDIT: As noted by Panagiotis in the comments, a task may not correlate to a thread, and as such you have to be cautious about what API you'll use to parallelize your work and how you will do it.

Marcelo Zabani
  • 2,139
  • 19
  • 27
  • Calculating the number of *threads* to use is a job for the TaskScheduler. A task is just a wrapper over a lambda. It's the TaskScheduler that runs the lambda on the thread. Limiting the number of tasks can only cause harm by ignoring opportunities to use more/fewer tasks according to CPU load, features like hyperthreading etc. – Panagiotis Kanavos Aug 23 '13 at 10:48
  • You're absolutely right, I've edited my answer and replaced *tasks* by *threads* to make it more correct. – Marcelo Zabani Aug 23 '13 at 13:52