-2

Objective:

I'm making a simple Image processing app that uses Tasks to multithread. A user selects an image from a folder and its displayed in the PicBox. When he inputs the number of threads, colorType to change and Value(0-255) of that colorType(R,G,B), and click edit button, the image is:

Procedure

  1. Converted to byte array
  2. The byte array is returned and a pivot is computed according to thread no.
  3. Task list is created and each task is assigned a start and end index of the larger byte array
  4. In the method, the small portion of larger byte (from start to end index) is saved into smaller byte array
  5. The method then converts the small byte array to Image and returns the image

Problem:

Everything goes fine until in the 5th step I try to convert the byte array to image. It specifically happens when the start index is greater than 0 which is during 2nd task's execution. It works ine for 1st task. Could it be that it can't accept Start Index >0?

Please look into the following code:

Code

                List<Task<Image>> processImgTask = new List<Task<Image>>(threadCount);

                threadCount = Convert.ToInt32(threadCombox.SelectedItem);
                interval = imgArray.Length / threadCount;

                for (int i = 0; i < threadCount; i++)
                {
                    Start = End;
                    End += interval;
                    if (i == threadCount - 1)
                    {
                        End = imgArray.Length;
                    }
                    object data = new object[3] { Start, End, imgArray };
                    processImgTask.Add(new Task<Image>(ImgProcess, data));
                }
                //Task.WaitAll(processImgTask);

                //EDIT followed by comments and answer
                Parallel.ForEach(processImgTask, task =>
                {
                    task.Start();
                    taskPicbox.Image = task.Result;
                });



    private Image ImgProcess(object data)
    {
        object[] indexes = (object[])data;
        int Start=(int)indexes[0];
        int End = (int)indexes[1];            
        byte[] img = (byte[])indexes[2];

        List<byte> splitArray = new List<byte>();
        for (int i =Start;i<End;i++)
        {
            splitArray.Add(img[i]);
        }
        byte[] b = splitArray.ToArray();

        //Error occurs here when task 2 (thread 2) is being executed->
            Image x = (Bitmap)((new ImageConverter()).ConvertFrom(b));
        //System.ArgumentException: 'Parameter is not valid.'                    
        return x;
    }
FawwazFaisal
  • 57
  • 2
  • 11
  • 1
    Are you aware of the tasks being executed in serie instead of parallel? `foreach (Task task in processImgTask) { task.Start(); taskPicbox.Image = task.Result; // <----- blocking }` ? – Jeroen van Langen Apr 14 '20 at 20:59
  • 1
    I would use the `Parallel.Foreach()` for this. It's CPU bound. – Jeroen van Langen Apr 14 '20 at 21:02
  • So you are converting the smaller byte arrays into multiple images? `Image x = (Bitmap)((new ImageConverter()).ConvertFrom(b));` where does the `data` variable come from? I think step 5 is unclear _"The method then converts the small byte array to Image and returns the image"_ If you want to create smaller images, you should feed the task a small image. – Jeroen van Langen Apr 14 '20 at 21:07
  • `object data = new object[3] { Start, End, imgArray };` here, the imgArray is the large array of original image converted to byte[] array. In the ImgProcess method, the the large array is traversed from start to end index in order to create to create a smaller byte[]. this array is then converted to image and reurned. – FawwazFaisal Apr 14 '20 at 21:14

2 Answers2

3

See this answer on how to convert a byte array with raw pixeldata to an bitmap.

I would also strongly suggest using Parallel.For instead of tasks. Tasks are designed to run code asynchronosly, i.e. allow the computer to do things while it is waiting for data. Parallel.For/Foreach is designed to run code in concurrently, i.e. use multiple cores for better performance. Async and parallel is not the same.

I would also recommend starting with a simple single threaded implementation of whatever you are trying to do. Processors are fast, unless you are doing something very demanding the overhead can be significant. And while parallelization may make your code run four times faster (or however many CPU cores you have), it is quite often the case that other optimization can improve performance hundredfold or more. And you can always parallelize later if needed.

For images the typical way to do parallelization would be do a parallel.For over each row in the image.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • thanks for that information. I have editted the code. Although I appreciate the suggestion for using parallelization, I am more interested in using the each task to create a new byte[] from original byte[] and then convert it to an image. Furthermore if I change this `int Start=(int)indexes[0];` to `int Start=0`; my code executes perfectly but not as desired – FawwazFaisal Apr 14 '20 at 21:24
  • What I'm trying to achieve is to convert the byte[] to image and then show it in PicBox. Sleep for 2 seconds and then continue execution of following thread. Until the last task has been executed and the PicBox is showing the image conveted by that last task – FawwazFaisal Apr 14 '20 at 21:26
  • If the goal is to do some kind of animation, the simple solution would be a simple loop in a async method. Update the frame by whatever method and then run await Task.Delay(delayBetweenFrames) – JonasH Apr 14 '20 at 21:38
1

As a response on JonasH, i've made an example for you. This example uses the Span type. It can be done with directly the array or an ArraySegment<byte>.

It's an example how you could process lines multithreaded:

private void Main()
{
    int width = 1024;
    int height = 768;
    int channelCount = 3;

    // create a buffer
    var data = new byte[width * height * channelCount];

    // fill with some data
    // example: 0, 1, 2, 3, 4, 5, 6
    PutSomeValuesInThatBuffer(data);

    // process the image and specify a line-edit callback
    // transforms to: 255, 254, 253, 252, 251, 250
    ProcessImage(data, width, height, channelCount, linePixels =>
    {
        int offset = 0;

        // we need to loop all pixel on this line
        while (offset < linePixels.Length)
        {
            // for RGB | R = channel[0], G = channel[1], B = channel[2], etc...

            // lets invert the colors, this loop isn't quite necessary
            // but it shows the use of channels  (R, G, B)  
            for (int i = 0; i < channelCount; i++)
            {
                linePixels[offset] = 255 - linePixels[offset];
                offset++;
            }
        }
    });
}

public delegate void LineProcessorAction(Span<byte> line);

// this is the process method which will split the data into lines 
// and process them over multiple threads.
private void ProcessImage(
    byte[] data,
    int width, int height, int channelCount,
    LineProcessorAction lineProcessor)
{
    var rowSizeInBytes = width * channelCount;

    Parallel.For(0, height, index => 
        lineProcessor(new Span<byte>(data, index * rowSizeInBytes, rowSizeInBytes)));
}

private static void PutSomeValuesInThatBuffer(byte[] data)
{
    for (int i = 0; i < data.Length; i++)
        data[i] = (byte)i;
}
Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57