3

Tiny WPF app converting image(BitmapImage) using Greyscale input params in async/await manner.

I read dozen of implementations and did not manage to make it work :/

Button method:

private async void btnConvertImage_ClickAsync(object sender, RoutedEventArgs e)
{
    try
    {
        var cts = new CancellationTokenSource();
        BitmapImage result = await ImageProcessing.GreyscaleAsync(orginalImage, cts.Token).ConfigureAwait(false);
        imgPhotoConverted.Source = result;
    }
}

Greyscale Task definition:

public static async Task<BitmapImage> GreyscaleAsync(BitmapImage inputBitmapImage, CancellationToken cancellationToken)
{
    return await Task.Run(() =>
    {
        Bitmap inputBitmap = ToBitmap(inputBitmapImage);
        Bitmap outputImage = new Bitmap(inputBitmap.Width, inputBitmap.Height);
        for (int i = 0; i < inputBitmap.Width; i++)
        {
            for (int x = 0; x < inputBitmap.Height; x++)
            {
                cancellationToken.ThrowIfCancellationRequested();
                Color imageColor = inputBitmap.GetPixel(i, x);
                int grayScale = (int)((imageColor.R * 0.21) + (imageColor.G * 0.72) + (imageColor.B * 0.07));
                Color newColor = Color.FromArgb(imageColor.A, grayScale, grayScale, grayScale);
                outputImage.SetPixel(i, x, newColor);
            }
        }

        return ToBitmapImage(outputImage);
    }, cancellationToken);
}

On line:

imgPhotoConverted.Source = result;

an error is thrown:

System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
6
  • 3
    Remove ConfigureAwait(false) Commented Aug 29, 2019 at 22:27
  • 1
    Still an error. Commented Aug 29, 2019 at 22:31
  • What error? Be more precise Commented Aug 29, 2019 at 23:00
  • Did you try imgPhotoConverted.Source = new Bitmap(result); Commented Aug 29, 2019 at 23:11
  • What's the stack trace of the exception? Commented Aug 29, 2019 at 23:37

3 Answers 3

3

I think there is an easier way to do this: Freeze() the bitmap before returning it.

Then it doesn't matter which thread accesses it (of course actual UI elements still need to be accessed only from the WPF thread)

I solved a similar problem with a modification like below.

Task.Run(() =>
{
...
        var bmp = ToBitMapImage(outputImage);
        bmp.Freeze();
        return bmp;
}...
Sign up to request clarification or add additional context in comments.

1 Comment

This is a much better/more concise answer as it explains what the actually problem is.
2

You should read more about async/await at Stephen Clearys Blog.

Following the advices there will lead you to a very smart solution

private async void btnConvertImage_ClickAsync(object sender, RoutedEventArgs e)
{
    var originalImage = ( imgPhotoOriginal.Source as BitmapImage );
    BitmapImage result = await Task.Run( () => originalImage.ToBitmap().ToGrayscale().ToBitmapImage() );
    imgPhotoConverted.Source = result;
}

which is consuming this extension class

public static class BitmapExtensions
{
    public static Bitmap ToGrayscale( this Bitmap source, CancellationToken cancellationToken = default )
    {
        Bitmap output = new Bitmap( source.Width, source.Height );
        for ( int i = 0; i < source.Width; i++ )
        {
            for ( int x = 0; x < source.Height; x++ )
            {
                cancellationToken.ThrowIfCancellationRequested();
                var imageColor = source.GetPixel( i, x );
                int grayScale = (int)( ( imageColor.R * 0.21 ) + ( imageColor.G * 0.72 ) + ( imageColor.B * 0.07 ) );
                var newColor = System.Drawing.Color.FromArgb( imageColor.A, grayScale, grayScale, grayScale );
                output.SetPixel( i, x, newColor );
            }
        }
        return output;
    }

    public static Bitmap ToBitmap( this BitmapImage source )
    {
        using ( MemoryStream outStream = new MemoryStream() )
        {
            BitmapEncoder enc = new BmpBitmapEncoder();
            enc.Frames.Add( BitmapFrame.Create( source ) );
            enc.Save( outStream );
            System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap( outStream );

            return new Bitmap( bitmap );
        }
    }

    public static BitmapImage ToBitmapImage( this Bitmap source )
    {
        using ( var memory = new MemoryStream() )
        {
            source.Save( memory, ImageFormat.Png );
            memory.Position = 0;

            var bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = memory;
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.EndInit();
            bitmapImage.Freeze();

            return bitmapImage;
        }
    }
}

3 Comments

Thx for a suggestion @SirRufo. This solution looks clean, the only 'but' is that my extension class do not encapsulate the Task itself in ASYNC manner (returning Task promise)
Hmmm, my extension class has nothing to to with any task because it is all cpu bound work. I execute it inside a task at the UI level as that is how you should do it
Please don't get me wrong -> your solution is absolutely helpful and I appreciate it very much. Thank you!
-1

I managed to solve it:

  • I did not have to use a BitmapSource, yet I had to Freeze() the output BitmaImage and wrap the result in proper Context using:
ThreadPool.QueueUserWorkItem(async delegate
{
    // ThreadPool
    var cts = new CancellationTokenSource();
    BitmapImage result = await ImageProcessing.GreyscaleAsync(orginalImage, cts.Token);
    result.Freeze();

    sc.Post(delegate
    {
        // original context (UI)
        imgPhotoConverted.Source = result;
        cts.Cancel();
    }, null);
}

I hope this will be useful for others. Thx!

1 Comment

My advice to you: read more about async/await. Although it is working it is still bad code and can be written much better/easier

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.