• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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** of a particular in an image or on a display
10device.  Being gamut correct means that we will display colors as the designer intended and
11consistently across display devices.  A common problem with new “wide gamut” devices and
12uncorrected colors is illustrated 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 draw 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	// Caution: There are versions of this constructor that do not take an
120	//          SkColorSpace.  But without an SkColorSpace, Skia does not have
121	//          enough information to draw correctly.
122	sk_sp<SkImage> MakeFromTexture(GrContext*, const GrBackendTextureDesc&,
123	                               SkAlphaType, sk_sp<SkColorSpace>, ...);
124
125**SkBitmap** is another (not preferred) representation for image sources.  Be careful to not forget
126the color space.
127
128<!--?prettify lang=cc?-->
129
130	SkBitmap bitmap;
131	bitmap.allocN32Pixels(); // Bad: What is the color space?
132
133	SkBitmap bitmap;
134	SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
135	bitmap.allocPixels(info); // Bad: N32 is shorthand for 8888, no color space
136
137	SkBitmap bitmap;
138	SkImageInfo info = SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType);
139	bitmap.allocPixels(info); // Good: S32 is shorthand for 8888, sRGB
140
141**SkImageInfo** is a useful struct for providing information about pixel buffers.  Remember to use
142the color correct variants.
143
144<!--?prettify lang=cc?-->
145
146	// sRGB, 8888
147	SkImageInfo MakeS32(int width, int height, SkAlphaType);
148
149	// Create an SkImageInfo in a particular color space
150	SkImageInfo Make(int width, int height, SkColorType, SkAlphaType,
151	                 sk_sp<SkColorSpace>);
152
153Moving to **destinations** (the surfaces that you draw to), there are also constructors that allow
154them to be tagged with color spaces.
155
156<!--?prettify lang=cc?-->
157
158	// Raster backed: Make sure |info| has a non-null color space
159	sk_sp<SkSurface> MakeRaster(const SkImageInfo& info);
160
161	// Gpu backed: Make sure |info| has a non-null color space
162	sk_sp<SkSurface> SkSurface::MakeRenderTarget(GrContext, SkBudgeted,
163	                                             const SkImageInfo& info);
164
165Opting In To Color Correct Skia
166-------------------------------
167
168By itself, **adding a color space tag to a source will not change draw behavior**.  In fact,
169tagging sources with color spaces is always a best practice, regardless of whether we want Skia’s
170color correct behavior.
171
172Adding a color space tag to the **destination is the trigger that turns on Skia color correct
173behavior**.
174
175Drawing a source without a color space to a destination with a color space is undefined.  Skia
176cannot know how to draw without knowing the color space of the source.
177
178<style scoped><!--
179#colortable {border-collapse:collapse;}
180#colortable tr th, #colortable tr td {border:#888888 2px solid;padding: 5px;}
181--></style>
182<table id="colortable">
183<tr><th>Source SkColorSpace</th> <th>Destination SkColorSpace</th>  <th>Behavior</th></tr>
184<tr><td>Non-null</td>            <td>Non-null</td>                  <td>Color Correct Skia</td></tr>
185<tr><td>Null</td>                <td>Non-null</td>                  <td>Undefined</td></tr>
186<tr><td>Non-null</td>            <td>Null</td>                      <td>Legacy Skia</td></tr>
187<tr><td>Null</td>                <td>Null</td>                      <td>Legacy Skia</td></tr>
188</table>
189
190It is possible to create **an object that is both a source and destination**, if Skia will both
191draw into it and then draw it somewhere else.  The same rules from above still apply, but it is
192subtle that the color space tag could have an effect (or no effect) depending on how the object is
193used.