0

I am writing a simple picture blender in Windows Forms. Although it works rather fine I have a problem - while application is in progress - I can't move the main Winodow Form. The application allows two threads to work in background. When only one is busy then if I try to drag the window it responds with a delay (as if it needed time to get a focus back). When two Background Builders do their task I can't move main form at all. After they finish I can move window again. I thought that passing 'this' as a argument to another thread might be the issue, but I make copies of fields I need and just in case I've added 'this.Activate()' after calling a separate thread. This makes no difference anyway.

This is how I call workers:

private void PerformBlending()
    {
        if (!bw2.IsBusy)
        {
            bw2.RunWorkerAsync(this);
            this.Activate();
        }
        else
        {
            bw3.RunWorkerAsync(this);
            this.Activate();
        }
    }

private void bw2_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker bw = sender as BackgroundWorker;
        Form1 mainForm = e.Argument as Form1;
        Bitmap leftBmp = new Bitmap(mainForm.buttonPic1.BackgroundImage);
        Bitmap rightBmp = new Bitmap(mainForm.buttonPic2.BackgroundImage);
        Bitmap resultBmp;
        int leftX = leftBmp.Width; int leftY = leftBmp.Height;
        int rightX = rightBmp.Width; int rightY = rightBmp.Height;
        int x = leftX < rightX ? leftX : rightX;
        int y = leftY < rightY ? leftY : rightY;
        resultBmp = new Bitmap(x, y);
        double alfa = mainForm.trackBarValue;
        for (int i = 0; i < x; ++i)
        //Parallel.For(0, x, i =>
        {
            for (int j = 0; j < y; ++j)
            {
                bw.ReportProgress((int)(((double)i / (double)y + (double)1 / (double)x) * 100));

                int leftR = (i < leftX && j < leftY) ? (int)(alfa * leftBmp.GetPixel(i, j).R) : 0;
                int leftG = (i < leftX && j < leftY) ? (int)(alfa * leftBmp.GetPixel(i, j).G) : 0;
                int leftB = (i < leftX && j < leftY) ? (int)(alfa * leftBmp.GetPixel(i, j).B) : 0;

                int rightR = (i < rightX && j < rightY) ? (int)((1 - alfa) * rightBmp.GetPixel(i, j).R) : 0;
                int rightG = (i < rightX && j < rightY) ? (int)((1 - alfa) * rightBmp.GetPixel(i, j).G) : 0;
                int rightB = (i < rightX && j < rightY) ? (int)((1 - alfa) * rightBmp.GetPixel(i, j).B) : 0;

                int r = leftR + rightR;
                int g = leftG + rightG;
                int b = leftB + rightB;
                resultBmp.SetPixel(i, j, Color.FromArgb(r, g, b));
            }
        }//);
        e.Result = resultBmp;

    }
wis.niowy
  • 87
  • 1
  • 12
  • Please show your `DoWorkEventHandler`s – James Durda Apr 09 '17 at 12:05
  • 1
    You are calling ReportProgress too often. GetPixel and SetPixel are slow, look up how to use LockBits – LarsTech Apr 09 '17 at 12:18
  • @LarsTech I know that ReportProgress handler is executed in the main (UI) thread. So calling ReportProgress to often makes my UI thread busy almost incessantly thus I cannot move the main form - is that right? – wis.niowy Apr 09 '17 at 12:20

1 Answers1

1

To expand on @LarsTech's comment, your current progress calculation isn't even dependent on j so move the ReportProgress call outside of the inner loop. This will help responsiveness by reducing the number of calls without even changing the behavior of your ProgressBar.

for (int i = 0; i < x; ++i)
    //Parallel.For(0, x, i =>
    {
        bw.ReportProgress((int)(((double)i / (double)y + (double)1 / (double)x) * 100));

        for (int j = 0; j < y; ++j)
        {
            int leftR = (i < leftX && j < leftY) ? (int)(alfa * leftBmp.GetPixel(i, j).R) : 0;
            int leftG = (i < leftX && j < leftY) ? (int)(alfa * leftBmp.GetPixel(i, j).G) : 0;
            int leftB = (i < leftX && j < leftY) ? (int)(alfa * leftBmp.GetPixel(i, j).B) : 0;

            int rightR = (i < rightX && j < rightY) ? (int)((1 - alfa) * rightBmp.GetPixel(i, j).R) : 0;
            int rightG = (i < rightX && j < rightY) ? (int)((1 - alfa) * rightBmp.GetPixel(i, j).G) : 0;
            int rightB = (i < rightX && j < rightY) ? (int)((1 - alfa) * rightBmp.GetPixel(i, j).B) : 0;

            int r = leftR + rightR;
            int g = leftG + rightG;
            int b = leftB + rightB;
            resultBmp.SetPixel(i, j, Color.FromArgb(r, g, b));
        }
    }//);

Also, using LockBits will help with actual performance. As this part is kind of its own topic and one I'm not too familiar with, I'll just give some links that might help you get started:

LockBits vs Get/Set Pixel

How do I conver my get GetPixel/SetPixel color processing to Lockbits

And for a different approach:

Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App

Community
  • 1
  • 1
James Durda
  • 625
  • 6
  • 12
  • "your current progress calculation isn't even dependent on `j`" - well, it should be, my mistake. It should be `bw.ReportProgress((int)(((double)(i*x+j)/(double)(x*y))*100));` actually. If `LockBits` mentioned, I'm during introducing this in my code – wis.niowy Apr 09 '17 at 13:48
  • If `LockBits` mentioned, should it work with two threads trying to read the same pair of bitmaps to blend at the same time? After I used `LockBits` - when first `BackgroundBuilder` is busy I cannot start second `BackgroundBuilder` at all - the main form doesn't respond at all. – wis.niowy Apr 09 '17 at 13:55
  • Is there any other background tasks? You have bw2 and bw3. Can I presume there is a bw1 doing something? – James Durda Apr 09 '17 at 14:25
  • I mocked this up just moving the progress update (using the version not dependent on `j`) out of the `j` loop and form responds just fine. This is without using `LockBits` which may be introducing another problem. Keep in mind, It is unlikely that you really need that inner loop of progress reporting. After all, it's happening so fast, it's choking your app to get notices about it. The outer loop produces a nice smooth progress indicator. Just be sure to change your maximum on your progress bar to the value of `x` – James Durda Apr 09 '17 at 15:02
  • @[James Durda] bw1 is responsible for waiting until 2 pictures are uploaded by the user so at the stage we start bw2 or bw3, bw1 is already finished and will not work again. I realise that moving ReportProgress outside of the loop should enhance the perforamance, but my task is to update `ProgressBar` at least every 5 percent of progress. I added the condition `if((int)(((double)(i*x+j)/(double)(x*y))*100) % 5 == 0)` (which is - if next 5 percent of a progress has ensued) then report progress and dragging the form while having two `BackgroundWorkers` busy is no problem now. – wis.niowy Apr 09 '17 at 17:40
  • I still have another issue, though. `LockBits` have given me food for thought for for that one thread this solution worked - it worked much faster, indeed. The solution I currently have - with GetPixel/SetPixel works with two `BackgroundBuilders` busy but it performs much worse than a sample solution given us by our lecturer. Hence I wonder how to make LockBits work for two parralel threads OR why the Parallel.For doesn't work (you can see it commented in the code above). – wis.niowy Apr 09 '17 at 17:46
  • `LockBits` and `Parallel` are a bit outside of my comfort zone but perhaps this link can help you: [Fast Image Processing in C#](http://csharpexamples.com/fast-image-processing-c/) Not sure if you can use `unsafe` or not but there is a `Parallel.For` version down at the bottom. – James Durda Apr 10 '17 at 02:41