locked
BitmapEncoder.CreateAsync never returns RRS feed

  • Question

  • Hi Everyone,

    I'm trying to save a WritableBitmap as JPEG file in my Windows Store app (C#). My problem is that the BitmapEncoder.CreateAsync seems never to return: my application hangs up and pausing it in VisualStudio shows that it is on that very line.

    If you have any idea what might be causing this I'll be glad to read about it...I'm some kind out of clues right now...

    Many Thanks in advance,

    Here is what I'm doing:

    private async Task SaveBitmap(WriteableBitmap bitmap, StorageFile file, int resolution, byte[] pixels)
           {
                using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
                {
                    var propertySet = new Dictionary<string, BitmapTypedValue>();
    
                    BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream, propertySet);
                    encoder.SetPixelData(BitmapPixelFormat.Rgba8,
                                         BitmapAlphaMode.Ignore,
                                         (uint)bitmap.PixelWidth,
                                         (uint)bitmap.PixelHeight,
                                         resolution,
                                         resolution,
                                         pixels);
                }
            }

    Sunday, January 27, 2013 7:11 PM

Answers

  • Alright I finally found how to have that whole thing to work.

    The body of the SaveBitmap method wasn't really the problem. As I explained in my previous answer I'm doing the whole thing in another thread than the dispatcher one. I had the call to SaveBitmap wrapped in a dispatcher invoke and it seems that this was the problem.

    The solution I found is to call the SaveBitmap method directly in the "background" thread context and only wrap the SetPixelData into a dispatcher invoke. SetPixelData indeed needs to be called in the dispatcher thread context.

    Many thanks to jrboddie and Dave Smits for their help !

    And finally my solution:

    public void MyMethodRunningInThread()
    {
       // 1. Generating the data for my bitmap
       // 2. Creating the Bitmap using WritableBitmap: in dispatcher thread context
       // 3. Save the bitmap
       SaveBitmap(myBitmap, ....);
    }
    
    private async Task SaveBitmap(WriteableBitmap bitmap, StorageFile file, int resolution, byte[] pixels)
            {
                // Information about how to use the BitmapEncoder class is available at
                // http://msdn.microsoft.com/en-us/library/windows/apps/hh465076.aspx
                using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
                {
                    // Information about encoder property set available at
                    // http://msdn.microsoft.com/en-us/library/windows/apps/jj218354.aspx
                    var propertySet = new BitmapPropertySet();
                    propertySet.Add("ImageQuality", new BitmapTypedValue(1.0, PropertyType.Single));
    
    
                    // BitmapEncoder documentation states that the size of the destination stream has to be 0
                    stream.Size = 0;
    
                    try
                    {
                        // The BitmapEncoder.CreateAsync never returned if it was called within the dispatcher
                        // thread context.
                        BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, 
                                                                                stream,
                                                                                propertySet);
    
                        UISafeInvoker.DispatcherSafeInvoke(() =>
                            {
                                // SetPixelData has to be called within the dispatcher thread
                                encoder.SetPixelData(BitmapPixelFormat.Rgba8,
                                                BitmapAlphaMode.Ignore,
                                                (uint)bitmap.PixelWidth,
                                                (uint)bitmap.PixelHeight,
                                                resolution,
                                                resolution,
                                                pixels);
                            });
    
                        await encoder.FlushAsync();
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                        Debugger.Break();
                    }
                }
            }

    • Marked as answer by Bruno Knittel Tuesday, January 29, 2013 7:09 AM
    Tuesday, January 29, 2013 7:09 AM

All replies

  • what if place try catch around it? exception been throw?

    Microsoft Certified Solutions Developer - Windows Store Apps Using C#

    Sunday, January 27, 2013 7:35 PM
  • I have zero experience with this but I notice that the documentation for BitmapEncoder states that it "expects that the output stream is empty.  You can ensure that the stream is empty by setting its Size property to 0."

    So try adding 

    stream.Size = 0;

    before the BitmapEncoder.CreateAsync is called.

    Make sure you are pointing your stream to a file location that you are allowed to access from a Windows Store app.

    Your encoding options dictionary is empty.  The documentation says it should contain one or more options.

    -Just a couple of ideas for you to check...


    • Edited by jrboddie Sunday, January 27, 2013 7:52 PM
    Sunday, January 27, 2013 7:50 PM
  • Many thanks to both of you for your quick answers. I tried all your suggestions.

    To avoid file permission problems I'm writting to an InMemoryRandomAccessStream.

    Sadly, I'm still experiencing that strange behavior: BitmapEncoder.CreateAsync seems never to return...

    If you have further ideas I'll be glad to read them.

    Here is my current code:

    private async Task SaveBitmap(WriteableBitmap bitmap, StorageFile file, int resolution, byte[] pixels)
            {
                // Information about how to use the BitmapEncoder class is available at
                // http://msdn.microsoft.com/en-us/library/windows/apps/hh465076.aspx
                using (IRandomAccessStream stream = new InMemoryRandomAccessStream())
                {
                    // Information about encoder property set available at
                    // http://msdn.microsoft.com/en-us/library/windows/apps/jj218354.aspx
                    var propertySet = new BitmapPropertySet();
                    propertySet.Add("ImageQuality", new BitmapTypedValue(1.0, PropertyType.Single));
    
    
                    // BitmapEncoder documentation states that the size of the destination stream has to be 0
                    stream.Size = 0;
    
                    try
                    {
                        BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, 
                                                                                stream,
                                                                                propertySet);
    
                        encoder.SetPixelData(BitmapPixelFormat.Rgba8,
                                             BitmapAlphaMode.Ignore,
                                             (uint)bitmap.PixelWidth,
                                             (uint)bitmap.PixelHeight,
                                             resolution,
                                             resolution,
                                             pixels);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                        Debugger.Break();
                    }
                }
            }


    • Edited by Bruno Knittel Monday, January 28, 2013 7:17 AM Wrong Code
    Monday, January 28, 2013 7:16 AM
  • In trying to duplicate your result, I found that you need to flush the encoder before the file is saved or error messages are transmitted.  See example here.

    Try adding:

     await encoder.FlushAsync();

    after encoder.SetPixelData(...

    Here is what worked for me using most of your latest attempt.

    Changes

    1. added the await encoder.FlushAsync
    2. hard coded the pixel width/height since that is the only thing your code uses 'WriteableBitmap' for
    3. reverted code to get stream from file
    4. added calling functions to create pixel array and file.
            private async void Button_Click_1(object sender, RoutedEventArgs e)
            {
                var picker = new Windows.Storage.Pickers.FileSavePicker();
                picker.FileTypeChoices.Add("JPEG image", new string[] { ".jpg" });
                picker.DefaultFileExtension = ".jpg";
    
                var file = await picker.PickSaveFileAsync();
                int resolution = 96; //dpi
                // An array representing 2x2 red, opaque pixel data
                var pixels = new byte[] {
                255, 0, 0, 255,
                255, 0, 0, 255,
                255, 0, 0, 255,
                255, 0, 0, 255
                };
    
                await SaveBitmap(file, resolution, pixels);
            }
    
            private async Task SaveBitmap(StorageFile file, int resolution, byte[] pixels)
            {
                // Information about how to use the BitmapEncoder class is available at
                // http://msdn.microsoft.com/en-us/library/windows/apps/hh465076.aspx
                using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
                {
                    // Information about encoder property set available at
                    // http://msdn.microsoft.com/en-us/library/windows/apps/jj218354.aspx
                    var propertySet = new BitmapPropertySet();
                    propertySet.Add("ImageQuality", new BitmapTypedValue(1.0, PropertyType.Single));
    
                    // BitmapEncoder documentation states that the size of the destination stream has to be 0
                    stream.Size = 0;
    
                    try
                    {
                        BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId,
                                                                                stream,
                                                                                propertySet);
    
                        encoder.SetPixelData(BitmapPixelFormat.Rgba8,
                                             BitmapAlphaMode.Ignore,
                                             2, //Pixel width
                                             2, //Pixel height
                                             resolution,
                                             resolution,
                                             pixels);
                        await encoder.FlushAsync();
                    }
    
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                        Debugger.Break();
                    }
                }
            }
    Hope this helps you get closer to a solution.



    • Edited by jrboddie Monday, January 28, 2013 2:42 PM
    Monday, January 28, 2013 1:26 PM
  • Many thanks jrboddie, you're involving yoursefl a lot.

    Unfortunately I don't get far enough to have the Flush issue as my code never executes beyond the await BitmapEncoder.CreateAsync line. Neverless you're right, the Flush is actually missing from my code. Thank you for having pointed that.

    I'm wondering if this might have something to do with dispatcher invocation. I'm actually doing that whole thing within a delegate that is invoked in the UI dispatcher...

    Anyway I'll post my updates here, and thanks again.

    Monday, January 28, 2013 5:55 PM
  • Alright I finally found how to have that whole thing to work.

    The body of the SaveBitmap method wasn't really the problem. As I explained in my previous answer I'm doing the whole thing in another thread than the dispatcher one. I had the call to SaveBitmap wrapped in a dispatcher invoke and it seems that this was the problem.

    The solution I found is to call the SaveBitmap method directly in the "background" thread context and only wrap the SetPixelData into a dispatcher invoke. SetPixelData indeed needs to be called in the dispatcher thread context.

    Many thanks to jrboddie and Dave Smits for their help !

    And finally my solution:

    public void MyMethodRunningInThread()
    {
       // 1. Generating the data for my bitmap
       // 2. Creating the Bitmap using WritableBitmap: in dispatcher thread context
       // 3. Save the bitmap
       SaveBitmap(myBitmap, ....);
    }
    
    private async Task SaveBitmap(WriteableBitmap bitmap, StorageFile file, int resolution, byte[] pixels)
            {
                // Information about how to use the BitmapEncoder class is available at
                // http://msdn.microsoft.com/en-us/library/windows/apps/hh465076.aspx
                using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
                {
                    // Information about encoder property set available at
                    // http://msdn.microsoft.com/en-us/library/windows/apps/jj218354.aspx
                    var propertySet = new BitmapPropertySet();
                    propertySet.Add("ImageQuality", new BitmapTypedValue(1.0, PropertyType.Single));
    
    
                    // BitmapEncoder documentation states that the size of the destination stream has to be 0
                    stream.Size = 0;
    
                    try
                    {
                        // The BitmapEncoder.CreateAsync never returned if it was called within the dispatcher
                        // thread context.
                        BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, 
                                                                                stream,
                                                                                propertySet);
    
                        UISafeInvoker.DispatcherSafeInvoke(() =>
                            {
                                // SetPixelData has to be called within the dispatcher thread
                                encoder.SetPixelData(BitmapPixelFormat.Rgba8,
                                                BitmapAlphaMode.Ignore,
                                                (uint)bitmap.PixelWidth,
                                                (uint)bitmap.PixelHeight,
                                                resolution,
                                                resolution,
                                                pixels);
                            });
    
                        await encoder.FlushAsync();
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                        Debugger.Break();
                    }
                }
            }

    • Marked as answer by Bruno Knittel Tuesday, January 29, 2013 7:09 AM
    Tuesday, January 29, 2013 7:09 AM
  • Great.  I just ran across this blog post which may explain the problem you found and fixed.  

    Psychic Debugging of Async Methods (See Item 3 "My Async method never completes")

    Tuesday, January 29, 2013 1:14 PM
  • Thanks for sharing the link. It indeed explains much about such deadlock situations.

    Tuesday, January 29, 2013 4:39 PM