Avalonia icon indicating copy to clipboard operation
Avalonia copied to clipboard

No way to specify ColorSpace when creating Bitmap/WriteableBitmap from buffer.

Open dbriard opened this issue 4 months ago • 4 comments

Is your feature request related to a problem? Please describe.

There is no way to specify a color space (sRGB, AdobeRGB, ProPhotoRGB, etc...) when creating an Avalonia Bitmap.

I run into an issue with an Image processing app I am working on. Basically when I load an Avalonia bitmap from a stream, the ColorSpace is read an used correctly (I suppose it is attached to SKBitmap or SKImage behind the scene).

However, as I modify pixels in my program, I need to recreate Bitmap from a buffer of pixels. The problem is that there is no way to specify the ColorSpace of the original bitmap when I create the new Bitmap. And so, as the new bitmap have no ColorSpace attached, the color looks wrong on the screen.

Describe the solution you'd like

I would like a way to set the colorspace when I create a Bitmap and WriteableBitmap.

Describe alternatives you've considered

Possible workarounds:

  1. Save (with SKBitmap and ColorSpace) and Load bitmap again (too slow for my needs)
  2. Use CustomDrawOperations to display the modified bitmap: need to look how WriteableBitmap work under the hood to reproduce the write pixel behavior.

dbriard avatar Feb 12 '24 18:02 dbriard

Wideranging color space support may be out of scope. However, you can still use SkiaSharp directly.

Assuming you have a pixel buffer:

  1. Use SKBitmap.InstallPixels to create a wrapper around your pixel buffer. This isn't a CPU memory copy.
  2. Render the SKBitmap yourself with ICustomDrawOperation (eg. https://github.com/stevemonaco/AvaloniaDemos/blob/master/SkiaBitmapAdapter/Views/SkiaBitmapView.axaml.cs) or use SKBitmapControl provided by the Avalonia.Controls.Skia package.

stevemonaco avatar Feb 12 '24 19:02 stevemonaco

@stevemonaco Thanks for your comment, I never tried InstallPixels and didn't know it was a wrapper. I will give a try.

I generally just create a SKBitmap where I modify the pixels directly with unsafe pointers, and render using DrawBitmap or create a SKImage from the bitmap before rendering, not sure what is the best method, but I will investigate.

In regards to Avalonia, may be an additional parameter byte[] profileBytes to the Bitmap and WriteableBitmap could be an easy way to deal with ColorProfile, especially as the rendering part is already working finewhen bitmap is created from a file.

I my case, I just need a sRGB profile when creating the WriteableBitmap instead of no profile (the display profile), as I finish my processing by converting to sRGB to show on screen.

dbriard avatar Feb 12 '24 21:02 dbriard

I think there was an idea to add more pixel formats to support srgb. Like this one https://github.com/AvaloniaUI/Avalonia/pull/9923

maxkatz6 avatar Feb 13 '24 01:02 maxkatz6

I manage to get the correct display using SKBitmap with sRGB color space in a CustomDrawOperation but that is not always convinient to use.

Until it become possible to specify a ColorSpace when creating an Avalonia bitmap with new Bitmap/WriteableBitmap. Could you consider to assign sRGB by default to Bitmaps created with new, instead of no color space at all?

Here an example of the problem:

The two larges images below are rendered using SKBitmap and CustomDrawOp: image

image

While the images below that I use in a photo browser are Avalonia Bitmap created from the large image using the Bitmap constructor that accept a buffer. (no color management :( ) image

As you can see the color is not the same. Of course I could also use my custom control in the photo browser (and I will), but as most of the time I want to use sRGB for thumbnails, having sRGB by default in Bitmap would be useful.

But ideally, a Avalonia ColorSpace class based on SKColorSpace would be awesome! ColorSpace could be created from ICCProfile (filename or bytes), from predefined statics, or manually like SKColorSpace, for example:

private static SKColorSpaceXyz ProPhotoColorSpaceXyz = new SKColorSpaceXyz(0.797668457f,
    0.135192871f,
    0.0313415527f,
    0.288040161f,
    0.711883545f,
    9.15527344E-05f,
    0.0f,
    0.0f,
    0.8249054f);

private static SKColorSpaceTransferFn ProPhotoColorSpaceTransferFn = new SKColorSpaceTransferFn(1.80078125f, 1, 0, 0, 0, 0, 0);

internal static SKColorSpace ProPhotoRGB = SKColorSpace.CreateRgb(ProPhotoColorSpaceTransferFn, ProPhotoColorSpaceXyz);

dbriard avatar Feb 17 '24 18:02 dbriard