1 /*
2 * Copyright 2018 The Android Open Source Project
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 "src/core/SkGlyphRunPainter.h"
9
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkColorFilter.h"
12 #include "include/core/SkColorSpace.h"
13 #include "include/core/SkMaskFilter.h"
14 #include "include/core/SkPathEffect.h"
15 #include "include/private/base/SkTDArray.h"
16 #include "src/core/SkDevice.h"
17 #include "src/core/SkDraw.h"
18 #include "src/core/SkEnumerate.h"
19 #include "src/core/SkFontPriv.h"
20 #include "src/core/SkGlyph.h"
21 #include "src/core/SkGlyphBuffer.h"
22 #include "src/core/SkRasterClip.h"
23 #include "src/core/SkScalerContext.h"
24 #include "src/core/SkStrike.h"
25 #include "src/core/SkStrikeCache.h"
26 #include "src/core/SkStrikeSpec.h"
27 #include "src/text/GlyphRun.h"
28
29 using namespace skglyph;
30 using namespace sktext;
31
32 namespace {
compute_scaler_context_flags(const SkColorSpace * cs)33 SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) {
34 // If we're doing linear blending, then we can disable the gamma hacks.
35 // Otherwise, leave them on. In either case, we still want the contrast boost:
36 // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
37 if (cs && cs->gammaIsLinear()) {
38 return SkScalerContextFlags::kBoostContrast;
39 } else {
40 return SkScalerContextFlags::kFakeGammaAndBoostContrast;
41 }
42 }
43
44 // TODO: collect this up into a single class when all the details are worked out.
45 // This is duplicate code. The original is in SubRunContainer.cpp.
46 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)47 prepare_for_path_drawing(SkStrike* strike,
48 SkZip<const SkGlyphID, const SkPoint> source,
49 SkZip<const SkGlyph*, SkPoint> acceptedBuffer,
50 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
51 int acceptedSize = 0;
52 int rejectedSize = 0;
53 strike->lock();
54 for (auto [glyphID, pos] : source) {
55 if (!SkScalarsAreFinite(pos.x(), pos.y())) {
56 continue;
57 }
58 const SkPackedGlyphID packedID{glyphID};
59 switch (SkGlyphDigest digest = strike->digestFor(kPath, packedID);
60 digest.actionFor(kPath)) {
61 case GlyphAction::kAccept:
62 acceptedBuffer[acceptedSize++] = std::make_tuple(strike->glyph(digest), pos);
63 break;
64 case GlyphAction::kReject:
65 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
66 break;
67 default:
68 break;
69 }
70 }
71 strike->unlock();
72 return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
73 }
74
75 // TODO: collect this up into a single class when all the details are worked out.
76 // This is duplicate code. The original is in SubRunContainer.cpp.
77 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)78 prepare_for_drawable_drawing(SkStrike* strike,
79 SkZip<const SkGlyphID, const SkPoint> source,
80 SkZip<const SkGlyph*, SkPoint> acceptedBuffer,
81 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
82 int acceptedSize = 0;
83 int rejectedSize = 0;
84 strike->lock();
85 for (auto [glyphID, pos] : source) {
86 if (!SkScalarsAreFinite(pos.x(), pos.y())) {
87 continue;
88 }
89 const SkPackedGlyphID packedID{glyphID};
90 switch (SkGlyphDigest digest = strike->digestFor(kDrawable, packedID);
91 digest.actionFor(kDrawable)) {
92 case GlyphAction::kAccept:
93 acceptedBuffer[acceptedSize++] = std::make_tuple(strike->glyph(digest), pos);
94 break;
95 case GlyphAction::kReject:
96 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
97 break;
98 default:
99 break;
100 }
101 }
102 strike->unlock();
103 return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
104 }
105
106 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)107 prepare_for_direct_mask_drawing(SkStrike* strike,
108 const SkMatrix& creationMatrix,
109 SkZip<const SkGlyphID, const SkPoint> source,
110 SkZip<const SkGlyph*, SkPoint> acceptedBuffer,
111 SkZip<SkGlyphID, SkPoint> rejectedBuffer) {
112 const SkIPoint mask = strike->roundingSpec().ignorePositionFieldMask;
113 const SkPoint halfSampleFreq = strike->roundingSpec().halfAxisSampleFreq;
114
115 // Build up the mapping from source space to device space. Add the rounding constant
116 // halfSampleFreq, so we just need to floor to get the device result.
117 SkMatrix positionMatrixWithRounding = creationMatrix;
118 positionMatrixWithRounding.postTranslate(halfSampleFreq.x(), halfSampleFreq.y());
119
120 int acceptedSize = 0;
121 int rejectedSize = 0;
122 strike->lock();
123 for (auto [glyphID, pos] : source) {
124 if (!SkScalarsAreFinite(pos.x(), pos.y())) {
125 continue;
126 }
127
128 const SkPoint mappedPos = positionMatrixWithRounding.mapPoint(pos);
129 const SkPackedGlyphID packedGlyphID = SkPackedGlyphID{glyphID, mappedPos, mask};
130 switch (SkGlyphDigest digest = strike->digestFor(kDirectMaskCPU, packedGlyphID);
131 digest.actionFor(kDirectMaskCPU)) {
132 case GlyphAction::kAccept: {
133 const SkPoint roundedPos{SkScalarFloorToScalar(mappedPos.x()),
134 SkScalarFloorToScalar(mappedPos.y())};
135 acceptedBuffer[acceptedSize++] =
136 std::make_tuple(strike->glyph(digest), roundedPos);
137 break;
138 }
139 case GlyphAction::kReject:
140 rejectedBuffer[rejectedSize++] = std::make_tuple(glyphID, pos);
141 break;
142 default:
143 break;
144 }
145 }
146 strike->unlock();
147
148 return {acceptedBuffer.first(acceptedSize), rejectedBuffer.first(rejectedSize)};
149 }
150 } // namespace
151
152 // -- SkGlyphRunListPainterCPU ---------------------------------------------------------------------
SkGlyphRunListPainterCPU(const SkSurfaceProps & props,SkColorType colorType,SkColorSpace * cs)153 SkGlyphRunListPainterCPU::SkGlyphRunListPainterCPU(const SkSurfaceProps& props,
154 SkColorType colorType,
155 SkColorSpace* cs)
156 : fDeviceProps{props}
157 , fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
158 , fColorType{colorType}
159 , fScalerContextFlags{compute_scaler_context_flags(cs)} {}
160
drawForBitmapDevice(SkCanvas * canvas,const BitmapDevicePainter * bitmapDevice,const sktext::GlyphRunList & glyphRunList,const SkPaint & paint,const SkMatrix & drawMatrix)161 void SkGlyphRunListPainterCPU::drawForBitmapDevice(SkCanvas* canvas,
162 const BitmapDevicePainter* bitmapDevice,
163 const sktext::GlyphRunList& glyphRunList,
164 const SkPaint& paint,
165 const SkMatrix& drawMatrix) {
166 SkSTArray<64, const SkGlyph*> acceptedPackedGlyphIDs;
167 SkSTArray<64, SkPoint> acceptedPositions;
168 SkSTArray<64, SkGlyphID> rejectedGlyphIDs;
169 SkSTArray<64, SkPoint> rejectedPositions;
170 const int maxGlyphRunSize = glyphRunList.maxGlyphRunSize();
171 acceptedPackedGlyphIDs.resize(maxGlyphRunSize);
172 acceptedPositions.resize(maxGlyphRunSize);
173 const auto acceptedBuffer = SkMakeZip(acceptedPackedGlyphIDs, acceptedPositions);
174 rejectedGlyphIDs.resize(maxGlyphRunSize);
175 rejectedPositions.resize(maxGlyphRunSize);
176 const auto rejectedBuffer = SkMakeZip(rejectedGlyphIDs, rejectedPositions);
177
178 // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
179 // convert the lcd text into A8 text. The props communicate this to the scaler.
180 auto& props = (kN32_SkColorType == fColorType && paint.isSrcOver())
181 ? fDeviceProps
182 : fBitmapFallbackProps;
183
184 SkPoint drawOrigin = glyphRunList.origin();
185 SkMatrix positionMatrix{drawMatrix};
186 positionMatrix.preTranslate(drawOrigin.x(), drawOrigin.y());
187 for (auto& glyphRun : glyphRunList) {
188 const SkFont& runFont = glyphRun.font();
189
190 SkZip<const SkGlyphID, const SkPoint> source = glyphRun.source();
191
192 if (SkStrikeSpec::ShouldDrawAsPath(paint, runFont, positionMatrix)) {
193 auto [strikeSpec, strikeToSourceScale] =
194 SkStrikeSpec::MakePath(runFont, paint, props, fScalerContextFlags);
195
196 auto strike = strikeSpec.findOrCreateStrike();
197
198 {
199 auto [accepted, rejected] = prepare_for_path_drawing(strike.get(),
200 source,
201 acceptedBuffer,
202 rejectedBuffer);
203
204 source = rejected;
205 // The paint we draw paths with must have the same anti-aliasing state as the
206 // runFont allowing the paths to have the same edging as the glyph masks.
207 SkPaint pathPaint = paint;
208 pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());
209
210 const bool stroking = pathPaint.getStyle() != SkPaint::kFill_Style;
211 const bool hairline = pathPaint.getStrokeWidth() == 0;
212 const bool needsExactCTM = pathPaint.getShader() ||
213 pathPaint.getPathEffect() ||
214 pathPaint.getMaskFilter() ||
215 (stroking && !hairline);
216
217 if (!needsExactCTM) {
218 for (auto [glyph, pos] : accepted) {
219 const SkPath* path = glyph->path();
220 SkMatrix m;
221 SkPoint translate = drawOrigin + pos;
222 m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
223 translate.x(), translate.y());
224 SkAutoCanvasRestore acr(canvas, true);
225 canvas->concat(m);
226 canvas->drawPath(*path, pathPaint);
227 }
228 } else {
229 for (auto [glyph, pos] : accepted) {
230 const SkPath* path = glyph->path();
231 SkMatrix m;
232 SkPoint translate = drawOrigin + pos;
233 m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
234 translate.x(), translate.y());
235
236 SkPath deviceOutline;
237 path->transform(m, &deviceOutline);
238 deviceOutline.setIsVolatile(true);
239 canvas->drawPath(deviceOutline, pathPaint);
240 }
241 }
242 }
243
244 if (!source.empty()) {
245 auto [accepted, rejected] = prepare_for_drawable_drawing(strike.get(),
246 source,
247 acceptedBuffer,
248 rejectedBuffer);
249 source = rejected;
250
251 for (auto [glyph, pos] : accepted) {
252 SkDrawable* drawable = glyph->drawable();
253 SkMatrix m;
254 SkPoint translate = drawOrigin + pos;
255 m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
256 translate.x(), translate.y());
257 SkAutoCanvasRestore acr(canvas, false);
258 SkRect drawableBounds = drawable->getBounds();
259 m.mapRect(&drawableBounds);
260 canvas->saveLayer(&drawableBounds, &paint);
261 drawable->draw(canvas, &m);
262 }
263 }
264 }
265 if (!source.empty() && !positionMatrix.hasPerspective()) {
266 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
267 runFont, paint, props, fScalerContextFlags, positionMatrix);
268
269 auto strike = strikeSpec.findOrCreateStrike();
270
271 auto [accepted, rejected] = prepare_for_direct_mask_drawing(strike.get(),
272 positionMatrix,
273 source,
274 acceptedBuffer,
275 rejectedBuffer);
276 source = rejected;
277 bitmapDevice->paintMasks(accepted, paint);
278 }
279 if (!source.empty()) {
280 std::vector<SkPoint> sourcePositions;
281
282 // Create a strike is source space to calculate scale information.
283 SkStrikeSpec scaleStrikeSpec = SkStrikeSpec::MakeMask(
284 runFont, paint, props, fScalerContextFlags, SkMatrix::I());
285 SkBulkGlyphMetrics metrics{scaleStrikeSpec};
286
287 auto glyphIDs = source.get<0>();
288 auto positions = source.get<1>();
289 SkSpan<const SkGlyph*> glyphs = metrics.glyphs(glyphIDs);
290 SkScalar maxScale = SK_ScalarMin;
291
292 // Calculate the scale that makes the longest edge 1:1 with its side in the cache.
293 for (auto [glyph, pos] : SkMakeZip(glyphs, positions)) {
294 SkPoint corners[4];
295 SkPoint srcPos = pos + drawOrigin;
296 // Store off the positions in device space to position the glyphs during drawing.
297 sourcePositions.push_back(srcPos);
298 SkRect rect = glyph->rect();
299 rect.makeOffset(srcPos);
300 positionMatrix.mapRectToQuad(corners, rect);
301 // left top -> right top
302 SkScalar scale = (corners[1] - corners[0]).length() / rect.width();
303 maxScale = std::max(maxScale, scale);
304 // right top -> right bottom
305 scale = (corners[2] - corners[1]).length() / rect.height();
306 maxScale = std::max(maxScale, scale);
307 // right bottom -> left bottom
308 scale = (corners[3] - corners[2]).length() / rect.width();
309 maxScale = std::max(maxScale, scale);
310 // left bottom -> left top
311 scale = (corners[0] - corners[3]).length() / rect.height();
312 maxScale = std::max(maxScale, scale);
313 }
314
315 if (maxScale <= 0) {
316 continue; // to the next run.
317 }
318
319 if (maxScale * runFont.getSize() > 256) {
320 maxScale = 256.0f / runFont.getSize();
321 }
322
323 SkMatrix cacheScale = SkMatrix::Scale(maxScale, maxScale);
324 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
325 runFont, paint, props, fScalerContextFlags, cacheScale);
326
327 auto strike = strikeSpec.findOrCreateStrike();
328
329 auto [accepted, rejected] = prepare_for_direct_mask_drawing(strike.get(),
330 positionMatrix,
331 source,
332 acceptedBuffer,
333 rejectedBuffer);
334 const SkScalar invMaxScale = 1.0f/maxScale;
335 for (auto [glyph, srcPos] : SkMakeZip(accepted.get<0>(), sourcePositions)) {
336 SkMask mask = glyph->mask();
337 // TODO: is this needed will A8 and BW just work?
338 if (mask.fFormat != SkMask::kARGB32_Format) {
339 continue;
340 }
341 SkBitmap bm;
342 bm.installPixels(SkImageInfo::MakeN32Premul(mask.fBounds.size()),
343 mask.fImage,
344 mask.fRowBytes);
345
346 // Since the glyph in the cache is scaled by maxScale, its top left vector is too
347 // long. Reduce it to find proper positions on the device.
348 SkPoint realPos =
349 srcPos + SkPoint::Make(mask.fBounds.left(), mask.fBounds.top())*invMaxScale;
350
351 // Calculate the preConcat matrix for drawBitmap to get the rectangle from the
352 // glyph cache (which is multiplied by maxScale) to land in the right place.
353 SkMatrix translate = SkMatrix::Translate(realPos);
354 translate.preScale(invMaxScale, invMaxScale);
355
356 // Draw the bitmap using the rect from the scaled cache, and not the source
357 // rectangle for the glyph.
358 bitmapDevice->drawBitmap(
359 bm, translate, nullptr, SkSamplingOptions{SkFilterMode::kLinear},
360 paint);
361 }
362 }
363
364 // TODO: have the mask stage above reject the glyphs that are too big, and handle the
365 // rejects in a more sophisticated stage.
366 }
367 }
368