3

I'm trying to resize images in my UWP application. Most of the time the appended code works, but sometimes await encoder.FlushAsync(); throws an ArgumentException.

I've headed over to MSDN (https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmapencoder.bitmaptransform.aspx) and they tell me (at "Remarks"):

If you try scale an image stored in an indexed pixel format using the BitmapTransform member, FlushAsync fails with HRESULT WINCODEC_ERR_INVALIDPARAMETER . Instead, you must use GetPixelDataAsync to obtain the scaled pixel data and then use SetPixelData to set it on the encoder.

I've tried to do that, see the two commented lines (which look somehow wrong to me due to the repetition). On the second line (where I try to SetPixelData) the Encoder rewards me with an buffer allocated not sufficient Exception.

var decoder = await BitmapDecoder.CreateAsync(streamToReadFrom.AsStream().AsRandomAccessStream());
if (decoder.OrientedPixelHeight > height ||
    decoder.OrientedPixelWidth > width)
{
    var resizedStream = new InMemoryRandomAccessStream();
    BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(resizedStream, decoder);

    encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
    encoder.BitmapTransform.ScaledHeight = newHeight;
    encoder.BitmapTransform.ScaledWidth = newWidth;

    //"buffer allocated not sufficient"
    // var pd = await decoder.GetPixelDataAsync(BitmapPixelFormat.Rgba16, BitmapAlphaMode.Ignore,
    //             encoder.BitmapTransform, ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);
    // encoder.SetPixelData(BitmapPixelFormat.Rgba16, BitmapAlphaMode.Ignore,
    //             decoder.OrientedPixelWidth, decoder.OrientedPixelHeight, decoder.DpiX, decoder.DpiY, pd.DetachPixelData());

    // write out to the stream
    // might fail cause https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmapencoder.bitmaptransform.aspx
    await encoder.FlushAsync();

    // Read out resizedStream and return
}

Example image which causes this issue: http://www.spiegel.de/images/image-1028227-hppano-lqbn.jpg. Unit Test here: https://github.com/famoser/OfflineMedia/blob/master/Famoser.OfflineMedia.UnitTests/Presentation/ImageResizeTest.cs

How can I avoid the ArgumentException? How do I know an image is in an "indexed pixel format", and how can I resize this format too?

Florian Moser
  • 2,583
  • 1
  • 30
  • 40

1 Answers1

1

On the second line (where I try to SetPixelData) the Encoder rewards me with an buffer allocated not sufficient Exception.

This is because when you SetPixelData, the pixel data doesn't match it from GetPixelDataAsync. You can for example code like this:

if (file != null)
{
    BitmapImage bmp = new BitmapImage();
    using(var imageStream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
    {
        BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream);
        InMemoryRandomAccessStream pixelras = new InMemoryRandomAccessStream();
        BitmapEncoder pixelencoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, pixelras);
        BitmapTransform transform = new BitmapTransform();
        transform.InterpolationMode = BitmapInterpolationMode.Fant;
        transform.ScaledHeight = 400;
        transform.ScaledWidth = 400;
        var provider = await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8, 
            BitmapAlphaMode.Ignore, 
            transform, 
            ExifOrientationMode.RespectExifOrientation, 
            ColorManagementMode.DoNotColorManage);
        var pixels = provider.DetachPixelData();
        pixelencoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, 400, 
            400, decoder.DpiX, decoder.DpiY, pixels);

        try
        {
            await pixelencoder.FlushAsync();
        }
        catch(Exception ex)
        {

        }
        bmp.SetSource(pixelras);
        img.Source = bmp;                  
    }
}

How do I know an image is in an "indexed pixel format", and how can I resize this format too?

I couldn't find any effective way to detect an indexed pixel format image, but since it is said

If you try scale an image stored in an indexed pixel format using the BitmapTransform member, FlushAsync fails with HRESULT WINCODEC_ERR_INVALIDPARAMETER . Instead, you must use GetPixelDataAsync to obtain the scaled pixel data and then use SetPixelData to set it on the encoder.

It is a method to use catch the exception and use SetPixelData again, for example:

if (file != null)
{
    BitmapImage bmp = new BitmapImage();
    using(var imageStream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
    {
        BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream);
        InMemoryRandomAccessStream ras = new InMemoryRandomAccessStream();
        BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(ras, decoder);

        encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
        encoder.BitmapTransform.ScaledHeight = 400;
        encoder.BitmapTransform.ScaledWidth = 400;

        try
        {
            await encoder.FlushAsync();
            bmp.SetSource(ras);
        }
        catch (Exception ex)
        {
            if (ex.HResult.ToString() == "WINCODEC_ERR_INVALIDPARAMETER")
            {
                InMemoryRandomAccessStream pixelras = new InMemoryRandomAccessStream();
                BitmapEncoder pixelencoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, pixelras)
                BitmapTransform transform = new BitmapTransform();
                transform.InterpolationMode = BitmapInterpolationMode.Fant;
                transform.ScaledHeight = 400;
                transform.ScaledWidth = 400;
                var provider = await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8,
                    BitmapAlphaMode.Ignore,
                    transform,
                    ExifOrientationMode.RespectExifOrientation,
                    ColorManagementMode.DoNotColorManage);
                var pixels = provider.DetachPixelData();
                pixelencoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, 400,
                    400, decoder.DpiX, decoder.DpiY, pixels);
                try
                {
                    await pixelencoder.FlushAsync();
                    bmp.SetSource(pixelras);
                }
                catch
                {

                }
            }
        }                    
        img.Source = bmp;                  
    }
}
Grace Feng
  • 16,564
  • 2
  • 22
  • 45
  • Thanks a lot! As soon as I can test it, I'll give it a try and accept your answer – Florian Moser Jul 28 '16 at 12:58
  • Unfortunately this did not solve the problem. The `GetPixelDataAsync` and `SetPixelData` seem to work fine, but the `pixelencoder.FlushAsync();` still throws an `ArgumentException`. I've added the link to an image which causes this issue to my question. – Florian Moser Jul 31 '16 at 13:21
  • 1
    @FlorianMoser, sorry for the late responding, I just test my first block of code with your provided image, I can't reproduce the problem? From your second link I found that you downloaded that image, is that possible with downloading? I didn't test downloading the image then transform it, I just put your image in the picture lib and use FileOpenPicker to select this image, it works fine. My os version is 10586. – Grace Feng Aug 02 '16 at 07:31
  • I'm developing on a Technical Preview build (14393.5), and building for min & max version 10586. The issue still happens on my machine, I've added more unit tests to confirm this. I think the only difference between your example and my code are the streams used, maybe there is a problem there, I'm gonna try some more as soon as I have the time. – Florian Moser Aug 02 '16 at 13:00
  • I can not reproduce this bug anmore – Florian Moser Mar 29 '17 at 09:37