diff options
author | Herb Derby <herb@google.com> | 2018-01-23 13:39:21 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-01-23 21:55:50 +0000 |
commit | 980a48de64baf2f974f6c99096391280d1c1d22c (patch) | |
tree | 8268924145dd50b4e6ce3e2e01bb36fb536ca948 /src/core/SkScalerContext.cpp | |
parent | 27059d36d63284b1af2c25e0e5a52c17485c54d7 (diff) |
Move glyph cache and descriptor functions off of SkPaint
BUG=skia:7515
Change-Id: If17b157db1077a9a3c0f9efd03929f62a3486419
Reviewed-on: https://skia-review.googlesource.com/98841
Reviewed-by: Mike Klein <mtklein@chromium.org>
Commit-Queue: Herb Derby <herb@google.com>
Diffstat (limited to 'src/core/SkScalerContext.cpp')
-rw-r--r-- | src/core/SkScalerContext.cpp | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp index d082232986..f1ac9c18da 100644 --- a/src/core/SkScalerContext.cpp +++ b/src/core/SkScalerContext.cpp @@ -5,6 +5,8 @@ * found in the LICENSE file. */ +#include "SkGlyphCache.h" +#include "SkPaint.h" #include "SkScalerContext.h" #include "SkAutoMalloc.h" @@ -23,6 +25,8 @@ #include "SkReadBuffer.h" #include "SkStroke.h" #include "SkStrokeRec.h" +#include "SkSurfacePriv.h" +#include "SkTextFormatParams.h" #include "SkWriteBuffer.h" void SkGlyph::toMask(SkMask* mask) const { @@ -835,3 +839,418 @@ std::unique_ptr<SkScalerContext> SkTypeface::createScalerContext( } return c; } + +/* + * Return the scalar with only limited fractional precision. Used to consolidate matrices + * that vary only slightly when we create our key into the font cache, since the font scaler + * typically returns the same looking resuts for tiny changes in the matrix. + */ +static SkScalar sk_relax(SkScalar x) { + SkScalar n = SkScalarRoundToScalar(x * 1024); + return n / 1024.0f; +} + +static SkMask::Format compute_mask_format(const SkPaint& paint) { + uint32_t flags = paint.getFlags(); + + // Antialiasing being disabled trumps all other settings. + if (!(flags & SkPaint::kAntiAlias_Flag)) { + return SkMask::kBW_Format; + } + + if (flags & SkPaint::kLCDRenderText_Flag) { + return SkMask::kLCD16_Format; + } + + return SkMask::kA8_Format; +} + +// Beyond this size, LCD doesn't appreciably improve quality, but it always +// cost more RAM and draws slower, so we set a cap. +#ifndef SK_MAX_SIZE_FOR_LCDTEXT + #define SK_MAX_SIZE_FOR_LCDTEXT 48 +#endif + +const SkScalar gMaxSize2ForLCDText = SK_MAX_SIZE_FOR_LCDTEXT * SK_MAX_SIZE_FOR_LCDTEXT; + +static bool too_big_for_lcd(const SkScalerContextRec& rec, bool checkPost2x2) { + if (checkPost2x2) { + SkScalar area = rec.fPost2x2[0][0] * rec.fPost2x2[1][1] - + rec.fPost2x2[1][0] * rec.fPost2x2[0][1]; + area *= rec.fTextSize * rec.fTextSize; + return area > gMaxSize2ForLCDText; + } else { + return rec.fTextSize > SK_MAX_SIZE_FOR_LCDTEXT; + } +} + +// if linear-text is on, then we force hinting to be off (since that's sort of +// the point of linear-text. +static SkPaint::Hinting computeHinting(const SkPaint& paint) { + SkPaint::Hinting h = paint.getHinting(); + if (paint.isLinearText()) { + h = SkPaint::kNo_Hinting; + } + return h; +} + +// The only reason this is not file static is because it needs the context of SkScalerContext to +// access SkPaint::computeLuminanceColor. +void SkScalerContext::MakeRecAndEffects(const SkPaint& paint, + const SkSurfaceProps* surfaceProps, + const SkMatrix* deviceMatrix, + SkScalerContextFlags scalerContextFlags, + SkScalerContextRec* rec, + SkScalerContextEffects* effects) { + SkASSERT(deviceMatrix == nullptr || !deviceMatrix->hasPerspective()); + + SkTypeface* typeface = paint.getTypeface(); + if (nullptr == typeface) { + typeface = SkTypeface::GetDefaultTypeface(); + } + rec->fFontID = typeface->uniqueID(); + rec->fTextSize = paint.getTextSize(); + rec->fPreScaleX = paint.getTextScaleX(); + rec->fPreSkewX = paint.getTextSkewX(); + + bool checkPost2x2 = false; + + if (deviceMatrix) { + const SkMatrix::TypeMask mask = deviceMatrix->getType(); + if (mask & SkMatrix::kScale_Mask) { + rec->fPost2x2[0][0] = sk_relax(deviceMatrix->getScaleX()); + rec->fPost2x2[1][1] = sk_relax(deviceMatrix->getScaleY()); + checkPost2x2 = true; + } else { + rec->fPost2x2[0][0] = rec->fPost2x2[1][1] = SK_Scalar1; + } + if (mask & SkMatrix::kAffine_Mask) { + rec->fPost2x2[0][1] = sk_relax(deviceMatrix->getSkewX()); + rec->fPost2x2[1][0] = sk_relax(deviceMatrix->getSkewY()); + checkPost2x2 = true; + } else { + rec->fPost2x2[0][1] = rec->fPost2x2[1][0] = 0; + } + } else { + rec->fPost2x2[0][0] = rec->fPost2x2[1][1] = SK_Scalar1; + rec->fPost2x2[0][1] = rec->fPost2x2[1][0] = 0; + } + + SkPaint::Style style = paint.getStyle(); + SkScalar strokeWidth = paint.getStrokeWidth(); + + unsigned flags = 0; + + if (paint.isFakeBoldText()) { +#ifdef SK_USE_FREETYPE_EMBOLDEN + flags |= SkScalerContext::kEmbolden_Flag; +#else + SkScalar fakeBoldScale = SkScalarInterpFunc(paint.getTextSize(), + kStdFakeBoldInterpKeys, + kStdFakeBoldInterpValues, + kStdFakeBoldInterpLength); + SkScalar extra = paint.getTextSize() * fakeBoldScale; + + if (style == SkPaint::kFill_Style) { + style = SkPaint::kStrokeAndFill_Style; + strokeWidth = extra; // ignore paint's strokeWidth if it was "fill" + } else { + strokeWidth += extra; + } +#endif + } + + if (paint.isDevKernText()) { + flags |= SkScalerContext::kDevKernText_Flag; + } + + if (style != SkPaint::kFill_Style && strokeWidth > 0) { + rec->fFrameWidth = strokeWidth; + rec->fMiterLimit = paint.getStrokeMiter(); + rec->fStrokeJoin = SkToU8(paint.getStrokeJoin()); + rec->fStrokeCap = SkToU8(paint.getStrokeCap()); + + if (style == SkPaint::kStrokeAndFill_Style) { + flags |= SkScalerContext::kFrameAndFill_Flag; + } + } else { + rec->fFrameWidth = 0; + rec->fMiterLimit = 0; + rec->fStrokeJoin = 0; + rec->fStrokeCap = 0; + } + + rec->fMaskFormat = SkToU8(compute_mask_format(paint)); + + if (SkMask::kLCD16_Format == rec->fMaskFormat) { + if (too_big_for_lcd(*rec, checkPost2x2)) { + rec->fMaskFormat = SkMask::kA8_Format; + flags |= SkScalerContext::kGenA8FromLCD_Flag; + } else { + SkPixelGeometry geometry = surfaceProps + ? surfaceProps->pixelGeometry() + : SkSurfacePropsDefaultPixelGeometry(); + switch (geometry) { + case kUnknown_SkPixelGeometry: + // eeek, can't support LCD + rec->fMaskFormat = SkMask::kA8_Format; + flags |= SkScalerContext::kGenA8FromLCD_Flag; + break; + case kRGB_H_SkPixelGeometry: + // our default, do nothing. + break; + case kBGR_H_SkPixelGeometry: + flags |= SkScalerContext::kLCD_BGROrder_Flag; + break; + case kRGB_V_SkPixelGeometry: + flags |= SkScalerContext::kLCD_Vertical_Flag; + break; + case kBGR_V_SkPixelGeometry: + flags |= SkScalerContext::kLCD_Vertical_Flag; + flags |= SkScalerContext::kLCD_BGROrder_Flag; + break; + } + } + } + + if (paint.isEmbeddedBitmapText()) { + flags |= SkScalerContext::kEmbeddedBitmapText_Flag; + } + if (paint.isSubpixelText()) { + flags |= SkScalerContext::kSubpixelPositioning_Flag; + } + if (paint.isAutohinted()) { + flags |= SkScalerContext::kForceAutohinting_Flag; + } + if (paint.isVerticalText()) { + flags |= SkScalerContext::kVertical_Flag; + } + if (paint.getFlags() & SkPaint::kGenA8FromLCD_Flag) { + flags |= SkScalerContext::kGenA8FromLCD_Flag; + } + rec->fFlags = SkToU16(flags); + + // these modify fFlags, so do them after assigning fFlags + rec->setHinting(computeHinting(paint)); + + rec->setLuminanceColor(paint.computeLuminanceColor()); + + // For now always set the paint gamma equal to the device gamma. + // The math in SkMaskGamma can handle them being different, + // but it requires superluminous masks when + // Ex : deviceGamma(x) < paintGamma(x) and x is sufficiently large. + rec->setDeviceGamma(SK_GAMMA_EXPONENT); + rec->setPaintGamma(SK_GAMMA_EXPONENT); + +#ifdef SK_GAMMA_CONTRAST + rec->setContrast(SK_GAMMA_CONTRAST); +#else + // A value of 0.5 for SK_GAMMA_CONTRAST appears to be a good compromise. + // With lower values small text appears washed out (though correctly so). + // With higher values lcd fringing is worse and the smoothing effect of + // partial coverage is diminished. + rec->setContrast(0.5f); +#endif + + rec->fReservedAlign = 0; + + // Allow the fonthost to modify our rec before we use it as a key into the + // cache. This way if we're asking for something that they will ignore, + // they can modify our rec up front, so we don't create duplicate cache + // entries. + typeface->onFilterRec(rec); + + if (!SkToBool(scalerContextFlags & SkScalerContextFlags::kFakeGamma)) { + rec->ignoreGamma(); + } + if (!SkToBool(scalerContextFlags & SkScalerContextFlags::kBoostContrast)) { + rec->setContrast(0); + } + + new (effects) SkScalerContextEffects{paint}; + if (effects->fPathEffect) { + rec->fMaskFormat = SkMask::kA8_Format; // force antialiasing when we do the scan conversion + // seems like we could support kLCD as well at this point... + } + if (effects->fMaskFilter) { + // force antialiasing with maskfilters + rec->fMaskFormat = SkMask::kA8_Format; + // Pre-blend is not currently applied to filtered text. + // The primary filter is blur, for which contrast makes no sense, + // and for which the destination guess error is more visible. + // Also, all existing users of blur have calibrated for linear. + rec->ignorePreBlend(); + } + + // If we're asking for A8, we force the colorlum to be gray, since that + // limits the number of unique entries, and the scaler will only look at + // the lum of one of them. + switch (rec->fMaskFormat) { + case SkMask::kLCD16_Format: { + // filter down the luminance color to a finite number of bits + SkColor color = rec->getLuminanceColor(); + rec->setLuminanceColor(SkMaskGamma::CanonicalColor(color)); + break; + } + case SkMask::kA8_Format: { + // filter down the luminance to a single component, since A8 can't + // use per-component information + SkColor color = rec->getLuminanceColor(); + U8CPU lum = SkComputeLuminance(SkColorGetR(color), + SkColorGetG(color), + SkColorGetB(color)); + // reduce to our finite number of bits + color = SkColorSetRGB(lum, lum, lum); + rec->setLuminanceColor(SkMaskGamma::CanonicalColor(color)); + break; + } + case SkMask::kBW_Format: + // No need to differentiate gamma or apply contrast if we're BW + rec->ignorePreBlend(); + break; + } +} + + +SkDescriptor* SkScalerContext::CreateDescriptorAndEffectsUsingPaint( + const SkPaint& paint, const SkSurfaceProps* surfaceProps, + SkScalerContextFlags scalerContextFlags, + const SkMatrix* deviceMatrix, SkAutoDescriptor* ad, + SkScalerContextEffects* effects) { + + SkScalerContextRec rec; + MakeRecAndEffects(paint, surfaceProps, deviceMatrix, scalerContextFlags, &rec, effects); + return AutoDescriptorGivenRecAndEffects(rec, *effects, ad); +} + +static size_t calculate_size_and_flatten( + const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + SkBinaryWriteBuffer* pathEffectBuffer, + SkBinaryWriteBuffer* maskFilterBuffer) +{ + size_t descSize = sizeof(rec); + int entryCount = 1; + + if (effects.fPathEffect) { + effects.fPathEffect->flatten(*pathEffectBuffer); + descSize += pathEffectBuffer->bytesWritten(); + entryCount += 1; + } + if (effects.fMaskFilter) { + effects.fMaskFilter->flatten(*maskFilterBuffer); + descSize += maskFilterBuffer->bytesWritten(); + entryCount += 1; + } + + descSize += SkDescriptor::ComputeOverhead(entryCount); + return descSize; +} + +#ifdef SK_DEBUG + #define TEST_DESC +#endif + +#ifdef TEST_DESC +static void test_desc(const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + SkBinaryWriteBuffer* peBuffer, + SkBinaryWriteBuffer* mfBuffer, + const SkDescriptor* desc) { + // Check that we completely write the bytes in desc (our key), and that + // there are no uninitialized bytes. If there were, then we would get + // false-misses (or worse, false-hits) in our fontcache. + // + // We do this buy filling 2 others, one with 0s and the other with 1s + // and create those, and then check that all 3 are identical. + SkAutoDescriptor ad1(desc->getLength()); + SkAutoDescriptor ad2(desc->getLength()); + SkDescriptor* desc1 = ad1.getDesc(); + SkDescriptor* desc2 = ad2.getDesc(); + + memset(desc1, 0x00, desc->getLength()); + memset(desc2, 0xFF, desc->getLength()); + + desc1->init(); + desc2->init(); + desc1->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec); + desc2->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec); + + auto add_flattenable = [](SkDescriptor* desc, uint32_t tag, + SkBinaryWriteBuffer* buffer) { + buffer->writeToMemory(desc->addEntry(tag, buffer->bytesWritten(), nullptr)); + }; + + if (effects.fPathEffect) { + add_flattenable(desc1, kPathEffect_SkDescriptorTag, peBuffer); + add_flattenable(desc2, kPathEffect_SkDescriptorTag, peBuffer); + } + if (effects.fMaskFilter) { + add_flattenable(desc1, kMaskFilter_SkDescriptorTag, mfBuffer); + add_flattenable(desc2, kMaskFilter_SkDescriptorTag, mfBuffer); + } + + SkASSERT(desc->getLength() == desc1->getLength()); + SkASSERT(desc->getLength() == desc2->getLength()); + desc1->computeChecksum(); + desc2->computeChecksum(); + SkASSERT(!memcmp(desc, desc1, desc->getLength())); + SkASSERT(!memcmp(desc, desc2, desc->getLength())); +} +#endif + +void generate_descriptor( + const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + SkBinaryWriteBuffer* pathEffectBuffer, + SkBinaryWriteBuffer* maskFilterBuffer, + SkDescriptor* desc) +{ + desc->init(); + desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec); + + auto add = [&desc](uint32_t tag, SkBinaryWriteBuffer* buffer) { + buffer->writeToMemory(desc->addEntry(tag, buffer->bytesWritten(), nullptr)); + }; + + if (effects.fPathEffect) { + add(kPathEffect_SkDescriptorTag, pathEffectBuffer); + } + if (effects.fMaskFilter) { + add(kMaskFilter_SkDescriptorTag, maskFilterBuffer); + } + + desc->computeChecksum(); +#ifdef TEST_DESC + test_desc(rec, effects, pathEffectBuffer, maskFilterBuffer, desc); +#endif +} + +SkDescriptor* SkScalerContext::AutoDescriptorGivenRecAndEffects( + const SkScalerContextRec& rec, + const SkScalerContextEffects& effects, + SkAutoDescriptor* ad) +{ + SkBinaryWriteBuffer peBuffer, mfBuffer; + + ad->reset(calculate_size_and_flatten(rec, effects, &peBuffer, &mfBuffer)); + + generate_descriptor(rec, effects, &peBuffer, &mfBuffer, ad->getDesc()); + + return ad->getDesc(); +} + +std::unique_ptr<SkDescriptor> SkScalerContext::DescriptorGivenRecAndEffects( + const SkScalerContextRec& rec, + const SkScalerContextEffects& effects) +{ + SkBinaryWriteBuffer peBuffer, mfBuffer; + + auto desc = SkDescriptor::Alloc(calculate_size_and_flatten(rec, effects, &peBuffer, &mfBuffer)); + + generate_descriptor(rec, effects, &peBuffer, &mfBuffer, desc.get()); + + return desc; +} + + |