aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/SkScalerContext.cpp
diff options
context:
space:
mode:
authorGravatar Herb Derby <herb@google.com>2018-01-23 13:39:21 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2018-01-23 21:55:50 +0000
commit980a48de64baf2f974f6c99096391280d1c1d22c (patch)
tree8268924145dd50b4e6ce3e2e01bb36fb536ca948 /src/core/SkScalerContext.cpp
parent27059d36d63284b1af2c25e0e5a52c17485c54d7 (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.cpp419
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;
+}
+
+