xref: /aosp_15_r20/external/skia/src/core/SkGlyphRunPainter.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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