From 176405ed45792bec9279a949710c48424472faca Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Sat, 30 Dec 2023 13:47:04 +0100 Subject: [PATCH] NinePatchPaintable: new GdkPaintable class to render .9.png files --- meson.build | 1 + .../graphics/NinePatchPaintable.c | 139 ++++++++++++++++++ .../graphics/NinePatchPaintable.h | 33 +++++ .../android_graphics_drawable_Drawable.c | 11 +- 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/api-impl-jni/graphics/NinePatchPaintable.c create mode 100644 src/api-impl-jni/graphics/NinePatchPaintable.h diff --git a/meson.build b/meson.build index 2b636080..834125b7 100644 --- a/meson.build +++ b/meson.build @@ -83,6 +83,7 @@ libtranslationlayer_so = shared_library('translation_layer_main', [ 'src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c', 'src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.c', 'src/api-impl-jni/graphics/android_graphics_Typeface.c', + 'src/api-impl-jni/graphics/NinePatchPaintable.c', 'src/api-impl-jni/media/android_media_MediaCodec.c', 'src/api-impl-jni/android_content_res_AssetManager.c', 'src/api-impl-jni/audio/android_media_AudioTrack.c', diff --git a/src/api-impl-jni/graphics/NinePatchPaintable.c b/src/api-impl-jni/graphics/NinePatchPaintable.c new file mode 100644 index 00000000..44cd18a9 --- /dev/null +++ b/src/api-impl-jni/graphics/NinePatchPaintable.c @@ -0,0 +1,139 @@ +#include +#include + +#include "NinePatchPaintable.h" + +enum { + // The 9 patch segment is not a solid color. + NO_COLOR = 0x00000001, + // The 9 patch segment is completely transparent. + TRANSPARENT_COLOR = 0x00000000 +}; + +static void ninepatch_paintable_snapshot(GdkPaintable *paintable, GdkSnapshot *snapshot, double width, double height) +{ + int i, j; + NinePatchPaintable *ninepatch = NINEPATCH_PAINTABLE(paintable); + struct Res_png_9patch *chunk = ninepatch->chunk; + if (chunk) { + int32_t *xDivs = (void *)chunk + chunk->xDivsOffset; + int32_t *yDivs = (void *)chunk + chunk->yDivsOffset; + int32_t *color = (void *)chunk + chunk->colorsOffset; + float strech_factor_width = (width - (ninepatch->width - ninepatch->strechy_width)) / ninepatch->strechy_width; + float strech_factor_height = (height - (ninepatch->height - ninepatch->strechy_height)) / ninepatch->strechy_height; + + graphene_rect_t rect; + GdkRGBA rgba; + for (j = 0, rect.origin.y = 0; j < chunk->numYDivs+1; j++, rect.origin.y += rect.size.height) { + int div_start = j ? yDivs[j-1] : 0; + int div_end = (j == chunk->numYDivs) ? ninepatch->height : yDivs[j]; + float patch_height = div_end - div_start; + if (j%2) // odd sections are stretchable + patch_height *= strech_factor_height; + rect.size.height = patch_height; + if (!patch_height) // skip empty sections + continue; + for (i = 0, rect.origin.x = 0; i < chunk->numXDivs+1; i++, rect.origin.x += rect.size.width) { + int div_start = i ? xDivs[i-1] : 0; + int div_end = (i == chunk->numXDivs) ? ninepatch->width : xDivs[i]; + float patch_width = div_end - div_start; + if (i%2) // odd sections are stretchable + patch_width *= strech_factor_width; + rect.size.width = patch_width; + if (!patch_width) // skip empty sections + continue; + if (*color == NO_COLOR) { + printf("NinePatchPaintable: warning NO_COLOR sections not yet implemented"); + } else if (*color != TRANSPARENT_COLOR) { + rgba.red = (*color >> 24 & 0xFF) / 255.f; + rgba.green = (*color >> 16 & 0xFF) / 255.f; + rgba.blue = (*color >> 8 & 0xFF) / 255.f; + rgba.alpha = (*color & 0xFF) / 255.f; + gtk_snapshot_append_color(snapshot, &rgba, &rect); + } + color++; + } + } + } +} + +static GdkPaintableFlags ninepatch_paintable_get_flags(GdkPaintable *paintable) +{ + return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE; +} + +static void ninepatch_paintable_init(NinePatchPaintable *ninepatch_paintable) +{ +} + +static void ninepatch_paintable_paintable_init(GdkPaintableInterface *iface) +{ + iface->snapshot = ninepatch_paintable_snapshot; + iface->get_flags = ninepatch_paintable_get_flags; +} + +static void ninepatch_paintable_class_init(NinePatchPaintableClass *class) +{ +} + +G_DEFINE_TYPE_WITH_CODE(NinePatchPaintable, ninepatch_paintable, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(GDK_TYPE_PAINTABLE, ninepatch_paintable_paintable_init)) + +GdkPaintable *ninepatch_paintable_new(const char *path) +{ + struct Res_png_9patch *chunk = NULL; + int width = 0; + int height = 0; + int strechy_width = 0; + int strechy_height = 0; + uint8_t buf[8]; + int i; + FILE *f = fopen(path, "r"); + fread(buf, 1, 8, f); + if (buf[0] != 0x89 || buf[1] != 'P' || buf[2] != 'N' || buf[3] != 'G') { + return NULL; + } + while (!feof(f)) { + fread(buf, 1, 8, f); + int size = ntohl(((uint32_t *)buf)[0]); + size += 4; // 4 bytes checksum + + if (!strncmp((char *)&buf[4], "npTc", 4)) { + chunk = malloc(size); + fread(chunk, 1, size, f); + break; + } else { + fseek(f, size, SEEK_CUR); + } + } + fclose(f); + + if (!chunk) + return NULL; + + int32_t *xDivs = (void *)chunk + chunk->xDivsOffset; + int32_t *yDivs = (void *)chunk + chunk->yDivsOffset; + + for (i = 0; i < chunk->numXDivs; i++) + xDivs[i] = ntohl(xDivs[i]); + for (i = 0; i < chunk->numYDivs; i++) + yDivs[i] = ntohl(yDivs[i]); + + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, NULL); + width = gdk_pixbuf_get_width(pixbuf); + height = gdk_pixbuf_get_height(pixbuf); + g_object_unref(pixbuf); + + for (i = 1; i < chunk->numXDivs+1; i+=2) + strechy_width += (i == chunk->numXDivs ? width : xDivs[i]) - xDivs[i-1]; + for (i = 1; i < chunk->numYDivs+1; i+=2) + strechy_height += (i == chunk->numYDivs ? height : yDivs[i]) - yDivs[i-1]; + + NinePatchPaintable *ninepatch = NINEPATCH_PAINTABLE(g_object_new(ninepatch_paintable_get_type(), NULL)); + ninepatch->chunk = chunk; + ninepatch->width = width; + ninepatch->height = height; + ninepatch->strechy_width = strechy_width; + ninepatch->strechy_height = strechy_height; + return GDK_PAINTABLE(ninepatch); +} diff --git a/src/api-impl-jni/graphics/NinePatchPaintable.h b/src/api-impl-jni/graphics/NinePatchPaintable.h new file mode 100644 index 00000000..532f0ffa --- /dev/null +++ b/src/api-impl-jni/graphics/NinePatchPaintable.h @@ -0,0 +1,33 @@ +#include + +struct Res_png_9patch +{ + int8_t wasDeserialized; + uint8_t numXDivs; + uint8_t numYDivs; + uint8_t numColors; + // The offset (from the start of this structure) to the xDivs & yDivs + // array for this 9patch. To get a pointer to this array, call + // getXDivs or getYDivs. Note that the serialized form for 9patches places + // the xDivs, yDivs and colors arrays immediately after the location + // of the Res_png_9patch struct. + uint32_t xDivsOffset; + uint32_t yDivsOffset; + int32_t paddingLeft, paddingRight; + int32_t paddingTop, paddingBottom; + // The offset (from the start of this structure) to the colors array + // for this 9patch. + uint32_t colorsOffset; +} __attribute__((packed)); + +struct _NinePatchPaintable { + GObject parent_instance; + struct Res_png_9patch *chunk; + int width; + int height; + int strechy_width; + int strechy_height; +}; +G_DECLARE_FINAL_TYPE(NinePatchPaintable, ninepatch_paintable, NINEPATCH, PAINTABLE, GObject) + +GdkPaintable *ninepatch_paintable_new(const char *path); diff --git a/src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c b/src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c index 7af21f81..f8969ff2 100644 --- a/src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c +++ b/src/api-impl-jni/graphics/android_graphics_drawable_Drawable.c @@ -1,12 +1,21 @@ #include +#include #include "../defines.h" +#include "NinePatchPaintable.h" #include "../generated_headers/android_graphics_drawable_Drawable.h" JNIEXPORT jlong JNICALL Java_android_graphics_drawable_Drawable_native_1paintable_1from_1path(JNIEnv *env, jclass class, jstring pathStr) { const char *path = (*env)->GetStringUTFChars(env, pathStr, NULL); + GdkPaintable *paintable = NULL; - GdkPaintable *paintable = GDK_PAINTABLE(gdk_texture_new_from_filename(path, NULL)); + // check if path ends with .9.png + int len = strlen(path); + if (len >= 6 && !strcmp(path + len - 6, ".9.png")) { + paintable = ninepatch_paintable_new(path); + } + if (!paintable) + paintable = GDK_PAINTABLE(gdk_texture_new_from_filename(path, NULL)); (*env)->ReleaseStringUTFChars(env, pathStr, path); return _INTPTR(paintable);