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