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