mono / SkiaSharp

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
MIT License
4.49k stars 538 forks source link

SKCanvasView default colorspace #732

Open deakjahn opened 5 years ago

deakjahn commented 5 years ago

It looks like SKCanvasView always get created using an SKImageInfo that lacks colorspace specification. Wouldn't there be a way to specify the color space we want our canvases to be created with? Converting images allows us to specify color space at both ends but drawing onto the canvas does not, so Skia's color corrected behavior doesn't kick in at all.

VS bug #753284

deakjahn commented 5 years ago

Just for illustration, in SkiaSharp.Views.Android\SKCanvasView.cs, line 50:

info = new SKImageInfo(0, 0, SKColorType.Rgba8888, SKAlphaType.Premul);

There is no colorspace definition at the end. You can't simply assume that whatever default there is is always the one the user actually wants.

mattleibow commented 5 years ago

This is a good point. Will a property (and maybe a constructor overload) that provides a place to set the desired colorspace be useful?

How would you want to set/specify a colorspace in your current usage? Also, if you were to specify a colorsapce, what other options will you need for the view to create a correct surface with that colorspace?

deakjahn commented 5 years ago

A constructor could be OK with UI created in code but doesn't help much with XAML. Isn't a property too late for the initialization? If it can be solved, that would be just fine. You could have an enum with Default and all the ready made spaces but I don't know how you would supply an ICC file if need be.

Perhaps if we knew if the passed SKImageInfo could be replaced later on or had to be absolutely ready and final when the surface is created.

mattleibow commented 5 years ago

A property might work because I technically only guarantee the surface during the draw method. If you change the colorspace, all we have to do is nuke the surface and invalidate. Then the draw method will re-create the surface with the new colorspace.

I probably will have to create a system like with the Brush of UWP/WPF because some of the colorspaces have additional parameters:

class abstract SKBaseColorSpace {
    public SKColorSpace ColorSpace { get; }
}
class SKSrgbColorSpace : SKBaseColorSpace {
    public bool IsLinear { get; set; }
}
class SKIccColorSpace : SKBaseColorSpace {
    public ImageSource Icc  { get; set; } // to byte[]
}
class SKRgbColorSpace : SKBaseColorSpace {
    // one of these
    public SKNamedGamma Gamma { get; set; } // enum
    public SKColorSpaceTransferFn Gamma { get; set; } // struct

    // with one of these
    public SKColorSpaceGamut Gamut { get; set; } // enum
    public SKMatrix44 Gamut { get; set; } // struct
}
<SKCanvasView>
        <!-- sRGB -->
        <SKSrgbColorSpace IsLinear="true" />

        <!-- ICC -->
        <SKIccColorSpace Icc="my_colors.icc" />

        <!-- RGB -->
        <SKRgbColorSpace Gamma="TwoDotTwoCurve" Gamut="AdobeRgb" />
        <SKRgbColorSpace Gamma="1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0" Gamut="0.4360747, 0.3850649, 0.1430804, 0.2225045, 0.7168786, 0.0606169, 0.0139322, 0.0971045, 0.7141733" />
    <SKCanvasView.ColorSpace>
</SKCanvasView>

This may be harder to do because not everything is great in XAML. Also, this is drawing logic, so there can be no delays.

With the ICC, this file may take a second to load, and we can't have the drawing freezing. So, do we just go with nothing and have bad colors for a few frames? Do we force this to be loaded synchronously and then set - basically this is a byte[] property?

With the RGB, there is a a combo of two properties, each of which can either be an enum value or a matrix-like format.

deakjahn commented 5 years ago

This might be more complicated than really necessary, although elegant. I could totally accept that spaces that need construction (full CreateRgb and CreateIcc) are only available through a constructor. Even if you use XAML predominantly, it's not that big a deal to instantiate one view yourself in specific cases. Then you'd only need to provide an enum for the simple cases:

Default, Srgb, SrgbLinear, AdobeRgb, etc

But you could include specific CreateRgb cases as well, this might be where AdobeRgb comes from. If you only allow the CreateRgb(SKColorSpaceRenderTargetGamma.Linear, SKColorSpaceGamut, SKColorSpaceFlags) form, then you have a still affordable solution like:

<SKCanvasView ColorSpace="Default" />
<SKCanvasView ColorSpace="Srgb" />
<SKCanvasView ColorSpace="AdobeRgb" />

Or, an alternative solution for the more complicated cases could be a static property that you need to fill in with the space before the canvas view gets created, practically in the constructor before InitializeComponent(). Then either coefficients or color profiles could be read whenever the user sees fit, during app startup, and simply provided as a ready SKColorSpace when necessary. And it also can be reused across the application, if necessary (eg. I have a list view where each cell has a canvas, it would be logical to use a single color space instance for them all).