reimplement Bitmap and Path using GdkTexture and GskPath

This allows us to use GskCanvas for Bitmap rendering

This increases the required GTK version to >= 4.14.
This commit is contained in:
Julian Winkler
2024-12-19 20:20:40 +01:00
parent c076c1e545
commit cd2c69cf73
26 changed files with 498 additions and 3559 deletions

View File

@@ -0,0 +1,75 @@
#include <gtk/gtk.h>
#include "../defines.h"
#include "../generated_headers/android_graphics_Bitmap.h"
JNIEXPORT jlong JNICALL Java_android_graphics_Bitmap_native_1create_1snapshot(JNIEnv *env, jclass class, jlong texture_ptr)
{
GtkSnapshot *snapshot = gtk_snapshot_new();
if (texture_ptr) {
GdkTexture *texture = GDK_TEXTURE(_PTR(texture_ptr));
gtk_snapshot_append_texture(snapshot, texture, &GRAPHENE_RECT_INIT(0, 0, gdk_texture_get_width(texture), gdk_texture_get_height(texture)));
g_object_unref(texture);
}
return _INTPTR(snapshot);
}
JNIEXPORT jlong JNICALL Java_android_graphics_Bitmap_native_1create_1texture(JNIEnv *env, jclass class, jlong snapshot_ptr, jint width, jint height)
{
GtkSnapshot *snapshot = _PTR(snapshot_ptr);
static GType renderer_type = 0;
if (!renderer_type) {
// Use same renderer type as for onscreen rendering.
GdkSurface *surface = gdk_surface_new_toplevel(gdk_display_get_default());
GskRenderer *renderer = gsk_renderer_new_for_surface(surface);
renderer_type = G_OBJECT_TYPE(renderer);
gsk_renderer_unrealize(renderer);
g_object_unref(renderer);
gdk_surface_destroy(surface);
}
GskRenderer *renderer = g_object_new(renderer_type, NULL);
gsk_renderer_realize(renderer, NULL, NULL);
GskRenderNode *node = snapshot ? gtk_snapshot_free_to_node(snapshot) : NULL;
graphene_rect_t bounds = GRAPHENE_RECT_INIT(0, 0, width, height);
if (!node)
node = gsk_color_node_new(&(GdkRGBA){.alpha = 0}, &bounds);
GdkTexture *texture = gsk_renderer_render_texture(renderer, node, &bounds);
gsk_render_node_unref(node);
gsk_renderer_unrealize(renderer);
g_object_unref(renderer);
return _INTPTR(texture);
}
JNIEXPORT jint JNICALL Java_android_graphics_Bitmap_native_1get_1width(JNIEnv *env, jclass class, jlong texture_ptr)
{
return gdk_texture_get_width(GDK_TEXTURE(_PTR(texture_ptr)));
}
JNIEXPORT jint JNICALL Java_android_graphics_Bitmap_native_1get_1height(JNIEnv *env, jclass class, jlong texture_ptr)
{
return gdk_texture_get_height(GDK_TEXTURE(_PTR(texture_ptr)));
}
JNIEXPORT jlong JNICALL Java_android_graphics_Bitmap_native_1erase_1color(JNIEnv *env, jclass class, jint color, jint width, jint height)
{
GdkRGBA rgba = {
.red = ((color >> 16) & 0xFF) / 255.f,
.green = ((color >> 8) & 0xFF) / 255.f,
.blue = ((color >> 0) & 0xFF) / 255.f,
.alpha = ((color >> 24) & 0xFF) / 255.f,
};
graphene_rect_t bounds = GRAPHENE_RECT_INIT(0, 0, width, height);
GtkSnapshot *snapshot = gtk_snapshot_new();
gtk_snapshot_append_color(snapshot, &rgba, &bounds);
return _INTPTR(snapshot);
}
JNIEXPORT void JNICALL Java_android_graphics_Bitmap_native_1recycle(JNIEnv *env, jclass class, jlong texture_ptr, jlong snapshot_ptr)
{
if (texture_ptr)
g_object_unref(GDK_TEXTURE(_PTR(texture_ptr)));
if (snapshot_ptr)
g_object_unref(GTK_SNAPSHOT(_PTR(snapshot_ptr)));
}

View File

@@ -56,5 +56,7 @@ JNIEXPORT jlong JNICALL Java_android_graphics_BitmapFactory_nativeDecodeStream(J
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, NULL);
g_object_unref(stream);
return _INTPTR(pixbuf);
GdkTexture *texture = gdk_texture_new_for_pixbuf(pixbuf);
g_object_unref(pixbuf);
return _INTPTR(texture);
}

View File

@@ -4,10 +4,8 @@
#include "include/c/sk_font.h"
#include "include/c/sk_paint.h"
#include "include/c/sk_path.h"
#include "../defines.h"
#include "../util.h"
#include "../generated_headers/android_graphics_GskCanvas.h"
@@ -51,17 +49,18 @@ JNIEXPORT void JNICALL Java_android_graphics_GskCanvas_native_1drawRect(JNIEnv *
JNIEXPORT void JNICALL Java_android_graphics_GskCanvas_native_1drawPath(JNIEnv *env, jclass this_class, jlong snapshot_ptr, jlong path_ptr, jlong paint_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_iterator_t *iterator = sk_path_create_iter(path, 0);
sk_path_verb_t verb;
sk_point_t line[4];
while ((verb = sk_path_iter_next(iterator, line)) != DONE_SK_PATH_VERB) {
// TODO: use GskPath to support other verbs
if (verb == LINE_SK_PATH_VERB) {
Java_android_graphics_GskCanvas_native_1drawLine(env, this_class, snapshot_ptr, line[0].x, line[0].y, line[1].x, line[1].y, paint_ptr);
}
GtkSnapshot *snapshot = GTK_SNAPSHOT(_PTR(snapshot_ptr));
GskPath *path = _PTR(path_ptr);
sk_paint_t *paint = (sk_paint_t *)_PTR(paint_ptr);
GdkRGBA gdk_color;
sk_paint_get_color4f(paint, (sk_color4f_t *)&gdk_color);
sk_paint_style_t style = sk_paint_get_style(paint);
if (style == STROKE_SK_PAINT_STYLE || style == STROKE_AND_FILL_SK_PAINT_STYLE) {
gtk_snapshot_append_stroke(snapshot, path, gsk_stroke_new(2), &gdk_color);
}
if (style == FILL_SK_PAINT_STYLE || style == STROKE_AND_FILL_SK_PAINT_STYLE) {
gtk_snapshot_append_fill(snapshot, path, GSK_FILL_RULE_WINDING, &gdk_color);
}
sk_path_iter_destroy(iterator);
}
JNIEXPORT void JNICALL Java_android_graphics_GskCanvas_native_1translate(JNIEnv *env, jclass this_class, jlong snapshot_ptr, jfloat dx, jfloat dy)

View File

@@ -1,216 +1,142 @@
#include "../sk_area/include/c/sk_path.h"
#include <graphene.h>
#include <gsk/gsk.h>
#include "../defines.h"
#include "../util.h"
#include "../generated_headers/android_graphics_Path.h"
#include "include/c/sk_types.h"
JNIEXPORT jlong JNICALL Java_android_graphics_Path_init1(JNIEnv *env, jclass class)
JNIEXPORT jlong JNICALL Java_android_graphics_Path_native_1create_1builder(JNIEnv *env, jclass this, jlong path_ptr)
{
return _INTPTR(sk_path_new());
GskPathBuilder *builder = gsk_path_builder_new();
if (path_ptr) {
GskPath *path = _PTR(path_ptr);
gsk_path_builder_add_path(builder, path);
gsk_path_unref(path);
}
return _INTPTR(builder);
}
JNIEXPORT jlong JNICALL Java_android_graphics_Path_init2(JNIEnv *env, jclass class, jlong path_ptr)
JNIEXPORT jlong JNICALL Java_android_graphics_Path_native_1create_1path(JNIEnv *env, jclass this, jlong builder_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
return _INTPTR(sk_path_clone(path));
GskPathBuilder *builder = _PTR(builder_ptr);
if (!builder)
builder = gsk_path_builder_new();
return _INTPTR(gsk_path_builder_free_to_path(builder));
}
JNIEXPORT void JNICALL Java_android_graphics_Path_finalizer(JNIEnv *env, jclass class, jlong path_ptr)
JNIEXPORT jlong JNICALL Java_android_graphics_Path_native_1ref_1path(JNIEnv *env, jclass this, jlong path_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
if (path_ptr != -1)
sk_path_delete(path);
return _INTPTR(gsk_path_ref(_PTR(path_ptr)));
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1rCubicTo(JNIEnv *env, jclass class, jlong path_ptr, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1reset(JNIEnv *env, jclass this, jlong path_ptr, jlong builder_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_rcubic_to(path, x1, y1, x2, y2, x3, y3);
if (path_ptr)
gsk_path_unref(_PTR(path_ptr));
if (builder_ptr)
gsk_path_builder_unref(_PTR(builder_ptr));
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1rLineTo(JNIEnv *env, jclass class, jlong path_ptr, jfloat x, jfloat y)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1close(JNIEnv *env, jclass this, jlong builder_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_rline_to(path, x, y);
gsk_path_builder_close(_PTR(builder_ptr));
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1cubicTo(JNIEnv *env, jclass class, jlong path_ptr, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1move_1to(JNIEnv *env, jclass this, jlong builder_ptr, jfloat x, jfloat y)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_cubic_to(path, x1, y1, x2, y2, x3, y3);
gsk_path_builder_move_to(_PTR(builder_ptr), x, y);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1addPath__JJJ(JNIEnv *env, jclass class, jlong path_ptr, jlong src_ptr, jlong matrix)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1line_1to(JNIEnv *env, jclass this, jlong builder_ptr, jfloat x, jfloat y)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_t *src = (sk_path_t *)_PTR(src_ptr);
sk_path_add_path(path, src, APPEND_SK_PATH_ADD_MODE);
gsk_path_builder_line_to(_PTR(builder_ptr), x, y);
}
JNIEXPORT jint JNICALL Java_android_graphics_Path_native_1getFillType(JNIEnv *env, jclass class, jlong path_ptr)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1cubic_1to(JNIEnv *env, jclass this, jlong builder_ptr, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
return sk_path_get_filltype(path);
gsk_path_builder_cubic_to(_PTR(builder_ptr), x1, y1, x2, y2, x3, y3);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1reset(JNIEnv *env, jclass class, jlong path_ptr)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1quad_1to(JNIEnv *env, jclass this, jlong builder_ptr, jfloat x1, jfloat y1, jfloat x2, jfloat y2)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_reset(path);
gsk_path_builder_quad_to(_PTR(builder_ptr), x1, y1, x2, y2);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1setFillType(JNIEnv *env, jclass class, jlong path_ptr, jint ft)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1rel_1move_1to(JNIEnv *env, jclass this, jlong builder_ptr, jfloat x, jfloat y)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_set_filltype(path, ft);
gsk_path_builder_rel_move_to(_PTR(builder_ptr), x, y);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1moveTo(JNIEnv *env, jclass class, jlong path_ptr, jfloat x, jfloat y)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1rel_1line_1to(JNIEnv *env, jclass this, jlong builder_ptr, jfloat x, jfloat y)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_move_to(path, x, y);
gsk_path_builder_rel_line_to(_PTR(builder_ptr), x, y);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1close(JNIEnv *env, jclass class, jlong path_ptr)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1rel_1cubic_1to(JNIEnv *env, jclass this, jlong builder_ptr, jfloat x1, jfloat y1, jfloat x2, jfloat y2, jfloat x3, jfloat y3)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_close(path);
gsk_path_builder_rel_cubic_to(_PTR(builder_ptr), x1, y1, x2, y2, x3, y3);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1lineTo(JNIEnv *env, jclass class, jlong path_ptr, jfloat x, jfloat y)
struct path_foreach_data {
GskPathBuilder *builder;
graphene_matrix_t *matrix;
graphene_point_t tmp_pts[4];
};
static gboolean path_foreach_transform(GskPathOperation op, const graphene_point_t* pts, gsize n_pts, float weight, gpointer user_data)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_line_to(path, x, y);
struct path_foreach_data *data = user_data;
for (gsize i = 0; i < n_pts; i++) {
graphene_matrix_transform_point(data->matrix, &pts[i], &data->tmp_pts[i]);
}
switch (op) {
case GSK_PATH_MOVE:
gsk_path_builder_move_to(data->builder, data->tmp_pts[0].x, data->tmp_pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close(data->builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to(data->builder, data->tmp_pts[1].x, data->tmp_pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to(data->builder, data->tmp_pts[1].x, data->tmp_pts[1].y, data->tmp_pts[2].x, data->tmp_pts[2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to(data->builder, data->tmp_pts[1].x, data->tmp_pts[1].y, data->tmp_pts[2].x, data->tmp_pts[2].y, data->tmp_pts[3].x, data->tmp_pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to(data->builder, data->tmp_pts[1].x, data->tmp_pts[1].y, data->tmp_pts[2].x, data->tmp_pts[2].y, weight);
break;
}
return TRUE;
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1rewind(JNIEnv *env, jclass class, jlong path_ptr)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1add_1path(JNIEnv *env, jclass this, jlong builder_ptr, jlong path_ptr, jlong matrix_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_rewind(path);
}
JNIEXPORT jboolean JNICALL Java_android_graphics_Path_native_1isEmpty(JNIEnv *env, jclass class, jlong path_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
return !sk_path_count_points(path) && !sk_path_count_verbs(path);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1computeBounds(JNIEnv *env, jclass class, jlong path_ptr, jobject rect)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_rect_t bounds;
sk_path_get_bounds(path, &bounds);
_SET_FLOAT_FIELD(rect, "left", bounds.left);
_SET_FLOAT_FIELD(rect, "top", bounds.top);
_SET_FLOAT_FIELD(rect, "right", bounds.right);
_SET_FLOAT_FIELD(rect, "bottom", bounds.bottom);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1arcTo(JNIEnv *env, jclass class, jlong path_ptr, jobject oval, jfloat startAngle, jfloat sweepAngle, jboolean forceMoveTo)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
float left = _GET_FLOAT_FIELD(oval, "left");
float top = _GET_FLOAT_FIELD(oval, "top");
float right = _GET_FLOAT_FIELD(oval, "right");
float bottom = _GET_FLOAT_FIELD(oval, "bottom");
sk_path_arc_to_with_oval(path, &(sk_rect_t){left, top, right, bottom}, startAngle, sweepAngle, forceMoveTo);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1addRect__JLandroid_graphics_RectF_2I(JNIEnv *env, jclass class, jlong path_ptr, jobject rect, jint dir)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
float left = _GET_FLOAT_FIELD(rect, "left");
float top = _GET_FLOAT_FIELD(rect, "top");
float right = _GET_FLOAT_FIELD(rect, "right");
float bottom = _GET_FLOAT_FIELD(rect, "bottom");
sk_path_add_rect(path, &(sk_rect_t){left, top, right, bottom}, (sk_path_direction_t)dir);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1transform__JJ(JNIEnv *env, jclass class, jlong path_ptr, jlong matrix_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
GskPathBuilder *builder = _PTR(builder_ptr);
GskPath *path = _PTR(path_ptr);
graphene_matrix_t *matrix = (graphene_matrix_t *)_PTR(matrix_ptr);
float v[16];
graphene_matrix_to_float(matrix, v);
sk_matrix_t m = {v[0], v[4], v[12],
v[1], v[5], v[13],
v[3], v[7], v[15]};
sk_path_transform(path, &m);
if (graphene_matrix_is_identity(matrix)) {
gsk_path_builder_add_path(builder, path);
} else {
struct path_foreach_data data = {
.builder = builder,
.matrix = matrix,
};
gsk_path_foreach(path, GSK_PATH_FOREACH_ALLOW_QUAD | GSK_PATH_FOREACH_ALLOW_CUBIC | GSK_PATH_FOREACH_ALLOW_CONIC, path_foreach_transform, &data);
}
}
JNIEXPORT jboolean JNICALL Java_android_graphics_Path_native_1op(JNIEnv *env, jclass class, jlong path_ptr, jlong other_ptr, jint op, jlong result_ptr)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1add_1rect(JNIEnv *env, jclass this, jlong builder_ptr, jfloat left, jfloat top, jfloat right, jfloat bottom)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_t *other = (sk_path_t *)_PTR(other_ptr);
sk_path_t *result = (sk_path_t *)_PTR(result_ptr);
return sk_pathop_op(path, other, (sk_pathop_t)op, result);
gsk_path_builder_add_rect(_PTR(builder_ptr), &GRAPHENE_RECT_INIT(left, top, right-left, bottom-top));
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1quadTo(JNIEnv *env, jclass class, jlong path_ptr, jfloat x1, jfloat y1, jfloat x2, jfloat y2)
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1get_1bounds(JNIEnv *env, jclass this, jlong path_ptr, jobject bounds)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_quad_to(path, x1, y1, x2, y2);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1rQuadTo(JNIEnv *env, jclass class, jlong path_ptr, jfloat dx1, jfloat dy1, jfloat dx2, jfloat dy2)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_rquad_to(path, dx1, dy1, dx2, dy2);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1rMoveTo(JNIEnv *env, jclass class, jlong path_ptr, jfloat dx, jfloat dy)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_rmove_to(path, dx, dy);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1addRoundRect__JFFFFFFI(JNIEnv *env, jclass class, jlong path_ptr, jfloat left, jfloat top, jfloat right, jfloat bottom, jfloat rx, jfloat ry, jint dir)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_add_rounded_rect(path, &(sk_rect_t){left, top, right, bottom}, rx, ry, (sk_path_direction_t)dir);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1addRoundRect__JLandroid_graphics_RectF_2_3FI(JNIEnv *env, jclass class, jlong path_ptr, jobject rect, jfloatArray radii, jint dir)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
float left = _GET_FLOAT_FIELD(rect, "left");
float top = _GET_FLOAT_FIELD(rect, "top");
float right = _GET_FLOAT_FIELD(rect, "right");
float bottom = _GET_FLOAT_FIELD(rect, "bottom");
jfloat *radii_array = (*env)->GetFloatArrayElements(env, radii, 0);
sk_path_add_rounded_rect(path, &(sk_rect_t){left, top, right, bottom}, radii_array[0], radii_array[1], (sk_path_direction_t)dir);
(*env)->ReleaseFloatArrayElements(env, radii, radii_array, 0);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1addOval(JNIEnv *env, jclass class, jlong path_ptr, jobject rect, jint dir)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
float left = _GET_FLOAT_FIELD(rect, "left");
float top = _GET_FLOAT_FIELD(rect, "top");
float right = _GET_FLOAT_FIELD(rect, "right");
float bottom = _GET_FLOAT_FIELD(rect, "bottom");
sk_path_add_oval(path, &(sk_rect_t){left, top, right, bottom}, (sk_path_direction_t)dir);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1addCircle(JNIEnv *env, jclass class, jlong path_ptr, jfloat x, jfloat y, jfloat radius, jint dir)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_add_circle(path, x, y, radius, (sk_path_direction_t)dir);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1addPath__JJ(JNIEnv *env, jclass class, jlong path_ptr, jlong src_ptr)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_t *src = (sk_path_t *)_PTR(src_ptr);
sk_path_add_path(path, src, APPEND_SK_PATH_ADD_MODE);
}
JNIEXPORT void JNICALL Java_android_graphics_Path_native_1addPath__JJFF(JNIEnv *env, jclass class, jlong path_ptr, jlong src_ptr, jfloat dx, jfloat dy)
{
sk_path_t *path = (sk_path_t *)_PTR(path_ptr);
sk_path_t *src = (sk_path_t *)_PTR(src_ptr);
sk_path_add_path_offset(path, src, dx, dy, APPEND_SK_PATH_ADD_MODE);
graphene_rect_t rect;
gsk_path_get_bounds(_PTR(path_ptr), &rect);
_SET_FLOAT_FIELD(bounds, "left", rect.origin.x);
_SET_FLOAT_FIELD(bounds, "top", rect.origin.y);
_SET_FLOAT_FIELD(bounds, "right", rect.origin.x + rect.size.width);
_SET_FLOAT_FIELD(bounds, "bottom", rect.origin.y + rect.size.height);
}