summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/core/SkGlyphRunPainter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/skia/skia/src/core/SkGlyphRunPainter.cpp')
-rw-r--r--gfx/skia/skia/src/core/SkGlyphRunPainter.cpp366
1 files changed, 366 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/core/SkGlyphRunPainter.cpp b/gfx/skia/skia/src/core/SkGlyphRunPainter.cpp
new file mode 100644
index 0000000000..48755aaff4
--- /dev/null
+++ b/gfx/skia/skia/src/core/SkGlyphRunPainter.cpp
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/core/SkGlyphRunPainter.h"
+
+#include "include/core/SkBitmap.h"
+#include "include/core/SkColorFilter.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkMaskFilter.h"
+#include "include/core/SkPathEffect.h"
+#include "include/private/base/SkTDArray.h"
+#include "src/core/SkDevice.h"
+#include "src/core/SkDraw.h"
+#include "src/core/SkEnumerate.h"
+#include "src/core/SkFontPriv.h"
+#include "src/core/SkGlyph.h"
+#include "src/core/SkRasterClip.h"
+#include "src/core/SkScalerContext.h"
+#include "src/core/SkStrike.h"
+#include "src/core/SkStrikeCache.h"
+#include "src/core/SkStrikeSpec.h"
+#include "src/text/GlyphRun.h"
+
+using namespace skglyph;
+using namespace sktext;
+
+namespace {
+SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) {
+ // If we're doing linear blending, then we can disable the gamma hacks.
+ // Otherwise, leave them on. In either case, we still want the contrast boost:
+ // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
+ if (cs && cs->gammaIsLinear()) {
+ return SkScalerContextFlags::kBoostContrast;
+ } else {
+ return SkScalerContextFlags::kFakeGammaAndBoostContrast;
+ }
+}
+
+// TODO: collect this up into a single class when all the details are worked out.
+// This is duplicate code. The original is in SubRunContainer.cpp.
+std::tuple<SkZip<const SkGlyph*, SkPoint>, SkZip<SkGlyphID, SkPoint>>
+prepare_for_path_drawing(SkStrike* strike,
+ SkZip<const SkGlyphID, const SkPoint> source,
+ SkZip<const SkGlyph*, SkPoint> acceptedBuffer,
+ SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
+ int acceptedSize = 0;
+ int rejectedSize = 0;
+ strike->lock();
+ for (auto [glyphID, pos] : source) {
+ if (!SkScalarsAreFinite(pos.x(), pos.y())) {
+ continue;
+ }
+ const SkPackedGlyphID packedID{glyphID};
+ switch (SkGlyphDigest digest = strike->digestFor(kPath, packedID);
+ digest.actionFor(kPath)) {
+ case GlyphAction::kAccept:
+ acceptedBuffer[acceptedSize++] = std::make_tuple(strike->glyph(digest), pos);
+ break;
+ case GlyphAction::kReject:
+ rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
+ break;
+ default:
+ break;
+ }
+ }
+ strike->unlock();
+ return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
+}
+
+// TODO: collect this up into a single class when all the details are worked out.
+// This is duplicate code. The original is in SubRunContainer.cpp.
+std::tuple<SkZip<const SkGlyph*, SkPoint>, SkZip<SkGlyphID, SkPoint>>
+prepare_for_drawable_drawing(SkStrike* strike,
+ SkZip<const SkGlyphID, const SkPoint> source,
+ SkZip<const SkGlyph*, SkPoint> acceptedBuffer,
+ SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
+ int acceptedSize = 0;
+ int rejectedSize = 0;
+ strike->lock();
+ for (auto [glyphID, pos] : source) {
+ if (!SkScalarsAreFinite(pos.x(), pos.y())) {
+ continue;
+ }
+ const SkPackedGlyphID packedID{glyphID};
+ switch (SkGlyphDigest digest = strike->digestFor(kDrawable, packedID);
+ digest.actionFor(kDrawable)) {
+ case GlyphAction::kAccept:
+ acceptedBuffer[acceptedSize++] = std::make_tuple(strike->glyph(digest), pos);
+ break;
+ case GlyphAction::kReject:
+ rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
+ break;
+ default:
+ break;
+ }
+ }
+ strike->unlock();
+ return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
+}
+
+std::tuple<SkZip<const SkGlyph*, SkPoint>, SkZip<SkGlyphID, SkPoint>>
+prepare_for_direct_mask_drawing(SkStrike* strike,
+ const SkMatrix& creationMatrix,
+ SkZip<const SkGlyphID, const SkPoint> source,
+ SkZip<const SkGlyph*, SkPoint> acceptedBuffer,
+ SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
+ const SkIPoint mask = strike->roundingSpec().ignorePositionFieldMask;
+ const SkPoint halfSampleFreq = strike->roundingSpec().halfAxisSampleFreq;
+
+ // Build up the mapping from source space to device space. Add the rounding constant
+ // halfSampleFreq, so we just need to floor to get the device result.
+ SkMatrix positionMatrixWithRounding = creationMatrix;
+ positionMatrixWithRounding.postTranslate(halfSampleFreq.x(), halfSampleFreq.y());
+
+ int acceptedSize = 0;
+ int rejectedSize = 0;
+ strike->lock();
+ for (auto [glyphID, pos] : source) {
+ if (!SkScalarsAreFinite(pos.x(), pos.y())) {
+ continue;
+ }
+
+ const SkPoint mappedPos = positionMatrixWithRounding.mapPoint(pos);
+ const SkPackedGlyphID packedGlyphID = SkPackedGlyphID{glyphID, mappedPos, mask};
+ switch (SkGlyphDigest digest = strike->digestFor(kDirectMaskCPU, packedGlyphID);
+ digest.actionFor(kDirectMaskCPU)) {
+ case GlyphAction::kAccept: {
+ const SkPoint roundedPos{SkScalarFloorToScalar(mappedPos.x()),
+ SkScalarFloorToScalar(mappedPos.y())};
+ acceptedBuffer[acceptedSize++] =
+ std::make_tuple(strike->glyph(digest), roundedPos);
+ break;
+ }
+ case GlyphAction::kReject:
+ rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
+ break;
+ default:
+ break;
+ }
+ }
+ strike->unlock();
+
+ return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
+}
+} // namespace
+
+// -- SkGlyphRunListPainterCPU ---------------------------------------------------------------------
+SkGlyphRunListPainterCPU::SkGlyphRunListPainterCPU(const SkSurfaceProps& props,
+ SkColorType colorType,
+ SkColorSpace* cs)
+ : fDeviceProps{props}
+ , fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
+ , fColorType{colorType}
+ , fScalerContextFlags{compute_scaler_context_flags(cs)} {}
+
+void SkGlyphRunListPainterCPU::drawForBitmapDevice(SkCanvas* canvas,
+ const BitmapDevicePainter* bitmapDevice,
+ const sktext::GlyphRunList& glyphRunList,
+ const SkPaint& paint,
+ const SkMatrix& drawMatrix) {
+ SkSTArray<64, const SkGlyph*> acceptedPackedGlyphIDs;
+ SkSTArray<64, SkPoint> acceptedPositions;
+ SkSTArray<64, SkGlyphID> rejectedGlyphIDs;
+ SkSTArray<64, SkPoint> rejectedPositions;
+ const int maxGlyphRunSize = glyphRunList.maxGlyphRunSize();
+ acceptedPackedGlyphIDs.resize(maxGlyphRunSize);
+ acceptedPositions.resize(maxGlyphRunSize);
+ const auto acceptedBuffer = SkMakeZip(acceptedPackedGlyphIDs, acceptedPositions);
+ rejectedGlyphIDs.resize(maxGlyphRunSize);
+ rejectedPositions.resize(maxGlyphRunSize);
+ const auto rejectedBuffer = SkMakeZip(rejectedGlyphIDs, rejectedPositions);
+
+ // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
+ // convert the lcd text into A8 text. The props communicate this to the scaler.
+ auto& props = (kN32_SkColorType == fColorType && paint.isSrcOver())
+ ? fDeviceProps
+ : fBitmapFallbackProps;
+
+ SkPoint drawOrigin = glyphRunList.origin();
+ SkMatrix positionMatrix{drawMatrix};
+ positionMatrix.preTranslate(drawOrigin.x(), drawOrigin.y());
+ for (auto& glyphRun : glyphRunList) {
+ const SkFont& runFont = glyphRun.font();
+
+ SkZip<const SkGlyphID, const SkPoint> source = glyphRun.source();
+
+ if (SkStrikeSpec::ShouldDrawAsPath(paint, runFont, positionMatrix)) {
+ auto [strikeSpec, strikeToSourceScale] =
+ SkStrikeSpec::MakePath(runFont, paint, props, fScalerContextFlags);
+
+ auto strike = strikeSpec.findOrCreateStrike();
+
+ {
+ auto [accepted, rejected] = prepare_for_path_drawing(strike.get(),
+ source,
+ acceptedBuffer,
+ rejectedBuffer);
+
+ source = rejected;
+ // The paint we draw paths with must have the same anti-aliasing state as the
+ // runFont allowing the paths to have the same edging as the glyph masks.
+ SkPaint pathPaint = paint;
+ pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());
+
+ const bool stroking = pathPaint.getStyle() != SkPaint::kFill_Style;
+ const bool hairline = pathPaint.getStrokeWidth() == 0;
+ const bool needsExactCTM = pathPaint.getShader() ||
+ pathPaint.getPathEffect() ||
+ pathPaint.getMaskFilter() ||
+ (stroking && !hairline);
+
+ if (!needsExactCTM) {
+ for (auto [glyph, pos] : accepted) {
+ const SkPath* path = glyph->path();
+ SkMatrix m;
+ SkPoint translate = drawOrigin + pos;
+ m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
+ translate.x(), translate.y());
+ SkAutoCanvasRestore acr(canvas, true);
+ canvas->concat(m);
+ canvas->drawPath(*path, pathPaint);
+ }
+ } else {
+ for (auto [glyph, pos] : accepted) {
+ const SkPath* path = glyph->path();
+ SkMatrix m;
+ SkPoint translate = drawOrigin + pos;
+ m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
+ translate.x(), translate.y());
+
+ SkPath deviceOutline;
+ path->transform(m, &deviceOutline);
+ deviceOutline.setIsVolatile(true);
+ canvas->drawPath(deviceOutline, pathPaint);
+ }
+ }
+ }
+
+ if (!source.empty()) {
+ auto [accepted, rejected] = prepare_for_drawable_drawing(strike.get(),
+ source,
+ acceptedBuffer,
+ rejectedBuffer);
+ source = rejected;
+
+ for (auto [glyph, pos] : accepted) {
+ SkDrawable* drawable = glyph->drawable();
+ SkMatrix m;
+ SkPoint translate = drawOrigin + pos;
+ m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
+ translate.x(), translate.y());
+ SkAutoCanvasRestore acr(canvas, false);
+ SkRect drawableBounds = drawable->getBounds();
+ m.mapRect(&drawableBounds);
+ canvas->saveLayer(&drawableBounds, &paint);
+ drawable->draw(canvas, &m);
+ }
+ }
+ }
+ if (!source.empty() && !positionMatrix.hasPerspective()) {
+ SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
+ runFont, paint, props, fScalerContextFlags, positionMatrix);
+
+ auto strike = strikeSpec.findOrCreateStrike();
+
+ auto [accepted, rejected] = prepare_for_direct_mask_drawing(strike.get(),
+ positionMatrix,
+ source,
+ acceptedBuffer,
+ rejectedBuffer);
+ source = rejected;
+ bitmapDevice->paintMasks(accepted, paint);
+ }
+ if (!source.empty()) {
+ std::vector<SkPoint> sourcePositions;
+
+ // Create a strike is source space to calculate scale information.
+ SkStrikeSpec scaleStrikeSpec = SkStrikeSpec::MakeMask(
+ runFont, paint, props, fScalerContextFlags, SkMatrix::I());
+ SkBulkGlyphMetrics metrics{scaleStrikeSpec};
+
+ auto glyphIDs = source.get<0>();
+ auto positions = source.get<1>();
+ SkSpan<const SkGlyph*> glyphs = metrics.glyphs(glyphIDs);
+ SkScalar maxScale = SK_ScalarMin;
+
+ // Calculate the scale that makes the longest edge 1:1 with its side in the cache.
+ for (auto [glyph, pos] : SkMakeZip(glyphs, positions)) {
+ SkPoint corners[4];
+ SkPoint srcPos = pos + drawOrigin;
+ // Store off the positions in device space to position the glyphs during drawing.
+ sourcePositions.push_back(srcPos);
+ SkRect rect = glyph->rect();
+ rect.makeOffset(srcPos);
+ positionMatrix.mapRectToQuad(corners, rect);
+ // left top -> right top
+ SkScalar scale = (corners[1] - corners[0]).length() / rect.width();
+ maxScale = std::max(maxScale, scale);
+ // right top -> right bottom
+ scale = (corners[2] - corners[1]).length() / rect.height();
+ maxScale = std::max(maxScale, scale);
+ // right bottom -> left bottom
+ scale = (corners[3] - corners[2]).length() / rect.width();
+ maxScale = std::max(maxScale, scale);
+ // left bottom -> left top
+ scale = (corners[0] - corners[3]).length() / rect.height();
+ maxScale = std::max(maxScale, scale);
+ }
+
+ if (maxScale <= 0) {
+ continue; // to the next run.
+ }
+
+ if (maxScale * runFont.getSize() > 256) {
+ maxScale = 256.0f / runFont.getSize();
+ }
+
+ SkMatrix cacheScale = SkMatrix::Scale(maxScale, maxScale);
+ SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
+ runFont, paint, props, fScalerContextFlags, cacheScale);
+
+ auto strike = strikeSpec.findOrCreateStrike();
+
+ auto [accepted, rejected] = prepare_for_direct_mask_drawing(strike.get(),
+ positionMatrix,
+ source,
+ acceptedBuffer,
+ rejectedBuffer);
+ const SkScalar invMaxScale = 1.0f/maxScale;
+ for (auto [glyph, srcPos] : SkMakeZip(accepted.get<0>(), sourcePositions)) {
+ SkMask mask = glyph->mask();
+ // TODO: is this needed will A8 and BW just work?
+ if (mask.fFormat != SkMask::kARGB32_Format) {
+ continue;
+ }
+ SkBitmap bm;
+ bm.installPixels(SkImageInfo::MakeN32Premul(mask.fBounds.size()),
+ mask.fImage,
+ mask.fRowBytes);
+
+ // Since the glyph in the cache is scaled by maxScale, its top left vector is too
+ // long. Reduce it to find proper positions on the device.
+ SkPoint realPos =
+ srcPos + SkPoint::Make(mask.fBounds.left(), mask.fBounds.top())*invMaxScale;
+
+ // Calculate the preConcat matrix for drawBitmap to get the rectangle from the
+ // glyph cache (which is multiplied by maxScale) to land in the right place.
+ SkMatrix translate = SkMatrix::Translate(realPos);
+ translate.preScale(invMaxScale, invMaxScale);
+
+ // Draw the bitmap using the rect from the scaled cache, and not the source
+ // rectangle for the glyph.
+ bitmapDevice->drawBitmap(
+ bm, translate, nullptr, SkSamplingOptions{SkFilterMode::kLinear},
+ paint);
+ }
+ }
+
+ // TODO: have the mask stage above reject the glyphs that are too big, and handle the
+ // rejects in a more sophisticated stage.
+ }
+}