Search code examples
c#visual-studio-2013windows-phone-8.1writeablebitmapwriteablebitmapex

WriteableBitmap renders PNG incorrectly


I am having trouble rendering PNGs that use Palette as "Color Type". Here is some simple code to reproduce the issue:

private async System.Threading.Tasks.Task Fetch()
{
    HttpClient httpClient = new HttpClient();
    Uri uri = new Uri("http://static.splashnology.com/articles/How-to-Optimize-PNG-and-JPEG-without-Quality-Loss/PNG-Palette.png");
    HttpResponseMessage response = await httpClient.GetAsync(uri);
    if (response.StatusCode == HttpStatusCode.Ok)
    {
        try
        {
            var content = await response.Content.ReadAsBufferAsync();
            WriteableBitmap image = await BitmapFactory.New(1, 1).FromStream(content.AsStream());
            Rect destination = new Rect(0, 0, image.PixelWidth, image.PixelHeight);
            Rect source = new Rect(0, 0, image.PixelWidth, image.PixelHeight);
            WriteableBitmap canvas = new WriteableBitmap(image.PixelWidth, image.PixelHeight);
            canvas.Blit(destination, image, source);
            RadarImage.Source = canvas;
        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine(e.Message);
            System.Diagnostics.Debug.WriteLine(e.StackTrace);
        }
    }
}

If I run that code using Windows Phone 8.1, the image appears using wrong colors. If I do the same test using a PNG that is using RGB as "Color Type", then everything is works fine.

I have looked at Codeplex forum and haven't seen any post related to that. I have reported it as an issue although it might be related to the way I'm rendering it. Is there any mistake in the way I'm using WriteableBitmap that could cause the wrong rendering?


UPDATE

According to this discussion https://writeablebitmapex.codeplex.com/discussions/274445 the issue is related to an unexpected order of the bytes. These comments are from one and a half years ago, so I think there should be a proper fix somewhere...

The image that is rendered incorrectly is in the code above.

This one, using the same code, is rendered correctly. http://www.queness.com/resources/images/png/apple_ex.png

The only difference between these two images is the "Color Type" property. The one that fails is set to "Palette" and the one rendered correctly is set to "RGB Alpha".

Thanks! Carlos.


Solution

  • The problem appears to be in the FromStream extension, which seems to translate the paletted png to RGBA. As you note, WriteableBitmap wants BGRA. I suspect FromStream passes non-palette pngs' pixels unswizzled. This lets the apple start and finish as BGRA while the monkey ends up RGBA and draws with red and blue reversed.

    You can bypass this problem by skipping FromStream and using a BitmapDecoder so you specify the format you want it to decode into:

    // Read the retrieved image's bytes and write them into an IRandomAccessStream
    IBuffer buffer = await response.Content.ReadAsBufferAsync();
    var randomAccessStream = new InMemoryRandomAccessStream();
    await randomAccessStream.WriteAsync(buffer);
    
    // Decode the downloaded image as Bgra8 with premultiplied alpha
    // GetPixelDataAsync lets us pass in the desired format and it'll do the magic to translate as it decodes
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(randomAccessStream);
    var pixelData = await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, new BitmapTransform(), ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);
    
    // Get the decoded bytes
    byte[] imageData = pixelData.DetachPixelData();
    
    // And stick them in a WriteableBitmap
    WriteableBitmap image = new WriteableBitmap((int)decoder.PixelWidth,(int) decoder.PixelHeight);
    Stream pixelStream = image.PixelBuffer.AsStream();
    
    pixelStream.Seek(0, SeekOrigin.Begin);
    pixelStream.Write(imageData, 0, imageData.Length);
    
    // And stick it in an Image so we can see it.
    RadarImage.Source = image;