• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1
2---
3title: "The Raster Tragedy in Skia"
4linkTitle: "The Raster Tragedy in Skia"
5
6---
7
8
9This is an extension of [The Raster Tragedy at Low-Resolution Revisited](http://rastertragedy.com)
10as it applies to Skia. The Raster Tragedy describes a number of issues with typeface rasterization
11with a particular emphasis on proper hinting to overcome these issues. Since not all fonts
12are nicely hinted and sometimes hinting is not desired, there are additional hacks which may
13be applied. Generally, one wants to hint purely informational text laid out for a particular
14device, but not hint text which is art. Unless, of course, the hinting is part of the art like
15Shift_JIS art.
16
17
18
19The Gamma Hack
20--------------
21
22First, one should be aware of transfer functions (of which 'gamma' is an
23example). A good introduction can be had at [What Every Coder Should Know About
24Gamma](https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/).
25
26In Skia, all color sources are converted into the destination color space and the blending is done
27in the destination color space by applying the linear blend function. Skia does not convert into
28a linear space, apply the linear blend, and convert back to the encoded space. If the destination
29color space does not have a linear encoding this will lead to 'incorrect' blending. The idea is
30that there are essentially two kinds of users of Skia. First there are existing systems which
31are already using a non-linear encoding with a linear blend function. While the blend isn't
32correct, these users generally don't want anything to change due to expectations. Second there
33are those who want everything done correctly and they are willing to pay for a linearly encoded
34destination in which the linear blend function is correct.
35
36For bi-level glyph rendering a pixel is either covered or not, so there are no coverage blending
37issues.
38
39For regular full pixel partial coverage (anti-aliased) glyph rendering the user may or may not
40want correct linear blending. In most non-linear encodings, using the linear blend function
41tends to make black on white look slightly heavier, using the pixel grid as a kind of contrast
42and optical sizing enhancement. It does the opposite for white on black, often making such
43glyphs a bit under-covered. However, this fights the common issue of blooming where light on
44dark on many displays tends to appear thicker than dark on light. (The black not being fully
45black also contributes.) If the pixels are small enough and there is proper optical sizing and
46perhaps anti-aliased drop out control (these latter two achieved either manually with proper
47font selection or 'opsz', automatically, or through hinting) then correct linear blending tends
48to look great. Otherwise black on white text tends to (correctly) get really anemic looking at
49small sizes. So correct blending of glyph masks here should be left up to the user of Skia. If
50they're really sophisticated and already tackled these issues then they may want linear blending
51of the glyphs for best effect. Otherwise the glyphs should just keep looking like they used to
52look due to expectations.
53
54For subpixel partial coverage (subpixel anti-aliased) glyph masks linear blending in a
55linear encoding is more or less required to avoid color fringing effects. The intensity of
56the subpixels is being directly exploited so needs to be carefully controlled. The subpixels
57tend to alleviate the issues with no full coverage (though still problematic if blitting text
58in one of the display's primaries). One will still want optical sizing since the glyphs will
59still look somewhat too light when scaled down linearly.
60
61So, if subpixel anti-aliased glyph masks (and sometimes full pixel anti-aliased glyph masks)
62need a correct blit how are they to be used with non-linearly encoded destinations?
63
64One possible solution is to special case these blits. If blitting on the CPU it's often fast and
65close enough to take the square root of the source and destination values, do the linear blend
66function, then square the result (approximating the destination encoding as if its transfer
67function is square). Many GPUs have a mode where they can blend in sRGB, though unfortunately
68this generally applies to the whole framebuffer, not just individual draws. For various reasons,
69Skia has avoided special casing these blends.
70
71What Skia currently does is the gamma hack. When creating the glyph mask one usually knows
72the approximate color which is going to be drawn, the transfer function, and that a linear
73source-over blend is going to be used. The destination color is then guessed to be a contrasting
74color (if there isn't any contrast the drawing won't be able to be seen anyway) so assume that the
75destination color will be the perceptually opposite color in the destination color space. One can
76now determine the desired value by converting the perceptual source and guessed destination into
77a linear encoding, do the linear source-over blend, and convert to the destination encoding. The
78coverage is then adjusted so that the result of a linear source-over blend on the non-linear
79encoded values will be as close as possible to this desired value.
80
81This works, but makes many assumptions. The first is the guess at the destination. A perceptual
82middle gray could equally well (or poorly) contrast with black or white so the best guess
83is drawing it on top of itself. Subpixel anti-aliased glyph masks drawn with this guess will
84be drawn without any adjustment at all, leaving them color fringy. On macOS Skia tweaks the
85destination guess to reduce the correction when using bright sources. This helps reducing
86'blooming' issues with light on dark (aka dark mode) and better matches how CoreText seems
87to do blending. The second is that a src-over blend is assumed, but users generally aren't as
88discriminating of the results of other blends.
89
90The gamma hack works best with subpixel anti-aliasing since the adjustment can be made per-channel
91instead of full pixel. If this hack is applied to full pixel anti-aliased masks everything is
92essentially being done in the nearest gray instead of the nearest color (since there is only
93one channel through which to communicate), leading to poor results. Most users will not want
94the gamma hack applied to full pixel anti-aliased glyphs.
95
96Since the gamma hack is logically part of the blend, it must always be the very last adjustment
97made to the coverage mask before being used by the blitter. The inputs are the destination
98transfer function and the current color (in the destination color space). In Skia these come
99from the color space on the SkSurface and the color on the SkPaint.
100
101
102Optical Sizing Hack
103-------------------
104
105In metal type a type designer will draw out on paper the design for the faces of the final
106sorts. Often there would be different drawings for different target sizes. Sometimes these
107different sizes look quite different (like Display and Text faces of the same family). Then a
108punch cutter takes these drawings and creates a piece of metal called a punch which can stamp
109out these faces. The punch is used to create a negative in a softer piece of metal forming a
110strike. This strike is made the correct width and called a matrix. This matrix is used as a mold
111to cast individual sorts. The sorts are collected into a box called a type case. A typesetter
112would take sorts from a type case and set them into a form. The printer would then ink the
113sorts in the form and press the paper. (Note that the terms 'typeface' and 'font' aren't used
114in this description, there is a lot of disagreement on what they apply to.)
115
116Every step of this process is now automated in some way. Unfortunately, knowledge embedded in
117the manual process has not always been replicated in the automation. This can be for a wide
118variety of reasons, such as being overlooked or being difficult to emulate. One of these areas
119is the art of optical sizing and managing thin features.
120
121In general smaller type should be relatively heavier in weight than would be expected if taking
122a larger size and linearly scaling it down. The type designer will draw out the faces with this
123in mind, potentially with an eye toward how it would vary with size and with potential need
124for ink traps. The punch cutter would then cut the punches at a given size with this in mind,
125adjusting until the soot proofs looked good. There may even be slight adjustments to the matrix
126itself if something seemed off. The typesetter would often know which specific cases contained
127slightly heavier or lighter sorts. The printer would then adjust the ink and pressure to get
128a good looking print.
129
130Popular digital font formats didn't really support this until recently with the variable font
131'opsz' axis. The presence of an 'opsz' axis is like the type designer giving really good
132instructions about how the faces should look at various sizes. However, not all fonts have or
133will have a 'opsz' axis. Since we don't always have an 'opsz' what can be done to prevent small
134glyphs from looking all washed out?
135
136One way the type designer could influence the optical size is through hinting. Any manual
137hinting is done by someone looking at the result at small sizes. Tweaks are made until it
138'looks good'. This often unconsciously bakes in optical sizing. Even autohinters eventually
139end up emulating this, generally making the text more contrasty and somewhat heavier at small
140pixel sizes (this depends on the target pixel size not the nominal requested size).
141
142An alternate way the designer could provide optical sized fonts is using multiple font files
143for different optical sizes and expect the user to select the right one (like Display and
144Text variants). In theory the right one might also be selected automatically based on some
145criteria. Switching font files because of nominal size changes may seem drastic, but this is
146how the system font on macOS worked for a while.
147
148One automatic way to do optical sizing is to artificially embolden the outline itself at small
149nominal sizes. This is the approach taken by CoreText. This is a lot like the dreaded fake-bold,
150but doesn't have the issue of the automatic fake bolding being heavier than ultra bold, since
151this emboldening is applied uniformally based on requested nominal size.
152
153Another automatic way to do optical sizing is to over-cover all the edges when doing
154anti-aliasing. The pixels are a fixed physical size so affect small features more than large
155features. Skia currently has rudimentary support for this in its 'contrast hack' which is
156described more later.
157
158As a note on optical sizing, this is one place where the nominal or optical size of the font is
159treated differently from the final pixel size of the font. A SkCanvas may have a scale transform
160active. Any hinting will be done at the final pixel size (the font size mapped through the
161current transformation matrix), but the optical size is not affected by the transform since
162it's actually a parameter for the SkTypeface (and maybe the SkFont).
163
164
165The Contrast Hack
166-----------------
167
168Consider the example of [a pixel wide stroke at half-pixel
169boundaries](http://rastertragedy.com/RTRCh3.htm#Sec1). As stated in section 3.1.3 "if we were
170to choose the stroke positions to render the most faithful proportions and advance width, we
171may have to compromise the rendering contrast." If the stroke lands on a pixel boundary all
172is well. If the stoke lands at a half pixel boundary and correct linear blending is used then
173the same number of photons are reaching the eye, but the reduction in photons is spread out
174over twice the area. If the pixels were small enough then this wouldn't be an issue. Back of
175the envelope suggests an 8K 20inch desktop monitor or ~400ppi being the minimum. Note that RGB
176subpixel anti-aliasing brings a 100dpi display up to 300dpi, which is close, but still a bit
177short. Exploiting the RGB subpixels also doesn't really increase the resolution as much when
178getting close to the RGB primaries for the fill color.
179
180One way to try to compensate for this is to cover some of these partially covered pixels more
181until it visually looks better. Note that this depends on a lot of factors like the pixel density
182of the display, the visual acuity of the user, the distance of the user from the display, and the
183user's sensitivity to the potential variations in stem darkness which may result. Automatically
184taking all these factors into account would be quite difficult. The correct function is generally
185determined by having the user look at the result of applying various amounts of extra coverage
186and having them to pick a setting that looks the least bad to them.
187
188This specific form of over-covering is a form of drop out control for anti-aliasing and could
189be implemented in a similar way, detecting when a stem comes on in one pixel and goes out in
190the next and mark that for additional coverage. At raster time a cruder approximation could be
191made by doing a pass in each of the horizontal and vertical directions and finding runs of more
192than one non-fully-covered pixel and increasing their coverage.
193
194If instead of doing these computationally expensive passes all coverage is boosted then in
195addition to the smeared stems the entire outside edge of the glyph will also be bolded. Making
196these outside edges heavier is a crude approximation of outsetting the initial path in a
197rather complicated way and amounts to an optical sizing tweak. Just as hinting can be used to
198approximate optical sizing if the user's perception of the pixel sizes is known in advance,
199this is a pixel level tweak tied to a specific user and display combination.
200
201Much like the gamma hack can be modified to reduce the correction for light on dark to
202fight blooming, the contrast hack can be reduced when the color being drawn is known to be
203light. Generally the contrast correction goes to zero as one approaches white.
204
205