• 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 "GrTextContext.h"
9 
10 #include "GrCaps.h"
11 #include "GrContext.h"
12 #include "GrRecordingContextPriv.h"
13 #include "GrSDFMaskFilter.h"
14 #include "GrTextBlobCache.h"
15 #include "SkDistanceFieldGen.h"
16 #include "SkDraw.h"
17 #include "SkDrawProcs.h"
18 #include "SkGlyphRun.h"
19 #include "SkGr.h"
20 #include "SkGraphics.h"
21 #include "SkMakeUnique.h"
22 #include "SkMaskFilterBase.h"
23 #include "SkPaintPriv.h"
24 #include "SkTo.h"
25 #include "ops/GrMeshDrawOp.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 
InitDistanceFieldPaint(const SkScalar textSize,const SkMatrix & viewMatrix,const Options & options,GrTextBlob * blob,SkPaint * skPaint,SkFont * skFont,SkScalar * textRatio,SkScalerContextFlags * flags)132 void GrTextContext::InitDistanceFieldPaint(const SkScalar textSize,
133                                            const SkMatrix& viewMatrix,
134                                            const Options& options,
135                                            GrTextBlob* blob,
136                                            SkPaint* skPaint,
137                                            SkFont* skFont,
138                                            SkScalar* textRatio,
139                                            SkScalerContextFlags* flags) {
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     // We have three sizes of distance field text, and within each size 'bucket' there is a floor
157     // and ceiling.  A scale outside of this range would require regenerating the distance fields
158     SkScalar dfMaskScaleFloor;
159     SkScalar dfMaskScaleCeil;
160     if (scaledTextSize <= kSmallDFFontLimit) {
161         dfMaskScaleFloor = options.fMinDistanceFieldFontSize;
162         dfMaskScaleCeil = kSmallDFFontLimit;
163         *textRatio = textSize / kSmallDFFontSize;
164         skFont->setSize(SkIntToScalar(kSmallDFFontSize));
165     } else if (scaledTextSize <= kMediumDFFontLimit) {
166         dfMaskScaleFloor = kSmallDFFontLimit;
167         dfMaskScaleCeil = kMediumDFFontLimit;
168         *textRatio = textSize / kMediumDFFontSize;
169         skFont->setSize(SkIntToScalar(kMediumDFFontSize));
170     } else {
171         dfMaskScaleFloor = kMediumDFFontLimit;
172         dfMaskScaleCeil = options.fMaxDistanceFieldFontSize;
173         *textRatio = textSize / kLargeDFFontSize;
174         skFont->setSize(SkIntToScalar(kLargeDFFontSize));
175     }
176 
177     // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
178     // minMaxScale to make regeneration decisions.  Specifically, we want the maximum minimum scale
179     // we can tolerate before we'd drop to a lower mip size, and the minimum maximum scale we can
180     // tolerate before we'd have to move to a large mip size.  When we actually test these values
181     // we look at the delta in scale between the new viewmatrix and the old viewmatrix, and test
182     // against these values to decide if we can reuse or not(ie, will a given scale change our mip
183     // level)
184     SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScaleCeil);
185     if (blob) {
186         blob->setMinAndMaxScale(dfMaskScaleFloor / scaledTextSize,
187                                 dfMaskScaleCeil / scaledTextSize);
188     }
189 
190     skFont->setEdging(SkFont::Edging::kAntiAlias);
191     skFont->setForceAutoHinting(false);
192     skFont->setHinting(kNormal_SkFontHinting);
193     skFont->setSubpixel(true);
194 
195     skPaint->setMaskFilter(GrSDFMaskFilter::Make());
196 
197     // We apply the fake-gamma by altering the distance in the shader, so we ignore the
198     // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
199     *flags = SkScalerContextFlags::kNone;
200 }
201 
202 ///////////////////////////////////////////////////////////////////////////////////////////////////
203 
204 #if GR_TEST_UTILS
205 
206 #include "GrRenderTargetContext.h"
207 
GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp)208 GR_DRAW_OP_TEST_DEFINE(GrAtlasTextOp) {
209     static uint32_t gContextID = SK_InvalidGenID;
210     static std::unique_ptr<GrTextContext> gTextContext;
211     static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
212 
213     if (context->priv().contextID() != gContextID) {
214         gContextID = context->priv().contextID();
215         gTextContext = GrTextContext::Make(GrTextContext::Options());
216     }
217 
218     const GrBackendFormat format =
219             context->contextPriv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
220 
221     // Setup dummy SkPaint / GrPaint / GrRenderTargetContext
222     sk_sp<GrRenderTargetContext> rtc(context->contextPriv().makeDeferredRenderTargetContext(
223         format, SkBackingFit::kApprox, 1024, 1024, kRGBA_8888_GrPixelConfig, nullptr));
224 
225     SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
226 
227     SkPaint skPaint;
228     skPaint.setColor(random->nextU());
229 
230     SkFont font;
231     if (random->nextBool()) {
232         font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
233     } else {
234         font.setEdging(random->nextBool() ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias);
235     }
236     font.setSubpixel(random->nextBool());
237 
238     const char* text = "The quick brown fox jumps over the lazy dog.";
239 
240     // create some random x/y offsets, including negative offsets
241     static const int kMaxTrans = 1024;
242     int xPos = (random->nextU() % 2) * 2 - 1;
243     int yPos = (random->nextU() % 2) * 2 - 1;
244     int xInt = (random->nextU() % kMaxTrans) * xPos;
245     int yInt = (random->nextU() % kMaxTrans) * yPos;
246 
247     return gTextContext->createOp_TestingOnly(context, gTextContext.get(), rtc.get(),
248                                               skPaint, font, viewMatrix, text, xInt, yInt);
249 }
250 
251 #endif
252