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 43 GrTextContext::GrTextContext(const Options& options) 44 : fDistanceAdjustTable(new GrDistanceFieldAdjustTable), fOptions(options) { 45 SanitizeOptions(&fOptions); 46 } 47 48 std::unique_ptr<GrTextContext> GrTextContext::Make(const Options& options) { 49 return std::unique_ptr<GrTextContext>(new GrTextContext(options)); 50 } 51 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 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 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 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 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 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