diff options
author | Derek Sollenberger <djsollen@google.com> | 2014-12-16 08:37:20 -0500 |
---|---|---|
committer | Derek Sollenberger <djsollen@google.com> | 2015-01-30 12:56:37 -0500 |
commit | 1db141f93c4fe79a4669440c3d14f63bc87b2e34 (patch) | |
tree | 674930c62207ed50620d51b67448cffe3d01b43a /libs | |
parent | 728dace14d83e87e777ceaaf3dfd031cf3502f80 (diff) | |
download | frameworks_base-1db141f93c4fe79a4669440c3d14f63bc87b2e34.zip frameworks_base-1db141f93c4fe79a4669440c3d14f63bc87b2e34.tar.gz frameworks_base-1db141f93c4fe79a4669440c3d14f63bc87b2e34.tar.bz2 |
Create proxy between Skia's SkCanvas and the framework Canvas.
This enables Picture.java to be replayed into HWUI in addition
to extending the Skia testing suite to HWUI.
Bug: 19011232
Change-Id: Id27ac03eec817b0784763e62ab8413a07b3b8cb2
Diffstat (limited to 'libs')
-rw-r--r-- | libs/hwui/Android.common.mk | 1 | ||||
-rw-r--r-- | libs/hwui/Canvas.h | 16 | ||||
-rw-r--r-- | libs/hwui/DisplayListRenderer.cpp | 10 | ||||
-rw-r--r-- | libs/hwui/DisplayListRenderer.h | 8 | ||||
-rw-r--r-- | libs/hwui/SkiaCanvasProxy.cpp | 347 | ||||
-rw-r--r-- | libs/hwui/SkiaCanvasProxy.h | 104 |
6 files changed, 474 insertions, 12 deletions
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk index 7c1a724..c76b2b5 100644 --- a/libs/hwui/Android.common.mk +++ b/libs/hwui/Android.common.mk @@ -63,6 +63,7 @@ LOCAL_SRC_FILES := \ ResourceCache.cpp \ ShadowTessellator.cpp \ SkiaCanvas.cpp \ + SkiaCanvasProxy.cpp \ SkiaShader.cpp \ Snapshot.cpp \ SpotShadow.cpp \ diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h index 45dc03a..7ad0683 100644 --- a/libs/hwui/Canvas.h +++ b/libs/hwui/Canvas.h @@ -42,14 +42,14 @@ public: */ static Canvas* create_canvas(SkCanvas* skiaCanvas); - // TODO: enable HWUI to either create similar canvas wrapper or subclass - // directly from Canvas - //static Canvas* create_canvas(uirenderer::Renderer* renderer); - - // TODO: this is a temporary affordance until all necessary logic can be - // moved within this interface! Further, the return value should - // NOT be unref'd and is valid until this canvas is destroyed or a - // new bitmap is set. + /** + * Provides a Skia SkCanvas interface that acts as a proxy to this Canvas. + * It is useful for testing and clients (e.g. Picture/Movie) that expect to + * draw their contents into an SkCanvas. + * + * Further, the returned SkCanvas should NOT be unref'd and is valid until + * this canvas is destroyed or a new bitmap is set. + */ virtual SkCanvas* asSkCanvas() = 0; virtual void setBitmap(SkBitmap* bitmap, bool copyState) = 0; diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index d98d744..23181bc 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -59,6 +59,7 @@ DisplayListData* DisplayListRenderer::finishRecording() { mPathMap.clear(); DisplayListData* data = mDisplayListData; mDisplayListData = nullptr; + mSkiaCanvasProxy.reset(nullptr); return data; } @@ -94,6 +95,15 @@ void DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) { mDisplayListData->functors.add(functor); } +SkCanvas* DisplayListRenderer::asSkCanvas() { + LOG_ALWAYS_FATAL_IF(!mDisplayListData, + "attempting to get an SkCanvas when we are not recording!"); + if (!mSkiaCanvasProxy) { + mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this)); + } + return mSkiaCanvasProxy.get(); +} + int DisplayListRenderer::save(SkCanvas::SaveFlags flags) { addStateOp(new (alloc()) SaveOp((int) flags)); return mState.save((int) flags); diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 527a0e5..bd0b3b7 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -28,6 +28,7 @@ #include "Canvas.h" #include "CanvasState.h" #include "DisplayList.h" +#include "SkiaCanvasProxy.h" #include "RenderNode.h" #include "Renderer.h" #include "ResourceCache.h" @@ -136,10 +137,8 @@ public: // ---------------------------------------------------------------------------- // android/graphics/Canvas interface // ---------------------------------------------------------------------------- - virtual SkCanvas* asSkCanvas() override { - LOG_ALWAYS_FATAL("DisplayListRenderer has no SkCanvas"); - return nullptr; - } + virtual SkCanvas* asSkCanvas() override; + virtual void setBitmap(SkBitmap* bitmap, bool copyState) override { LOG_ALWAYS_FATAL("DisplayListRenderer is not backed by a bitmap."); } @@ -244,6 +243,7 @@ public: private: CanvasState mState; + std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy; enum DeferredBarrierType { kBarrier_None, diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp new file mode 100644 index 0000000..de5f91c --- /dev/null +++ b/libs/hwui/SkiaCanvasProxy.cpp @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SkiaCanvasProxy.h" + +#include <cutils/log.h> +#include <SkPatchUtils.h> + +namespace android { +namespace uirenderer { + +SkiaCanvasProxy::SkiaCanvasProxy(Canvas* canvas) + : INHERITED(canvas->width(), canvas->height()) + , mCanvas(canvas) {} + +void SkiaCanvasProxy::onDrawPaint(const SkPaint& paint) { + mCanvas->drawPaint(paint); +} + +void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + // convert the SkPoints into floats + SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + const size_t floatCount = count << 1; + const float* floatArray = &pts[0].fX; + + switch (pointMode) { + case kPoints_PointMode: { + mCanvas->drawPoints(floatArray, floatCount, paint); + break; + } + case kLines_PointMode: { + mCanvas->drawLines(floatArray, floatCount, paint); + break; + } + case kPolygon_PointMode: { + SkPaint strokedPaint(paint); + strokedPaint.setStyle(SkPaint::kStroke_Style); + + SkPath path; + for (size_t i = 0; i < count - 1; i++) { + path.moveTo(pts[i]); + path.lineTo(pts[i+1]); + this->drawPath(path, strokedPaint); + path.rewind(); + } + break; + } + default: + LOG_ALWAYS_FATAL("Unknown point type"); + } +} + +void SkiaCanvasProxy::onDrawOval(const SkRect& rect, const SkPaint& paint) { + mCanvas->drawOval(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint); +} + +void SkiaCanvasProxy::onDrawRect(const SkRect& rect, const SkPaint& paint) { + mCanvas->drawRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint); +} + +void SkiaCanvasProxy::onDrawRRect(const SkRRect& roundRect, const SkPaint& paint) { + if (!roundRect.isComplex()) { + const SkRect& rect = roundRect.rect(); + SkVector radii = roundRect.getSimpleRadii(); + mCanvas->drawRoundRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, + radii.fX, radii.fY, paint); + } else { + SkPath path; + path.addRRect(roundRect); + mCanvas->drawPath(path, paint); + } +} + +void SkiaCanvasProxy::onDrawPath(const SkPath& path, const SkPaint& paint) { + mCanvas->drawPath(path, paint); +} + +void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, + const SkPaint* paint) { + mCanvas->drawBitmap(bitmap, left, top, paint); +} + +void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* srcPtr, + const SkRect& dst, const SkPaint* paint, DrawBitmapRectFlags) { + SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(bitmap.width(), bitmap.height()); + mCanvas->drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, + dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint); +} + +void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, + const SkRect& dst, const SkPaint*) { + //TODO make nine-patch drawing a method on Canvas.h + SkDEBUGFAIL("SkiaCanvasProxy::onDrawBitmapNine is not yet supported"); +} + +void SkiaCanvasProxy::onDrawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint) { + mCanvas->save(SkCanvas::kMatrixClip_SaveFlag); + mCanvas->setMatrix(SkMatrix::I()); + mCanvas->drawBitmap(bitmap, left, top, paint); + mCanvas->restore(); +} + +void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkPoint vertices[], + const SkPoint texs[], const SkColor colors[], SkXfermode*, const uint16_t indices[], + int indexCount, const SkPaint& paint) { + // convert the SkPoints into floats + SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + const int floatCount = vertexCount << 1; + const float* vArray = &vertices[0].fX; + const float* tArray = (texs) ? &texs[0].fX : NULL; + const int* cArray = (colors) ? (int*)colors : NULL; + mCanvas->drawVertices(mode, floatCount, vArray, tArray, cArray, indices, indexCount, paint); +} + +SkSurface* SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) { + SkDEBUGFAIL("SkiaCanvasProxy::onNewSurface is not supported"); + return NULL; +} + +void SkiaCanvasProxy::willSave() { + mCanvas->save(SkCanvas::kMatrixClip_SaveFlag); +} + +SkCanvas::SaveLayerStrategy SkiaCanvasProxy::willSaveLayer(const SkRect* rectPtr, + const SkPaint* paint, SaveFlags flags) { + SkRect rect; + if (rectPtr) { + rect = *rectPtr; + } else if(!mCanvas->getClipBounds(&rect)) { + rect = SkRect::MakeEmpty(); + } + mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint, flags); + return SkCanvas::kNoLayer_SaveLayerStrategy; +} + +void SkiaCanvasProxy::willRestore() { + mCanvas->restore(); +} + +void SkiaCanvasProxy::didConcat(const SkMatrix& matrix) { + mCanvas->concat(matrix); +} + +void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) { + mCanvas->setMatrix(matrix); +} + +void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) { + SkPath path; + path.addRRect(outer); + path.addRRect(inner); + path.setFillType(SkPath::kEvenOdd_FillType); + this->drawPath(path, paint); +} + +/** + * Utility class that converts the incoming text & paint from the given encoding + * into glyphIDs. + */ +class GlyphIDConverter { +public: + GlyphIDConverter(const void* text, size_t byteLength, const SkPaint& origPaint) { + paint = origPaint; + if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) { + glyphIDs = (uint16_t*)text; + count = byteLength >> 1; + } else { + storage.reset(byteLength); // ensures space for one glyph per ID given UTF8 encoding. + glyphIDs = storage.get(); + count = paint.textToGlyphs(text, byteLength, storage.get()); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + } + } + + SkPaint paint; + uint16_t* glyphIDs; + int count; +private: + SkAutoSTMalloc<32, uint16_t> storage; +}; + +void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, + const SkPaint& origPaint) { + // convert to glyphIDs if necessary + GlyphIDConverter glyphs(text, byteLength, origPaint); + + // compute the glyph positions + SkAutoSTMalloc<32, SkPoint> pointStorage(glyphs.count); + SkAutoSTMalloc<32, SkScalar> glyphWidths(glyphs.count); + glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get()); + + // compute conservative bounds + // NOTE: We could call the faster paint.getFontBounds for a less accurate, + // but even more conservative bounds if this is too slow. + SkRect bounds; + glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds); + + // adjust for non-left alignment + if (glyphs.paint.getTextAlign() != SkPaint::kLeft_Align) { + SkScalar stop = 0; + for (int i = 0; i < glyphs.count; i++) { + stop += glyphWidths[i]; + } + if (glyphs.paint.getTextAlign() == SkPaint::kCenter_Align) { + stop = SkScalarHalf(stop); + } + if (glyphs.paint.isVerticalText()) { + y -= stop; + } else { + x -= stop; + } + } + + // setup the first glyph position and adjust bounds if needed + if (mCanvas->drawTextAbsolutePos()) { + bounds.offset(x,y); + pointStorage[0].set(x, y); + } else { + pointStorage[0].set(0, 0); + } + + // setup the remaining glyph positions + if (glyphs.paint.isVerticalText()) { + for (int i = 1; i < glyphs.count; i++) { + pointStorage[i].set(x, glyphWidths[i-1] + pointStorage[i-1].fY); + } + } else { + for (int i = 1; i < glyphs.count; i++) { + pointStorage[i].set(glyphWidths[i-1] + pointStorage[i-1].fX, y); + } + } + + SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + mCanvas->drawText(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint, + x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0); +} + +void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], + const SkPaint& origPaint) { + // convert to glyphIDs if necessary + GlyphIDConverter glyphs(text, byteLength, origPaint); + + // convert to relative positions if necessary + int x, y; + const SkPoint* posArray; + SkAutoSTMalloc<32, SkPoint> pointStorage; + if (mCanvas->drawTextAbsolutePos()) { + x = 0; + y = 0; + posArray = pos; + } else { + x = pos[0].fX; + y = pos[0].fY; + posArray = pointStorage.reset(glyphs.count); + for (int i = 0; i < glyphs.count; i++) { + pointStorage[i].fX = pos[i].fX- x; + pointStorage[i].fY = pos[i].fY- y; + } + } + + // compute conservative bounds + // NOTE: We could call the faster paint.getFontBounds for a less accurate, + // but even more conservative bounds if this is too slow. + SkRect bounds; + glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds); + + SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + mCanvas->drawText(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y, + bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0); +} + +void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], + SkScalar constY, const SkPaint& paint) { + const size_t pointCount = byteLength >> 1; + SkAutoSTMalloc<32, SkPoint> storage(pointCount); + SkPoint* pts = storage.get(); + for (size_t i = 0; i < pointCount; i++) { + pts[i].set(xpos[i], constY); + } + this->onDrawPosText(text, byteLength, pts, paint); +} + +void SkiaCanvasProxy::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, + const SkMatrix* matrix, const SkPaint& origPaint) { + // convert to glyphIDs if necessary + GlyphIDConverter glyphs(text, byteLength, origPaint); + mCanvas->drawTextOnPath(glyphs.glyphIDs, glyphs.count, path, 0, 0, glyphs.paint); +} + +void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + SkDEBUGFAIL("SkiaCanvasProxy::onDrawTextBlob is not supported"); +} + +void SkiaCanvasProxy::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkXfermode* xmode, const SkPaint& paint) { + SkPatchUtils::VertexData data; + + SkMatrix matrix; + mCanvas->getMatrix(&matrix); + SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &matrix); + + // It automatically adjusts lodX and lodY in case it exceeds the number of indices. + // If it fails to generate the vertices, then we do not draw. + if (SkPatchUtils::getVertexData(&data, cubics, colors, texCoords, lod.width(), lod.height())) { + this->drawVertices(SkCanvas::kTriangles_VertexMode, data.fVertexCount, data.fPoints, + data.fTexCoords, data.fColors, xmode, data.fIndices, data.fIndexCount, + paint); + } +} + +void SkiaCanvasProxy::onClipRect(const SkRect& rect, SkRegion::Op op, ClipEdgeStyle) { + mCanvas->clipRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, op); +} + +void SkiaCanvasProxy::onClipRRect(const SkRRect& roundRect, SkRegion::Op op, ClipEdgeStyle) { + SkPath path; + path.addRRect(roundRect); + mCanvas->clipPath(&path, op); +} + +void SkiaCanvasProxy::onClipPath(const SkPath& path, SkRegion::Op op, ClipEdgeStyle) { + mCanvas->clipPath(&path, op); +} + +void SkiaCanvasProxy::onClipRegion(const SkRegion& region, SkRegion::Op op) { + mCanvas->clipRegion(®ion, op); +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h new file mode 100644 index 0000000..4322fcf --- /dev/null +++ b/libs/hwui/SkiaCanvasProxy.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SkiaCanvasProxy_DEFINED +#define SkiaCanvasProxy_DEFINED + +#include <cutils/compiler.h> +#include <SkCanvas.h> + +#include "Canvas.h" + +namespace android { +namespace uirenderer { + +/** + * This class serves as a proxy between Skia's SkCanvas and Android Framework's + * Canvas. The class does not maintain any state and will pass through any request + * directly to the Canvas provided in the constructor. + * + * Upon construction it is expected that the provided Canvas has already been + * prepared for recording and will continue to be in the recording state while + * this proxy class is being used. + */ +class ANDROID_API SkiaCanvasProxy : public SkCanvas { +public: + SkiaCanvasProxy(Canvas* canvas); + virtual ~SkiaCanvasProxy() {} + +protected: + + virtual SkSurface* onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; + + virtual void willSave() override; + virtual SaveLayerStrategy willSaveLayer(const SkRect*, const SkPaint*, SaveFlags) override; + virtual void willRestore() override; + + virtual void didConcat(const SkMatrix&) override; + virtual void didSetMatrix(const SkMatrix&) override; + + virtual void onDrawPaint(const SkPaint& paint) override; + virtual void onDrawPoints(PointMode, size_t count, const SkPoint pts[], + const SkPaint&) override; + virtual void onDrawOval(const SkRect&, const SkPaint&) override; + virtual void onDrawRect(const SkRect&, const SkPaint&) override; + virtual void onDrawRRect(const SkRRect&, const SkPaint&) override; + virtual void onDrawPath(const SkPath& path, const SkPaint&) override; + virtual void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, + const SkPaint*) override; + virtual void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, + const SkPaint* paint, DrawBitmapRectFlags flags) override; + virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, + const SkRect& dst, const SkPaint*) override; + virtual void onDrawSprite(const SkBitmap&, int left, int top, + const SkPaint*) override; + virtual void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[], + const SkPoint texs[], const SkColor colors[], SkXfermode*, + const uint16_t indices[], int indexCount, + const SkPaint&) override; + + virtual void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + + virtual void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, + const SkPaint&) override; + virtual void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], + const SkPaint&) override; + virtual void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], + SkScalar constY, const SkPaint&) override; + virtual void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, + const SkMatrix* matrix, const SkPaint&) override; + virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override; + + virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkXfermode* xmode, + const SkPaint& paint) override; + + virtual void onClipRect(const SkRect&, SkRegion::Op, ClipEdgeStyle) override; + virtual void onClipRRect(const SkRRect&, SkRegion::Op, ClipEdgeStyle) override; + virtual void onClipPath(const SkPath&, SkRegion::Op, ClipEdgeStyle) override; + virtual void onClipRegion(const SkRegion&, SkRegion::Op) override; + +private: + Canvas* mCanvas; + + typedef SkCanvas INHERITED; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // SkiaCanvasProxy_DEFINED |