NinePatchPaintable: new GdkPaintable class to render .9.png files

This commit is contained in:
Julian Winkler
2023-12-30 13:47:04 +01:00
parent ca3c17d773
commit 176405ed45
4 changed files with 183 additions and 1 deletions

View File

@@ -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_Drawable.c',
'src/api-impl-jni/graphics/android_graphics_drawable_DrawableContainer.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/android_graphics_Typeface.c',
'src/api-impl-jni/graphics/NinePatchPaintable.c',
'src/api-impl-jni/media/android_media_MediaCodec.c', 'src/api-impl-jni/media/android_media_MediaCodec.c',
'src/api-impl-jni/android_content_res_AssetManager.c', 'src/api-impl-jni/android_content_res_AssetManager.c',
'src/api-impl-jni/audio/android_media_AudioTrack.c', 'src/api-impl-jni/audio/android_media_AudioTrack.c',

View File

@@ -0,0 +1,139 @@
#include <arpa/inet.h>
#include <gtk/gtk.h>
#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);
}

View File

@@ -0,0 +1,33 @@
#include <gdk/gdk.h>
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);

View File

@@ -1,12 +1,21 @@
#include <gdk/gdk.h> #include <gdk/gdk.h>
#include <string.h>
#include "../defines.h" #include "../defines.h"
#include "NinePatchPaintable.h"
#include "../generated_headers/android_graphics_drawable_Drawable.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) { 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); 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); (*env)->ReleaseStringUTFChars(env, pathStr, path);
return _INTPTR(paintable); return _INTPTR(paintable);