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