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