1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "SkiaCanvasProxy.h"
18 
19 #include <memory>
20 
21 #include <log/log.h>
22 
23 #include <SkLatticeIter.h>
24 #include <SkPaint.h>
25 #include <SkPatchUtils.h>
26 #include <SkPath.h>
27 #include <SkPixelRef.h>
28 #include <SkRRect.h>
29 #include <SkRSXform.h>
30 #include <SkRect.h>
31 #include <SkSurface.h>
32 #include <SkTextBlobRunIterator.h>
33 #include <SkVertices.h>
34 #include "hwui/Bitmap.h"
35 
36 namespace android {
37 namespace uirenderer {
38 
SkiaCanvasProxy(Canvas * canvas,bool filterHwuiCalls)39 SkiaCanvasProxy::SkiaCanvasProxy(Canvas* canvas, bool filterHwuiCalls)
40         : INHERITED(canvas->width(), canvas->height())
41         , mCanvas(canvas)
42         , mFilterHwuiCalls(filterHwuiCalls) {}
43 
onDrawPaint(const SkPaint & paint)44 void SkiaCanvasProxy::onDrawPaint(const SkPaint& paint) {
45     mCanvas->drawPaint(paint);
46 }
47 
onDrawPoints(PointMode pointMode,size_t count,const SkPoint pts[],const SkPaint & paint)48 void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPoint pts[],
49                                    const SkPaint& paint) {
50     if (!pts || count == 0) {
51         return;
52     }
53 
54     // convert the SkPoints into floats
55     static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint is no longer two floats");
56     const size_t floatCount = count << 1;
57     const float* floatArray = &pts[0].fX;
58 
59     switch (pointMode) {
60         case kPoints_PointMode: {
61             mCanvas->drawPoints(floatArray, floatCount, paint);
62             break;
63         }
64         case kLines_PointMode: {
65             mCanvas->drawLines(floatArray, floatCount, paint);
66             break;
67         }
68         case kPolygon_PointMode: {
69             SkPaint strokedPaint(paint);
70             strokedPaint.setStyle(SkPaint::kStroke_Style);
71 
72             SkPath path;
73             for (size_t i = 0; i < count - 1; i++) {
74                 path.moveTo(pts[i]);
75                 path.lineTo(pts[i + 1]);
76                 this->drawPath(path, strokedPaint);
77                 path.rewind();
78             }
79             break;
80         }
81         default:
82             LOG_ALWAYS_FATAL("Unknown point type");
83     }
84 }
85 
onDrawOval(const SkRect & rect,const SkPaint & paint)86 void SkiaCanvasProxy::onDrawOval(const SkRect& rect, const SkPaint& paint) {
87     mCanvas->drawOval(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint);
88 }
89 
onDrawRect(const SkRect & rect,const SkPaint & paint)90 void SkiaCanvasProxy::onDrawRect(const SkRect& rect, const SkPaint& paint) {
91     mCanvas->drawRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint);
92 }
93 
onDrawRRect(const SkRRect & roundRect,const SkPaint & paint)94 void SkiaCanvasProxy::onDrawRRect(const SkRRect& roundRect, const SkPaint& paint) {
95     if (!roundRect.isComplex()) {
96         const SkRect& rect = roundRect.rect();
97         SkVector radii = roundRect.getSimpleRadii();
98         mCanvas->drawRoundRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, radii.fX, radii.fY,
99                                paint);
100     } else {
101         SkPath path;
102         path.addRRect(roundRect);
103         mCanvas->drawPath(path, paint);
104     }
105 }
106 
onDrawArc(const SkRect & rect,SkScalar startAngle,SkScalar sweepAngle,bool useCenter,const SkPaint & paint)107 void SkiaCanvasProxy::onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle,
108                                 bool useCenter, const SkPaint& paint) {
109     mCanvas->drawArc(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, startAngle, sweepAngle,
110                      useCenter, paint);
111 }
112 
onDrawPath(const SkPath & path,const SkPaint & paint)113 void SkiaCanvasProxy::onDrawPath(const SkPath& path, const SkPaint& paint) {
114     mCanvas->drawPath(path, paint);
115 }
116 
onDrawBitmap(const SkBitmap & bitmap,SkScalar left,SkScalar top,const SkPaint * paint)117 void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top,
118                                    const SkPaint* paint) {
119     sk_sp<Bitmap> hwuiBitmap = Bitmap::createFrom(bitmap.info(), *bitmap.pixelRef());
120     // HWUI doesn't support extractSubset(), so convert any subsetted bitmap into
121     // a drawBitmapRect(); pass through an un-subsetted bitmap.
122     if (hwuiBitmap && bitmap.dimensions() != hwuiBitmap->info().dimensions()) {
123         SkIPoint origin = bitmap.pixelRefOrigin();
124         mCanvas->drawBitmap(
125                 *hwuiBitmap, origin.fX, origin.fY, origin.fX + bitmap.dimensions().width(),
126                 origin.fY + bitmap.dimensions().height(), left, top,
127                 left + bitmap.dimensions().width(), top + bitmap.dimensions().height(), paint);
128     } else {
129         mCanvas->drawBitmap(*hwuiBitmap, left, top, paint);
130     }
131 }
132 
onDrawBitmapRect(const SkBitmap & skBitmap,const SkRect * srcPtr,const SkRect & dst,const SkPaint * paint,SrcRectConstraint)133 void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& skBitmap, const SkRect* srcPtr,
134                                        const SkRect& dst, const SkPaint* paint, SrcRectConstraint) {
135     SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(skBitmap.width(), skBitmap.height());
136     // TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src?
137     Bitmap* bitmap = reinterpret_cast<Bitmap*>(skBitmap.pixelRef());
138     mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft, dst.fTop,
139                         dst.fRight, dst.fBottom, paint);
140 }
141 
onDrawBitmapNine(const SkBitmap & bitmap,const SkIRect & center,const SkRect & dst,const SkPaint *)142 void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center,
143                                        const SkRect& dst, const SkPaint*) {
144     // TODO make nine-patch drawing a method on Canvas.h
145     SkDEBUGFAIL("SkiaCanvasProxy::onDrawBitmapNine is not yet supported");
146 }
147 
onDrawImage(const SkImage * image,SkScalar left,SkScalar top,const SkPaint * paint)148 void SkiaCanvasProxy::onDrawImage(const SkImage* image, SkScalar left, SkScalar top,
149                                   const SkPaint* paint) {
150     SkBitmap skiaBitmap;
151     SkPixmap pixmap;
152     if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) {
153         onDrawBitmap(skiaBitmap, left, top, paint);
154     }
155 }
156 
onDrawImageRect(const SkImage * image,const SkRect * srcPtr,const SkRect & dst,const SkPaint * paint,SrcRectConstraint constraint)157 void SkiaCanvasProxy::onDrawImageRect(const SkImage* image, const SkRect* srcPtr, const SkRect& dst,
158                                       const SkPaint* paint, SrcRectConstraint constraint) {
159     SkBitmap skiaBitmap;
160     SkPixmap pixmap;
161     if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) {
162         sk_sp<Bitmap> bitmap = Bitmap::createFrom(skiaBitmap.info(), *skiaBitmap.pixelRef());
163         SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(image->width(), image->height());
164         mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft,
165                             dst.fTop, dst.fRight, dst.fBottom, paint);
166     }
167 }
168 
onDrawImageNine(const SkImage *,const SkIRect & center,const SkRect & dst,const SkPaint *)169 void SkiaCanvasProxy::onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst,
170                                       const SkPaint*) {
171     SkDEBUGFAIL("SkiaCanvasProxy::onDrawImageNine is not yet supported");
172 }
173 
onDrawImageLattice(const SkImage * image,const Lattice & lattice,const SkRect & dst,const SkPaint * paint)174 void SkiaCanvasProxy::onDrawImageLattice(const SkImage* image, const Lattice& lattice,
175                                          const SkRect& dst, const SkPaint* paint) {
176     SkLatticeIter iter(lattice, dst);
177     SkRect srcR, dstR;
178     while (iter.next(&srcR, &dstR)) {
179         onDrawImageRect(image, &srcR, dstR, paint, SkCanvas::kFast_SrcRectConstraint);
180     }
181 }
182 
onDrawVerticesObject(const SkVertices * vertices,SkBlendMode bmode,const SkPaint & paint)183 void SkiaCanvasProxy::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode,
184                                            const SkPaint& paint) {
185     if (mFilterHwuiCalls) {
186         return;
187     }
188     mCanvas->drawVertices(vertices, bmode, paint);
189 }
190 
onNewSurface(const SkImageInfo &,const SkSurfaceProps &)191 sk_sp<SkSurface> SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) {
192     SkDEBUGFAIL("SkiaCanvasProxy::onNewSurface is not supported");
193     return NULL;
194 }
195 
willSave()196 void SkiaCanvasProxy::willSave() {
197     mCanvas->save(android::SaveFlags::MatrixClip);
198 }
199 
saveFlags(SkCanvas::SaveLayerFlags layerFlags)200 static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) {
201     SaveFlags::Flags saveFlags = 0;
202 
203     if (!(layerFlags & SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag)) {
204         saveFlags |= SaveFlags::ClipToLayer;
205     }
206 
207     return saveFlags;
208 }
209 
getSaveLayerStrategy(const SaveLayerRec & saveLayerRec)210 SkCanvas::SaveLayerStrategy SkiaCanvasProxy::getSaveLayerStrategy(
211         const SaveLayerRec& saveLayerRec) {
212     SkRect rect;
213     if (saveLayerRec.fBounds) {
214         rect = *saveLayerRec.fBounds;
215     } else if (!mCanvas->getClipBounds(&rect)) {
216         rect = SkRect::MakeEmpty();
217     }
218     mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, saveLayerRec.fPaint,
219                        saveFlags(saveLayerRec.fSaveLayerFlags));
220     return SkCanvas::kNoLayer_SaveLayerStrategy;
221 }
222 
willRestore()223 void SkiaCanvasProxy::willRestore() {
224     mCanvas->restore();
225 }
226 
didConcat(const SkMatrix & matrix)227 void SkiaCanvasProxy::didConcat(const SkMatrix& matrix) {
228     mCanvas->concat(matrix);
229 }
230 
didSetMatrix(const SkMatrix & matrix)231 void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) {
232     mCanvas->setMatrix(matrix);
233 }
234 
onDrawDRRect(const SkRRect & outer,const SkRRect & inner,const SkPaint & paint)235 void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
236                                    const SkPaint& paint) {
237     SkPath path;
238     path.addRRect(outer);
239     path.addRRect(inner);
240     path.setFillType(SkPath::kEvenOdd_FillType);
241     this->drawPath(path, paint);
242 }
243 
244 /**
245  * Utility class that converts the incoming text & paint from the given encoding
246  * into glyphIDs.
247  */
248 class GlyphIDConverter {
249 public:
GlyphIDConverter(const void * text,size_t byteLength,const SkPaint & origPaint)250     GlyphIDConverter(const void* text, size_t byteLength, const SkPaint& origPaint) {
251         paint = origPaint;
252         if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) {
253             glyphIDs = (uint16_t*)text;
254             count = byteLength >> 1;
255         } else {
256             // ensure space for one glyph per ID given UTF8 encoding.
257             storage.reset(new uint16_t[byteLength]);
258             glyphIDs = storage.get();
259             count = paint.textToGlyphs(text, byteLength, storage.get());
260             paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
261         }
262     }
263 
264     SkPaint paint;
265     uint16_t* glyphIDs;
266     int count;
267 
268 private:
269     std::unique_ptr<uint16_t[]> storage;
270 };
271 
onDrawText(const void * text,size_t byteLength,SkScalar x,SkScalar y,const SkPaint & origPaint)272 void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
273                                  const SkPaint& origPaint) {
274     // convert to glyphIDs if necessary
275     GlyphIDConverter glyphs(text, byteLength, origPaint);
276 
277     // compute the glyph positions
278     std::unique_ptr<SkScalar[]> glyphWidths(new SkScalar[glyphs.count]);
279     glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get());
280 
281     // compute conservative bounds
282     // NOTE: We could call the faster paint.getFontBounds for a less accurate,
283     //       but even more conservative bounds if this  is too slow.
284     SkRect bounds;
285     glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds);
286 
287     // adjust for non-left alignment
288     if (glyphs.paint.getTextAlign() != SkPaint::kLeft_Align) {
289         SkScalar stop = 0;
290         for (int i = 0; i < glyphs.count; i++) {
291             stop += glyphWidths[i];
292         }
293         if (glyphs.paint.getTextAlign() == SkPaint::kCenter_Align) {
294             stop = SkScalarHalf(stop);
295         }
296         if (glyphs.paint.isVerticalText()) {
297             y -= stop;
298         } else {
299             x -= stop;
300         }
301     }
302 
303     // setup the first glyph position and adjust bounds if needed
304     int xBaseline = 0;
305     int yBaseline = 0;
306     if (mCanvas->drawTextAbsolutePos()) {
307         bounds.offset(x, y);
308         xBaseline = x;
309         yBaseline = y;
310     }
311 
312     static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint is no longer two floats");
313     auto glyphFunc = [&](uint16_t* text, float* positions) {
314         memcpy(text, glyphs.glyphIDs, glyphs.count * sizeof(uint16_t));
315         size_t posIndex = 0;
316         // setup the first glyph position
317         positions[posIndex++] = xBaseline;
318         positions[posIndex++] = yBaseline;
319         // setup the remaining glyph positions
320         if (glyphs.paint.isVerticalText()) {
321             float yPosition = yBaseline;
322             for (int i = 1; i < glyphs.count; i++) {
323                 positions[posIndex++] = xBaseline;
324                 yPosition += glyphWidths[i - 1];
325                 positions[posIndex++] = yPosition;
326             }
327         } else {
328             float xPosition = xBaseline;
329             for (int i = 1; i < glyphs.count; i++) {
330                 xPosition += glyphWidths[i - 1];
331                 positions[posIndex++] = xPosition;
332                 positions[posIndex++] = yBaseline;
333             }
334         }
335     };
336     mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop,
337                         bounds.fRight, bounds.fBottom, 0);
338 }
339 
onDrawPosText(const void * text,size_t byteLength,const SkPoint pos[],const SkPaint & origPaint)340 void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
341                                     const SkPaint& origPaint) {
342     // convert to glyphIDs if necessary
343     GlyphIDConverter glyphs(text, byteLength, origPaint);
344 
345     // convert to relative positions if necessary
346     int x, y;
347     if (mCanvas->drawTextAbsolutePos()) {
348         x = 0;
349         y = 0;
350     } else {
351         x = pos[0].fX;
352         y = pos[0].fY;
353     }
354 
355     // Compute conservative bounds.  If the content has already been processed
356     // by Minikin then it had already computed these bounds.  Unfortunately,
357     // there is no way to capture those bounds as part of the Skia drawPosText
358     // API so we need to do that computation again here.
359     SkRect bounds = SkRect::MakeEmpty();
360     for (int i = 0; i < glyphs.count; i++) {
361         SkRect glyphBounds = SkRect::MakeEmpty();
362         glyphs.paint.measureText(&glyphs.glyphIDs[i], sizeof(uint16_t), &glyphBounds);
363         glyphBounds.offset(pos[i].fX, pos[i].fY);
364         bounds.join(glyphBounds);
365     }
366 
367     static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint is no longer two floats");
368     auto glyphFunc = [&](uint16_t* text, float* positions) {
369         memcpy(text, glyphs.glyphIDs, glyphs.count * sizeof(uint16_t));
370         if (mCanvas->drawTextAbsolutePos()) {
371             memcpy(positions, pos, 2 * glyphs.count * sizeof(float));
372         } else {
373             for (int i = 0, posIndex = 0; i < glyphs.count; i++) {
374                 positions[posIndex++] = pos[i].fX - x;
375                 positions[posIndex++] = pos[i].fY - y;
376             }
377         }
378     };
379     mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop,
380                         bounds.fRight, bounds.fBottom, 0);
381 }
382 
onDrawPosTextH(const void * text,size_t byteLength,const SkScalar xpos[],SkScalar constY,const SkPaint & paint)383 void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
384                                      SkScalar constY, const SkPaint& paint) {
385     const size_t pointCount = byteLength >> 1;
386     std::unique_ptr<SkPoint[]> pts(new SkPoint[pointCount]);
387     for (size_t i = 0; i < pointCount; i++) {
388         pts[i].set(xpos[i], constY);
389     }
390     this->onDrawPosText(text, byteLength, pts.get(), paint);
391 }
392 
onDrawTextOnPath(const void * text,size_t byteLength,const SkPath & path,const SkMatrix * matrix,const SkPaint & origPaint)393 void SkiaCanvasProxy::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path,
394                                        const SkMatrix* matrix, const SkPaint& origPaint) {
395     SkDEBUGFAIL("SkiaCanvasProxy::onDrawTextOnPath is not supported");
396 }
397 
onDrawTextRSXform(const void * text,size_t byteLength,const SkRSXform xform[],const SkRect * cullRect,const SkPaint & paint)398 void SkiaCanvasProxy::onDrawTextRSXform(const void* text, size_t byteLength,
399                                         const SkRSXform xform[], const SkRect* cullRect,
400                                         const SkPaint& paint) {
401     GlyphIDConverter glyphs(text, byteLength, paint);  // Just get count
402     SkMatrix localM, currM, origM;
403     mCanvas->getMatrix(&currM);
404     origM = currM;
405     for (int i = 0; i < glyphs.count; i++) {
406         localM.setRSXform(*xform++);
407         currM.setConcat(origM, localM);
408         mCanvas->setMatrix(currM);
409         this->onDrawText((char*)text + (byteLength / glyphs.count * i), byteLength / glyphs.count,
410                          0, 0, paint);
411     }
412     mCanvas->setMatrix(origM);
413 }
414 
onDrawTextBlob(const SkTextBlob * blob,SkScalar x,SkScalar y,const SkPaint & paint)415 void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
416                                      const SkPaint& paint) {
417     SkPaint runPaint = paint;
418 
419     SkTextBlobRunIterator it(blob);
420     for (; !it.done(); it.next()) {
421         size_t textLen = it.glyphCount() * sizeof(uint16_t);
422         const SkPoint& offset = it.offset();
423         // applyFontToPaint() always overwrites the exact same attributes,
424         // so it is safe to not re-seed the paint for this reason.
425         it.applyFontToPaint(&runPaint);
426 
427         switch (it.positioning()) {
428             case SkTextBlob::kDefault_Positioning:
429                 this->drawText(it.glyphs(), textLen, x + offset.x(), y + offset.y(), runPaint);
430                 break;
431             case SkTextBlob::kHorizontal_Positioning: {
432                 std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]);
433                 for (size_t i = 0; i < it.glyphCount(); i++) {
434                     pts[i].set(x + offset.x() + it.pos()[i], y + offset.y());
435                 }
436                 this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint);
437                 break;
438             }
439             case SkTextBlob::kFull_Positioning: {
440                 std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]);
441                 for (size_t i = 0; i < it.glyphCount(); i++) {
442                     const size_t xIndex = i * 2;
443                     const size_t yIndex = xIndex + 1;
444                     pts[i].set(x + offset.x() + it.pos()[xIndex],
445                                y + offset.y() + it.pos()[yIndex]);
446                 }
447                 this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint);
448                 break;
449             }
450             default:
451                 SK_ABORT("unhandled positioning mode");
452         }
453     }
454 }
455 
onDrawPatch(const SkPoint cubics[12],const SkColor colors[4],const SkPoint texCoords[4],SkBlendMode bmode,const SkPaint & paint)456 void SkiaCanvasProxy::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
457                                   const SkPoint texCoords[4], SkBlendMode bmode,
458                                   const SkPaint& paint) {
459     if (mFilterHwuiCalls) {
460         return;
461     }
462     SkMatrix matrix;
463     mCanvas->getMatrix(&matrix);
464     SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &matrix);
465 
466     mCanvas->drawVertices(
467             SkPatchUtils::MakeVertices(cubics, colors, texCoords, lod.width(), lod.height()).get(),
468             bmode, paint);
469 }
470 
onClipRect(const SkRect & rect,SkClipOp op,ClipEdgeStyle)471 void SkiaCanvasProxy::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle) {
472     mCanvas->clipRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, op);
473 }
474 
onClipRRect(const SkRRect & roundRect,SkClipOp op,ClipEdgeStyle)475 void SkiaCanvasProxy::onClipRRect(const SkRRect& roundRect, SkClipOp op, ClipEdgeStyle) {
476     SkPath path;
477     path.addRRect(roundRect);
478     mCanvas->clipPath(&path, op);
479 }
480 
onClipPath(const SkPath & path,SkClipOp op,ClipEdgeStyle)481 void SkiaCanvasProxy::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle) {
482     mCanvas->clipPath(&path, op);
483 }
484 
485 };  // namespace uirenderer
486 };  // namespace android
487