diff options
Diffstat (limited to 'gfx/skia/skia/src/core/SkGlyphRunPainter.cpp')
-rw-r--r-- | gfx/skia/skia/src/core/SkGlyphRunPainter.cpp | 366 |
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. + } +} |