Skip to content

Commit

Permalink
Add a way to replace picture placeholders during drawing (#1009)
Browse files Browse the repository at this point in the history
New `PictureFilterCanvas` class that allows override picture rendering
during drawing

Required for
JetBrains/compose-multiplatform-core#1766
  • Loading branch information
MatkovIvan authored Jan 14, 2025
1 parent cbf3345 commit c22b0fc
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.jetbrains.skia.impl.Library.Companion.staticLoad
*/
abstract class PaintFilterCanvas(private val canvas: Canvas, unrollDrawable: Boolean) :
Canvas(makePaintFilterCanvas(canvas, unrollDrawable), true, canvas) {
companion object {
private companion object {
init {
staticLoad()
}
Expand All @@ -23,7 +23,9 @@ abstract class PaintFilterCanvas(private val canvas: Canvas, unrollDrawable: Boo
*
* Note: The base implementation calls onFilter() for top-level/explicit paints only.
*/
abstract fun onFilter(paint: Paint): Boolean
protected abstract fun onFilter(paint: Paint): Boolean

// For JNI call
fun onFilter(paintPtr: NativePointer): Boolean {
val paint = Paint(paintPtr, false)
return onFilter(paint)
Expand Down Expand Up @@ -56,13 +58,3 @@ internal expect fun PaintFilterCanvas.doInit(ptr: NativePointer)
@ExternalSymbolName("org_jetbrains_skia_PaintFilterCanvas__1nMake")
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_PaintFilterCanvas__1nMake")
private external fun PaintFilterCanvas_nMake(canvasPtr: NativePointer, unrollDrawable: Boolean): NativePointer

// Native/JS only

@ExternalSymbolName("org_jetbrains_skia_PaintFilterCanvas__1nInit")
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_PaintFilterCanvas__1nInit")
internal external fun PaintFilterCanvas_nInit(ptr: NativePointer, onFilter: InteropPointer)

@ExternalSymbolName("org_jetbrains_skia_PaintFilterCanvas__1nGetOnFilterPaint")
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_PaintFilterCanvas__1nGetOnFilterPaint")
internal external fun PaintFilterCanvas_nGetOnFilterPaint(ptr: NativePointer): NativePointer
2 changes: 1 addition & 1 deletion skiko/src/commonMain/kotlin/org/jetbrains/skia/Picture.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package org.jetbrains.skia
import org.jetbrains.skia.impl.*
import org.jetbrains.skia.impl.Library.Companion.staticLoad

class Picture internal constructor(ptr: NativePointer) : RefCnt(ptr) {
class Picture internal constructor(ptr: NativePointer, managed: Boolean = true) : RefCnt(ptr, managed) {
companion object {
/**
* Recreates Picture that was serialized into data. Returns constructed Picture
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.jetbrains.skia

import org.jetbrains.skia.impl.Library.Companion.staticLoad
import org.jetbrains.skia.impl.NativePointer
import org.jetbrains.skia.impl.Stats
import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skia.impl.reachabilityBarrier

abstract class PictureFilterCanvas(canvas: Canvas) :
Canvas(makePictureFilterCanvas(canvas), true, this) {
private companion object {
init {
staticLoad()
}
}
init {
Stats.onNativeCall()
try {
doInit(_ptr)
} finally {
reachabilityBarrier(this)
}
}

protected abstract fun onDrawPicture(picture: Picture, matrix: Matrix33? = null, paint: Paint? = null): Boolean

fun onDrawPicture(picturePtr: NativePointer, matrixPtr: NativePointer, paintPtr: NativePointer): Boolean {
val picture = Picture(picturePtr, managed = false)
// TODO: Provide mapping for matrix arg
val paint = if (paintPtr == NullPointer) null else Paint(paintPtr, managed = false)
return onDrawPicture(picture, null, paint)
}
}

private fun makePictureFilterCanvas(canvas: Canvas): NativePointer {
Stats.onNativeCall()
return try {
PictureFilterCanvas_nMake(getPtr(canvas))
} finally {
reachabilityBarrier(canvas)
}
}

internal expect fun PictureFilterCanvas.doInit(ptr: NativePointer)

@ExternalSymbolName("org_jetbrains_skia_PictureFilterCanvas__1nMake")
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_PictureFilterCanvas__1nMake")
private external fun PictureFilterCanvas_nMake(canvasPtr: NativePointer): NativePointer
10 changes: 5 additions & 5 deletions skiko/src/jvmMain/cpp/common/PaintFilterCanvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
#include "SkPaintFilterCanvas.h"
#include "interop.hh"

class SkijaPaintFilterCanvas : public SkPaintFilterCanvas {
class SkikoPaintFilterCanvas : public SkPaintFilterCanvas {
public:
SkijaPaintFilterCanvas(
SkikoPaintFilterCanvas(
SkCanvas* canvas,
bool unrollDrawable
) : SkPaintFilterCanvas(canvas), unrollDrawable(unrollDrawable) {}

virtual ~SkijaPaintFilterCanvas() {
virtual ~SkikoPaintFilterCanvas() {
skija::PaintFilterCanvas::detach(jobj);
}

Expand All @@ -37,13 +37,13 @@ class SkijaPaintFilterCanvas : public SkPaintFilterCanvas {

extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_PaintFilterCanvas_1jvmKt_PaintFilterCanvas_1nInit
(JNIEnv* env, jclass jclass, jobject jobj, jlong canvasPtr) {
SkijaPaintFilterCanvas* canvas = reinterpret_cast<SkijaPaintFilterCanvas*>(static_cast<uintptr_t>(canvasPtr));
SkikoPaintFilterCanvas* canvas = reinterpret_cast<SkikoPaintFilterCanvas*>(static_cast<uintptr_t>(canvasPtr));
canvas->jobj = skija::PaintFilterCanvas::attach(env, jobj);
}

extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_PaintFilterCanvasKt_PaintFilterCanvas_1nMake
(JNIEnv* env, jclass jclass, jlong canvasPtr, jboolean unrollDrawable) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(canvasPtr));
SkijaPaintFilterCanvas* filterCanvas = new SkijaPaintFilterCanvas(canvas, unrollDrawable);
SkikoPaintFilterCanvas* filterCanvas = new SkikoPaintFilterCanvas(canvas, unrollDrawable);
return reinterpret_cast<jlong>(filterCanvas);
}
38 changes: 38 additions & 0 deletions skiko/src/jvmMain/cpp/common/PictureFilterCanvas.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include <jni.h>
#include "SkNWayCanvas.h"
#include "interop.hh"

class SkikoPictureFilterCanvas : public SkNWayCanvas {
public:
SkikoPictureFilterCanvas(SkCanvas* canvas) :
SkNWayCanvas(canvas->imageInfo().width(), canvas->imageInfo().height()),
_jobject(nullptr) {
this->addCanvas(canvas);
}

virtual ~SkikoPictureFilterCanvas() {
skija::PictureFilterCanvas::detach(_jobject);
}

jobject _jobject;

protected:
void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) override {
jboolean handled = skija::PictureFilterCanvas::onDrawPicture(_jobject, picture, matrix, paint);
if (!handled) {
SkCanvas::onDrawPicture(picture, matrix, paint);
}
}
};

extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_PictureFilterCanvas_1jvmKt_PictureFilterCanvas_1nInit
(JNIEnv* env, jclass jclass, jobject pictureFilterCanvas, jlong canvasPtr) {
SkikoPictureFilterCanvas* canvas = reinterpret_cast<SkikoPictureFilterCanvas*>(static_cast<uintptr_t>(canvasPtr));
canvas->_jobject = skija::PictureFilterCanvas::attach(env, pictureFilterCanvas);
}

extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_PictureFilterCanvasKt_PictureFilterCanvas_1nMake
(JNIEnv* env, jclass jclass, SkCanvas* canvas) {
SkikoPictureFilterCanvas* filterCanvas = new SkikoPictureFilterCanvas(canvas);
return reinterpret_cast<jlong>(filterCanvas);
}
35 changes: 35 additions & 0 deletions skiko/src/jvmMain/cpp/common/interop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,39 @@ namespace skija {
}
}

namespace PictureFilterCanvas {
JavaVM* _vm;
jmethodID onDrawPictureId;

void onLoad(JNIEnv* env) {
env->GetJavaVM(&_vm);
jclass local = env->FindClass("org/jetbrains/skia/PictureFilterCanvas");
onDrawPictureId = env->GetMethodID(local, "onDrawPicture", "(JJJ)Z");
}

void onUnload(JNIEnv* env) {
}

bool onDrawPicture(jobject obj, const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) {
JNIEnv *env;
_vm->AttachCurrentThread(AS_JNI_ENV_PTR(&env), NULL);
jboolean result = env->CallBooleanMethod(obj, onDrawPictureId, reinterpret_cast<jlong>(picture), reinterpret_cast<jlong>(matrix), reinterpret_cast<jlong>(paint));
_vm->DetachCurrentThread();
return result;
}

jobject attach(JNIEnv* env, jobject obj) {
return env->NewGlobalRef(obj);
}

void detach(jobject obj) {
JNIEnv *env;
_vm->AttachCurrentThread(AS_JNI_ENV_PTR(&env), NULL);
env->DeleteGlobalRef(obj);
_vm->DetachCurrentThread();
}
}

namespace Rect {
jclass cls;
jmethodID makeLTRB;
Expand Down Expand Up @@ -897,6 +930,7 @@ namespace skija {
PathSegment::onLoad(env);
Point::onLoad(env);
PaintFilterCanvas::onLoad(env);
PictureFilterCanvas::onLoad(env);
Rect::onLoad(env);
RRect::onLoad(env);
RSXform::onLoad(env);
Expand All @@ -909,6 +943,7 @@ namespace skija {
RRect::onUnload(env);
Rect::onUnload(env);
PaintFilterCanvas::onUnload(env);
PictureFilterCanvas::onUnload(env);
Point::onUnload(env);
PathSegment::onUnload(env);
Path::onUnload(env);
Expand Down
10 changes: 10 additions & 0 deletions skiko/src/jvmMain/cpp/common/interop.hh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "SkMatrix.h"
#include "SkM44.h"
#include "SkPaint.h"
#include "SkPicture.h"
#include "SkRefCnt.h"
#include "SkRect.h"
#include "SkRRect.h"
Expand Down Expand Up @@ -271,6 +272,15 @@ namespace skija {
void detach(jobject obj);
}

namespace PictureFilterCanvas {
extern jmethodID onDrawPictureId;
void onLoad(JNIEnv* env);
void onUnload(JNIEnv* env);
bool onDrawPicture(jobject obj, const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint);
jobject attach(JNIEnv* env, jobject obj);
void detach(jobject obj);
}

namespace Rect {
extern jclass cls;
extern jmethodID makeLTRB;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.jetbrains.skia

import org.jetbrains.skia.impl.NativePointer

internal actual fun PictureFilterCanvas.doInit(ptr: NativePointer) {
PictureFilterCanvas_nInit(this, ptr)
}

private external fun PictureFilterCanvas_nInit(thisPtr: PictureFilterCanvas, canvasPtr: NativePointer)
25 changes: 14 additions & 11 deletions skiko/src/nativeJsMain/cpp/PaintFilterCanvas.cc
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
#include <iostream>
#include "SkCanvas.h"
#include "SkDrawable.h"
#include "SkPaintFilterCanvas.h"
#include "common.h"

class SkijaPaintFilterCanvas : public SkPaintFilterCanvas {
class SkikoPaintFilterCanvas : public SkPaintFilterCanvas {
public:
SkijaPaintFilterCanvas(
SkikoPaintFilterCanvas(
SkCanvas* canvas,
bool unrollDrawable
) : SkPaintFilterCanvas(canvas), unrollDrawable(unrollDrawable), _onFilter(nullptr), _onFilterPaint(nullptr) {}
) : SkPaintFilterCanvas(canvas), unrollDrawable(unrollDrawable), _onFilter(nullptr), _onFilter_paint(nullptr) {}

SkPaint* onFilterPaint() const {
return _onFilterPaint;
return _onFilter_paint;
}

void init(KInteropPointer onFilter) {
_onFilter = KBooleanCallback(onFilter);
}

protected:
bool onFilter(SkPaint& paint) const override {
_onFilterPaint = &paint;
_onFilter_paint = &paint;
KBoolean result = _onFilter();
_onFilterPaint = nullptr;
_onFilter_paint = nullptr;
return static_cast<bool>(result);
}

Expand All @@ -36,26 +36,29 @@ class SkijaPaintFilterCanvas : public SkPaintFilterCanvas {

private:
KBooleanCallback _onFilter;
mutable SkPaint* _onFilterPaint;

// TODO: Support callback with parameters properly
mutable SkPaint* _onFilter_paint;

bool unrollDrawable;
};

SKIKO_EXPORT void org_jetbrains_skia_PaintFilterCanvas__1nInit
(KNativePointer canvasPtr, KInteropPointer onFilter) {
SkijaPaintFilterCanvas* canvas = reinterpret_cast<SkijaPaintFilterCanvas*>((canvasPtr));
SkikoPaintFilterCanvas* canvas = reinterpret_cast<SkikoPaintFilterCanvas*>((canvasPtr));
canvas->init(onFilter);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_PaintFilterCanvas__1nMake
(KNativePointer canvasPtr, KBoolean unrollDrawable) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((canvasPtr));
SkijaPaintFilterCanvas* filterCanvas = new SkijaPaintFilterCanvas(canvas, unrollDrawable);
SkikoPaintFilterCanvas* filterCanvas = new SkikoPaintFilterCanvas(canvas, unrollDrawable);
return reinterpret_cast<KNativePointer>(filterCanvas);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_PaintFilterCanvas__1nGetOnFilterPaint
(KNativePointer canvasPtr) {
SkijaPaintFilterCanvas* canvas = reinterpret_cast<SkijaPaintFilterCanvas*>((canvasPtr));
SkikoPaintFilterCanvas* canvas = reinterpret_cast<SkikoPaintFilterCanvas*>((canvasPtr));
return reinterpret_cast<KNativePointer>(canvas->onFilterPaint());
}

68 changes: 68 additions & 0 deletions skiko/src/nativeJsMain/cpp/PictureFilterCanvas.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "SkNWayCanvas.h"
#include "common.h"

class SkikoPictureFilterCanvas : public SkNWayCanvas {
public:
SkikoPictureFilterCanvas(SkCanvas* canvas) :
SkNWayCanvas(canvas->imageInfo().width(), canvas->imageInfo().height()),
_onDrawPicture(nullptr),
_onDrawPicture_picture(nullptr),
_onDrawPicture_matrix(nullptr),
_onDrawPicture_paint(nullptr) {
this->addCanvas(canvas);
}

KBooleanCallback _onDrawPicture;

// TODO: Support callback with parameters properly
const SkPicture* _onDrawPicture_picture;
const SkMatrix* _onDrawPicture_matrix;
const SkPaint* _onDrawPicture_paint;

protected:
void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) override {
_onDrawPicture_picture = picture;
_onDrawPicture_matrix = matrix;
_onDrawPicture_paint = paint;

KBoolean handled = _onDrawPicture();

_onDrawPicture_picture = nullptr;
_onDrawPicture_matrix = nullptr;
_onDrawPicture_paint = nullptr;

if (!handled) {
SkCanvas::onDrawPicture(picture, matrix, paint);
}
}
};

SKIKO_EXPORT KNativePointer org_jetbrains_skia_SkikoPictureFilterCanvas__1nMake
(SkCanvas* canvas) {
SkikoPictureFilterCanvas* filterCanvas = new SkikoPictureFilterCanvas(canvas);
return reinterpret_cast<KNativePointer>(filterCanvas);
}

SKIKO_EXPORT void org_jetbrains_skia_SkikoPictureFilterCanvas__1nInit
(KNativePointer canvasPtr, KInteropPointer onDrawPicture) {
SkikoPictureFilterCanvas* canvas = reinterpret_cast<SkikoPictureFilterCanvas*>(canvasPtr);
canvas->_onDrawPicture = KBooleanCallback(onDrawPicture);
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_SkikoPictureFilterCanvas__1nGetOnDrawPicture_picture
(KNativePointer canvasPtr) {
SkikoPictureFilterCanvas* canvas = reinterpret_cast<SkikoPictureFilterCanvas*>(canvasPtr);
return reinterpret_cast<KNativePointer>(const_cast<SkPicture *>(canvas->_onDrawPicture_picture));
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_SkikoPictureFilterCanvas__1nGetOnDrawPicture_matrix
(KNativePointer canvasPtr) {
SkikoPictureFilterCanvas* canvas = reinterpret_cast<SkikoPictureFilterCanvas*>(canvasPtr);
return reinterpret_cast<KNativePointer>(const_cast<SkMatrix *>(canvas->_onDrawPicture_matrix));
}

SKIKO_EXPORT KNativePointer org_jetbrains_skia_SkikoPictureFilterCanvas__1nGetOnDrawPicture_paint
(KNativePointer canvasPtr) {
SkikoPictureFilterCanvas* canvas = reinterpret_cast<SkikoPictureFilterCanvas*>(canvasPtr);
return reinterpret_cast<KNativePointer>(const_cast<SkPaint *>(canvas->_onDrawPicture_paint));
}
Loading

0 comments on commit c22b0fc

Please sign in to comment.