1Color Correct Skia 2================== 3 4Why is Skia Color Correct? 5-------------------------- 6 7A color space is a **gamut** and a **transfer function**. 8 9Gamut refers to the **available range of colors** in an image or on a display device. Being 10gamut correct means that we will display colors as the designer intended and consistently across 11display devices. A common problem with new “wide gamut” devices and uncorrected colors is 12illustrated below. 13 14Device Dependent Color (Wrong) 15 16<img src='gamut_wrong.png'> 17 18Gamut Corrected Color 19 20<img src='gamut_correct.png'> 21 22Transfer function refers to **a non-linear encoding of pixel values**. A common transfer function 23is shown below. 24 25<img src='transfer_fn.png'> 26 27If we ignore the transfer function and treat non-linear values as if they are linear (when 28filtering, blending, anti-aliasing, multiplying), everything gets “too dark”. 29 30For example, we should see yellow (not brown) as the average of red and green light. 31 32Ignore Transfer Function 33 34<img src='gradient_wrong.png'> 35 36Apply Transfer Function 37 38<img src='gradient_correct.png'> 39 40Also, we should maintain fine detail when anti-aliasing (or downscaling). 41 42Ignore Transfer Function 43 44<img src='detail_wrong.png'> 45 46Apply Transfer Function 47 48<img src='detail_correct.png'> 49 50Skia Architecture for Color Correctness 51--------------------------------------- 52 53<img src='architecture.png'> 54 55The major stages of the Skia drawing pipeline (premultiplication, filtering, blending) all assume 56linear inputs and linear outputs. Also, because they are linear operations, they are 57interchangeable. 58 59The gamut transform is a new operation (3x3 matrix) in the pipeline, but with similar properties: 60it is a linear operation with linear inputs and linear outputs. 61 62The important shift in logic from the legacy pipeline is that we actually apply the transfer 63function to transform the pixels to linear values before performing the linear operations. 64 65The most common transfer function, sRGB, is actually free on GPU! GPU hardware can transform sRGB 66to linear on reads and linear to sRGB on writes. 67 68Best Practices for Color Correct Skia 69------------------------------------- 70 71In order to perform color correct rendering, Skia needs to know the **SkColorSpace** of the content 72that you draw and the **SkColorSpace** of the surface that you draw to. There are useful factories 73to make color spaces. 74 75<!--?prettify lang=cc?--> 76 77 // Really common color spaces 78 sk_sp<SkColorSpace> MakeSRGB(); 79 sk_sp<SkColorSpace> MakeSRGBLinear(); 80 81 // Choose a common gamut and a common transfer function 82 sk_sp<SkColorSpace> MakeRGB(RenderTargetGamma, Gamut); 83 84 // Create a color space from an ICC profile 85 sk_sp<SkColorSpace> MakeICC(); 86 87Starting with **sources** (the things that you draw), there are a number of ways to make sure 88that they are tagged with a color space. 89 90**SkColor** (stored on **SkPaint**) is assumed to be in the sRGB color space - meaning that it 91is in the sRGB gamut and encoded with the sRGB transfer function. 92 93**SkShaders** (also stored on **SkPaint**) can be used to create more complex colors. Color and 94gradient shaders typically accept **SkColor4f** (float colors). These high precision colors 95can be in any gamut, but must have a linear transfer function. 96 97<!--?prettify lang=cc?--> 98 99 // Create a high precision color in a particular color space 100 sk_sp<SkShader> MakeColorShader(const SkColor4f&, sk_sp<SkColorSpace>); 101 102 // Create a gradient shader in a particular color space 103 sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const SkColor4f colors[2], 104 sk_sp<SkColorSpace>, ...); 105 106 // Many more variations of shaders... 107 // Remember that SkColor is always assumed to be sRGB as a convenience 108 109**SkImage** is the preferred representation for image sources. It is easy to create **SkImages** 110 that are tagged with color spaces. 111 112<!--?prettify lang=cc?--> 113 114 // Create an image from encoded data (jpeg, png, etc.) 115 // Will be tagged with the color space of the encoded data 116 sk_sp<SkImage> MakeFromEncoded(sk_sp<SkData> encoded); 117 118 // Create an image from a texture in a particular color space 119 sk_sp<SkImage> MakeFromTexture(GrContext*, const GrBackendTexture&, 120 GrSurfaceOrigin, SkAlphaType, sk_sp<SkColorSpace>, 121 ...); 122 123**SkBitmap** is another (not preferred) representation for image sources. Be careful to not forget 124the color space. 125 126<!--?prettify lang=cc?--> 127 128 SkBitmap bitmap; 129 bitmap.allocN32Pixels(); // Bad: What is the color space? 130 131 SkBitmap bitmap; 132 SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); 133 bitmap.allocPixels(info); // Bad: N32 is shorthand for 8888, no color space 134 135 SkBitmap bitmap; 136 SkImageInfo info = SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType); 137 bitmap.allocPixels(info); // Good: S32 is shorthand for 8888, sRGB 138 139**SkImageInfo** is a useful struct for providing information about pixel buffers. Remember to use 140the color correct variants. 141 142<!--?prettify lang=cc?--> 143 144 // sRGB, 8888 145 SkImageInfo MakeS32(int width, int height, SkAlphaType); 146 147 // Create an SkImageInfo in a particular color space 148 SkImageInfo Make(int width, int height, SkColorType, SkAlphaType, 149 sk_sp<SkColorSpace>); 150 151Moving to **destinations** (the surfaces that you draw to), there are also constructors that allow 152them to be tagged with color spaces. 153 154<!--?prettify lang=cc?--> 155 156 // Raster backed: Make sure |info| has a non-null color space 157 sk_sp<SkSurface> MakeRaster(const SkImageInfo& info); 158 159 // Gpu backed: Make sure |info| has a non-null color space 160 sk_sp<SkSurface> SkSurface::MakeRenderTarget(GrContext, SkBudgeted, 161 const SkImageInfo& info); 162 163Opting In To Color Correct Skia 164------------------------------- 165 166By itself, **adding a color space tag to a source will not change draw behavior**. In fact, 167tagging sources with color spaces is always a best practice, regardless of whether or not we want 168Skia's color correct behavior. 169 170Adding a color space tag to the **destination is the trigger that turns on Skia color correct 171behavior**. 172 173Drawing a source without a color space to a destination with a color space is undefined. Skia 174cannot know how to draw without knowing the color space of the source. 175 176<style scoped><!-- 177#colortable {border-collapse:collapse;} 178#colortable tr th, #colortable tr td {border:#888888 2px solid;padding: 5px;} 179--></style> 180<table id="colortable"> 181<tr><th>Source SkColorSpace</th> <th>Destination SkColorSpace</th> <th>Behavior</th></tr> 182<tr><td>Non-null</td> <td>Non-null</td> <td>Color Correct Skia</td></tr> 183<tr><td>Null</td> <td>Non-null</td> <td>Undefined</td></tr> 184<tr><td>Non-null</td> <td>Null</td> <td>Legacy Skia</td></tr> 185<tr><td>Null</td> <td>Null</td> <td>Legacy Skia</td></tr> 186</table> 187 188It is possible to create **an object that is both a source and destination**, if Skia will both 189draw into it and then draw it somewhere else. The same rules from above still apply, but it is 190subtle that the color space tag could have an effect (or no effect) depending on how the object is 191used. 192 193