SkiaSharp: [BUG] Xamarin : Broken Bitmap.Encode on real devices

Description

The image is darkened when encoded by SkiaSharp. This behavior occurs on physicals devices, but not on :

  • Android emulator
  • iOS emulator

So I want to be able to Decode an image and Encode it with SkiaSharp without changing the color.

Version of Skiasharp: 2.88.6 Last Known Good Version of SkiaSharp: 2.88.5

Code

Link to a reproduction project repository: https://github.com/NovaKs68/bug-skiasharp

The following two code blocks modify the image color:

using (var stream = File.OpenRead(imagePath))
{
    SKImage img = SKImage.FromEncodedData(stream);
    SKBitmap bitmap = SKBitmap.FromImage(img);

    using (MemoryStream memStream = new MemoryStream())
    using (SKManagedWStream wstream = new SKManagedWStream(memStream))
    {
        bitmap.Encode(wstream, SKEncodedImageFormat.Jpeg, 100);
        imageCompressedView.Source = ImageSource.FromStream(() => new MemoryStream(memStream.ToArray()));
    }
}
var codec = SKCodec.Create(imagePath);
var skImage = SKBitmap.Decode(codec);

using (MemoryStream memStream = new MemoryStream())
using (SKManagedWStream wstream = new SKManagedWStream(memStream))
{
    skImage.Encode(wstream, SKEncodedImageFormat.Jpeg, 100);
    imageCompressedView.Source = ImageSource.FromStream(() => new MemoryStream(memStream.ToArray()));
}

Expected Behavior

The result image should be similar to the input image.

Actual Behavior

The result image is darker than the input image (see screenshots).

Version of SkiaSharp

Other (Please indicate in the description)

Last Known Good Version of SkiaSharp

Other (Please indicate in the description)

IDE / Editor

Visual Studio (Windows)

Platform / Operating System

Android, iOS

Platform / Operating System Version

  • Android 11
  • iOS 17.0.3

Devices

  • Xiaomi Redmi 11 Pro
  • IPhone 12 Pro Max

Relevant Screenshots

On iOS: MicrosoftTeams-image (18)

On Android: 2023-10-10_11-49-33

Relevant Log Output

No response

Code of Conduct

  • I agree to follow this project’s Code of Conduct

About this issue

  • Original URL
  • State: open
  • Created 9 months ago
  • Reactions: 6
  • Comments: 28

Most upvoted comments

@mattleibow i’m not sure what your involvement is in this but if you could take a look at this critical issue i’m sure we’d all appreciate it!

I experienced the exact same thing and had to roll back to a previous version (iOS and Android / MAUI).

With the release of .net-8.0, which is meant to be the saving graces for MAUI - and SkiaSharp is pretty closely linked. Maybe the .net-8 release is just taking major priority over everything else?

It is a little concerning…

I can contribute to the issue: we’re using 2.88.6

Original image: original image

Encode on windows (works): win-full-size-only-redrawn

Encode on android (same result on ios; broken): andr-full-size-only-redrawn

Using https://www.guiffy.com/Binary-Diff-Tool.html I found a difference in a small amount of bytes: diff

To my analysis, the broken image is shifted to the right and having an artifact at the top left. Sometimes the image is darkened, brightened, pink, purple, cyan… just a broken calculation of the bits based on the bytes different. image

That’s the code I used to resize the image:

  private static Stream resizeImage(Stream sourceImageStream, SKEncodedImageFormat imageFormat, int quality)
  {
    // make sure the image stream is readable from the start
    try { sourceImageStream.Position = 0; } catch (NotSupportedException) { }

    using var inputStream = new SKManagedStream(sourceImageStream);
    using var original = SKImage.FromEncodedData(inputStream);

    var imageWidth = original.Width;
    var imageHeight = original.Height;

    var useWidth = imageWidth;
    var useHeight = imageHeight;

    var info = new SKImageInfo(useWidth, useHeight, original.ColorType, original.AlphaType, original.ColorSpace);
    using var surface = SKSurface.Create(info);
    surface.Canvas.Clear();
	
    surface.Canvas.DrawImage(original,
      SKRect.Create(0, 0, useWidth, useHeight)
    );
    surface.Canvas.Flush();

    using var snapshot = surface.Snapshot();

    using var data = snapshot.Encode(imageFormat, quality);
    var resizedContent = data.ToArray();

    var resizedContentStream = new MemoryStream(resizedContent);

    return resizedContentStream;
  }

I’m sure the code could be shorter (as others posted) but I wanted to give you a way to reproduce the exact same bytes.

I really hope that helps!

From my observations:

  • loading PNG image and saving as JPEG: ❌ broken
  • loading JPEG image and saving as PNG: ✅ working
  • generating image and saving as JPEG: ❌ broken
  • generating image and saving as PNG: ✅ working

Tested on: MacBook Pro 16 M2 Max ARM64, Sonoma 14.1.1

It’s exactly the same for us, we did a downgrade to the previous version but since there’s a high-severity vulnerability (https://github.com/advisories/GHSA-j7hp-h8jx-5ppr), it would be really nice to have a patch for that…

Hello @IainS1986 , unfortunately the images uploaded to the test project are in JPG and RGBA format.

JPGs don’t support transparency, so they will be in RGB format.

Might confirm a bit more that it’s JPG/RGB images specifically…

Oh wow, I just spent the last hour pulling my hair out over this same issue trying to figure out if I was doing something wrong. Glad to know I’m not the only one. I only get this bug in iOS.

Thanks for stopping me from Googling endlessly searching for solutions. 👍

Anyone who can, please fix.

Echoing what everyone else has said, we are experiencing a similar issue on our Xamarin.iOS project, except rather than just being “darker,” the images seem to either be completely black or look really corrupt. Just a couple examples of what some photos look like:

0991f6c7-d588-471b-8b8f-0b03c1940a03 a7512b50-835b-4d9f-8edf-e2c367194879

Photos look normal when running on UWP and in the simulator, but when deployed to a physical iOS device they are corrupted like this. Downgrading our Xamarin.iOS project to SkiaSharp version 2.8.8.3 fixed this issue for us. We are going with this solution for now because this concerns a critical part of our application, but since there is a major vulnerability in this package, a fix is very much needed.