Home | History | Annotate | Download | only in text
      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