• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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