1/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "src/gpu/text/GrTextContext.h"
9
10#include "include/core/SkGraphics.h"
11#include "include/gpu/GrContext.h"
12#include "include/private/SkTo.h"
13#include "src/core/SkDistanceFieldGen.h"
14#include "src/core/SkDraw.h"
15#include "src/core/SkDrawProcs.h"
16#include "src/core/SkGlyphRun.h"
17#include "src/core/SkMaskFilterBase.h"
18#include "src/core/SkPaintPriv.h"
19#include "src/gpu/GrCaps.h"
20#include "src/gpu/GrRecordingContextPriv.h"
21#include "src/gpu/SkGr.h"
22#include "src/gpu/ops/GrMeshDrawOp.h"
23#include "src/gpu/text/GrSDFMaskFilter.h"
24#include "src/gpu/text/GrTextBlobCache.h"
25
26// DF sizes and thresholds for usage of the small and medium sizes. For example, above
27// kSmallDFFontLimit we will use the medium size. The large size is used up until the size at
28// which we switch over to drawing as paths as controlled by Options.
29static const int kSmallDFFontSize = 32;
30static const int kSmallDFFontLimit = 32;
31static const int kMediumDFFontSize = 72;
32static const int kMediumDFFontLimit = 72;
33static const int kLargeDFFontSize = 162;
34#ifdef SK_BUILD_FOR_MAC
35static const int kLargeDFFontLimit = 162;
36static const int kExtraLargeDFFontSize = 256;
37#endif
38
39static const int kDefaultMinDistanceFieldFontSize = 18;
40#if defined(SK_BUILD_FOR_ANDROID)
41static const int kDefaultMaxDistanceFieldFontSize = 384;
42#elif defined(SK_BUILD_FOR_MAC)
43static const int kDefaultMaxDistanceFieldFontSize = kExtraLargeDFFontSize;
44#else
45static const int kDefaultMaxDistanceFieldFontSize = 2 * kLargeDFFontSize;
46#endif
47
48GrTextContext::GrTextContext(const Options& options) : fOptions(options) {
49 SanitizeOptions(&fOptions);
50}
51
52std::unique_ptr<GrTextContext> GrTextContext::Make(const Options& options) {
53 return std::unique_ptr<GrTextContext>(new GrTextContext(options));
54}
55
56SkColor GrTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd) {
57 SkColor canonicalColor = SkPaintPriv::ComputeLuminanceColor(paint);
58 if (lcd) {
59 // This is the correct computation, but there are tons of cases where LCD can be overridden.
60 // For now we just regenerate if any run in a textblob has LCD.
61 // TODO figure out where all of these overrides are and see if we can incorporate that logic
62 // at a higher level *OR* use sRGB
63 SkASSERT(false);
64 //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor);
65 } else {
66 // A8, though can have mixed BMP text but it shouldn't matter because BMP text won't have
67 // gamma corrected masks anyways, nor color
68 U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor),
69 SkColorGetG(canonicalColor),
70 SkColorGetB(canonicalColor));
71 // reduce to our finite number of bits
72 canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum));
73 }
74 return canonicalColor;
75}
76
77SkScalerContextFlags GrTextContext::ComputeScalerContextFlags(const GrColorInfo& colorInfo) {
78 // If we're doing linear blending, then we can disable the gamma hacks.
79 // Otherwise, leave them on. In either case, we still want the contrast boost:
80 // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
81 if (colorInfo.isLinearlyBlended()) {
82 return SkScalerContextFlags::kBoostContrast;
83 } else {
84 return SkScalerContextFlags::kFakeGammaAndBoostContrast;
85 }
86}
87
88void GrTextContext::SanitizeOptions(Options* options) {
89 if (options->fMaxDistanceFieldFontSize < 0.f) {
90 options->fMaxDistanceFieldFontSize = kDefaultMaxDistanceFieldFontSize;
91 }
92 if (options->fMinDistanceFieldFontSize < 0.f) {
93 options->fMinDistanceFieldFontSize = kDefaultMinDistanceFieldFontSize;
94 }
95}
96
97bool GrTextContext::CanDrawAsDistanceFields(const SkPaint& paint, const SkFont& font,
98 const SkMatrix& viewMatrix,
99 const SkSurfaceProps& props,
100 bool contextSupportsDistanceFieldText,
101 const Options& options) {
102 // mask filters modify alpha, which doesn't translate well to distance
103 if (paint.getMaskFilter() || !contextSupportsDistanceFieldText) {
104 return false;
105 }
106
107 // TODO: add some stroking support
108 if (paint.getStyle() != SkPaint::kFill_Style) {
109 return false;
110 }
111
112 if (viewMatrix.hasPerspective()) {
113 if (!options.fDistanceFieldVerticesAlwaysHaveW) {
114 return false;
115 }
116 } else {
117 SkScalar maxScale = viewMatrix.getMaxScale();
118 SkScalar scaledTextSize = maxScale * font.getSize();
119 // Hinted text looks far better at small resolutions
120 // Scaling up beyond 2x yields undesireable artifacts
121 if (scaledTextSize < options.fMinDistanceFieldFontSize ||
122 scaledTextSize > options.fMaxDistanceFieldFontSize) {
123 return false;
124 }
125
126 bool useDFT = props.isUseDeviceIndependentFonts();
127#if SK_FORCE_DISTANCE_FIELD_TEXT
128 useDFT = true;
129#endif
130
131 if (!useDFT && scaledTextSize < kLargeDFFontSize) {
132 return false;
133 }
134 }
135
136 return true;
137}
138
139SkScalar scaled_text_size(const SkScalar textSize, const SkMatrix& viewMatrix) {
140 SkScalar scaledTextSize = textSize;
141
142 if (viewMatrix.hasPerspective()) {
143 // for perspective, we simply force to the medium size
144 // TODO: compute a size based on approximate screen area
145 scaledTextSize = kMediumDFFontLimit;
146 } else {
147 SkScalar maxScale = viewMatrix.getMaxScale();
148 // if we have non-unity scale, we need to choose our base text size
149 // based on the SkPaint's text size multiplied by the max scale factor
150 // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)?
151 if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) {
152 scaledTextSize *= maxScale;
153 }
154 }
155
156 return scaledTextSize;
157}
158
159SkFont GrTextContext::InitDistanceFieldFont(const SkFont& font,
160 const SkMatrix& viewMatrix,
161 const Options& options,
162 SkScalar* textRatio) {
163 SkScalar textSize = font.getSize();
164 SkScalar scaledTextSize = scaled_text_size(textSize, viewMatrix);
165
166 SkFont dfFont{font};
167
168 if (scaledTextSize <= kSmallDFFontLimit) {
169 *textRatio = textSize / kSmallDFFontSize;
170 dfFont.setSize(SkIntToScalar(kSmallDFFontSize));
171 } else if (scaledTextSize <= kMediumDFFontLimit) {
172 *textRatio = textSize / kMediumDFFontSize;
173 dfFont.setSize(SkIntToScalar(kMediumDFFontSize));
174#ifdef SK_BUILD_FOR_MAC
175 } else if (scaledTextSize <= kLargeDFFontLimit) {
176 *textRatio = textSize / kLargeDFFontSize;
177 dfFont.setSize(SkIntToScalar(kLargeDFFontSize));
178 } else {
179 *textRatio = textSize / kExtraLargeDFFontSize;
180 dfFont.setSize(SkIntToScalar(kExtraLargeDFFontSize));
181 }
182#else
183 } else {
184 *textRatio = textSize / kLargeDFFontSize;
185 dfFont.setSize(SkIntToScalar(kLargeDFFontSize));
186 }
187#endif
188
189 dfFont.setEdging(SkFont::Edging::kAntiAlias);
190 dfFont.setForceAutoHinting(false);
191 dfFont.setHinting(SkFontHinting::kNormal);
192
193 // The sub-pixel position will always happen when transforming to the screen.
194 dfFont.setSubpixel(false);
195 return dfFont;
196}
197
198std::pair<SkScalar, SkScalar> GrTextContext::InitDistanceFieldMinMaxScale(
199 SkScalar textSize,
200 const SkMatrix& viewMatrix,
201 const GrTextContext::Options& options) {
202
203 SkScalar scaledTextSize = scaled_text_size(textSize, viewMatrix);
204
205 // We have three sizes of distance field text, and within each size 'bucket' there is a floor
206 // and ceiling. A scale outside of this range would require regenerating the distance fields
207 SkScalar dfMaskScaleFloor;
208 SkScalar dfMaskScaleCeil;
209 if (scaledTextSize <= kSmallDFFontLimit) {
210 dfMaskScaleFloor = options.fMinDistanceFieldFontSize;
211 dfMaskScaleCeil = kSmallDFFontLimit;
212 } else if (scaledTextSize <= kMediumDFFontLimit) {
213 dfMaskScaleFloor = kSmallDFFontLimit;
214 dfMaskScaleCeil = kMediumDFFontLimit;
215 } else {
216 dfMaskScaleFloor = kMediumDFFontLimit;
217 dfMaskScaleCeil = options.fMaxDistanceFieldFontSize;
218 }
219
220 // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
221 // minMaxScale to make regeneration decisions. Specifically, we want the maximum minimum scale
222 // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can
223 // tolerate before we'd have to move to a large mip size. When we actually test these values
224 // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test
225 // against these values to decide if we can reuse or not(ie, will a given scale change our mip
226 // level)
227 SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil);
228
229 return std::make_pair(dfMaskScaleFloor / scaledTextSize, dfMaskScaleCeil / scaledTextSize);
230}
231
232SkPaint GrTextContext::InitDistanceFieldPaint(const SkPaint& paint) {
233 SkPaint dfPaint{paint};
234 dfPaint.setMaskFilter(GrSDFMaskFilter::Make());
235 return dfPaint;
236}
237
238///////////////////////////////////////////////////////////////////////////////////////////////////
239
240#if GR_TEST_UTILS
241
242#include "src/gpu/GrRenderTargetContext.h"
243
244GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) {
245 static uint32_t gContextID = SK_InvalidGenID;
246 static std::unique_ptr<GrTextContext> gTextContext;
247 static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
248
249 if (context->priv().contextID() != gContextID) {
250 gContextID = context->priv().contextID();
251 gTextContext = GrTextContext::Make(GrTextContext::Options());
252 }
253
254 // Setup dummy SkPaint / GrPaint / GrRenderTargetContext
255 auto rtc = GrRenderTargetContext::Make(
256 context, GrColorType::kRGBA_8888, nullptr, SkBackingFit::kApprox, {1024, 1024});
257
258 SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
259
260 SkPaint skPaint;
261 skPaint.setColor(random->nextU());
262
263 SkFont font;
264 if (random->nextBool()) {
265 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
266 } else {
267 font.setEdging(random->nextBool() ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias);
268 }
269 font.setSubpixel(random->nextBool());
270
271 const char* text = "The quick brown fox jumps over the lazy dog.";
272
273 // create some random x/y offsets, including negative offsets
274 static const int kMaxTrans = 1024;
275 int xPos = (random->nextU() % 2) * 2 - 1;
276 int yPos = (random->nextU() % 2) * 2 - 1;
277 int xInt = (random->nextU() % kMaxTrans) * xPos;
278 int yInt = (random->nextU() % kMaxTrans) * yPos;
279
280 return gTextContext->createOp_TestingOnly(context, gTextContext.get(), rtc.get(),
281 skPaint, font, viewMatrix, text, xInt, yInt);
282}
283
284#endif
285