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