1

I am experimenting with native drawing using the Java Canvas API on Android. As far as I can understand, Canvas is built as an abstraction on top of SKCanvas from the Skia graphics engine. I'm willing to pass the Canvas object obtained in the OnDraw() method of a view to native code and draw using C++ and the Skia API.

What would be the best way to gain access to the SkCanvas pointer after passing the java Canvas object to native code using JNI?

Here is the information I have been able to assimilate, which ultimately boils down to accessing a private member from the instance a native platform object.

Source code of Canvas.java from https://cs.android.com/android/platform/superproject/+/master:frameworks/base/graphics/java/android/graphics/Canvas.java;l=59 shows that it has private variable named mNativeCanvasWrapper (defined in frameworks/base/graphics/java/android/graphics/BaseCanvas.java) which can be accessed via reflection through the function declared below.

/** @hide */
@UnsupportedAppUsage
public long getNativeCanvasWrapper() {
    return mNativeCanvasWrapper;
}

mNativeCanvasWrapper, among other things, is passed to all functions responsible for drawing. Following the trail of one such function (getWidth()), I can see it's JNI registration in https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/jni/android_graphics_Canvas.cpp;l=692

{"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},

Definition of CanvasJNI::getWidth()

static jint getWidth(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) {
    return static_cast<jint>(get_canvas(canvasHandle)->width());
}

Definition of get_canvas(canvasHandle)

static Canvas* get_canvas(jlong canvasHandle) {
    return reinterpret_cast<Canvas*>(canvasHandle);
}

The above Canvas* pointer actually points to a class called SkiaCanvas, which inherits from the Canvas base class. Definition for the class can be found at frameworks/base/libs/hwui/SkiaCanvas.cpp. This is the class that contains the actual pointer to the SKCanvas object that I need (marked private), as can be seen at https://cs.android.com/android/platform/superproject/+/master:frameworks/base/libs/hwui/SkiaCanvas.h;l=219.

How can I obtain the SKCanvas pointer from a SkiaCanvas pointer, given that it's marked private and the only getter is marked protected?

Sourav Banerjee
  • 191
  • 2
  • 8

1 Answers1

1

The answer is in two parts.
First, getting the Canvas* out of the JNI object:

jclass cls_Canvas = env->GetObjectClass(obj);
jmethodID mid_Canvas_getNativeCanvasWrapper = env->getMethodID("getNativeCanvasWrapper", "()J");
jlong canvas_ptr = env->CallLongMethod(obj, mid_Canvas_getNativeCanvasWrapper);

The second part goes from there:

class MyCanvas : public SkiaCanvas { public: SkCanvas* getCanvas() { return asSkCanvas(); };
MyCanvas* skc = reinterpret_cast<MyCanvas*>(canvas_ptr);
SkCanvas* s = myc->getCanvas();
Botje
  • 26,269
  • 3
  • 31
  • 41
  • Thanks a lot for the response, subclassing SkiaCanvas seems to make perfect sense here. I've got a couple of more queries regarding this. 1. Since SkiaCanvas.h header is not a part of the standard NDK API, how should I go about building this code? I'm aware that I'll have to use the android source tree somehow, but I am not sure exactly what paths to include. Can you please shed some light on this as I have never worked with private NDK API's before? – Sourav Banerjee Jun 16 '22 at 15:39
  • 2. I've managed to build Skia for my target architecture (arm64) as a static library which gives me a ton of libs like libskunicode.a and libharfbuzz.a along with libskia.a. Also included in an a file called icudtl.dat which is probably an icu data file. Do I need to link against all of these to ensure that everything works correctly? I only intend to render primitive shapes and text (that is already formatted correctly, don't need any text shaping or RTL detection). – Sourav Banerjee Jun 16 '22 at 15:39
  • Your Android application already has a copy of Skia loaded, since the Canvas requires it. The code you add should simply include the Skia headers from AOSP. You can try linking against the libskia.so present on your phone or try building your own .so and link against that? – Botje Jun 17 '22 at 07:50