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