From b9c191d690fd5d1480858469df23cc4509996fae Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 23 Apr 2024 14:30:38 +0800 Subject: Implement face stipples on Android * .gitignore: * java/Makefile.in: Fix typos. * java/org/gnu/emacs/EmacsFillRectangle.java (perform): Call blitOpaqueStipple if filling an unobscured rectangle with an opaque stipple. * java/org/gnu/emacs/EmacsGC.java (EmacsGC) : New field. (markDirty): Synchronize the current stipple with tileObject. (prepareStipple, blitOpaqueStipple): New functions. * java/org/gnu/emacs/EmacsService.java (EmacsService) : New static field. (onCreate): Set it. * src/android.c (android_create_bitmap_from_data): Correct order of arguments to android_create_pixmap_from_bitmap_data. (HAS_BUILTIN_TRAP): Delete macro. (emacs_abort): Always induce backtraces by means of a NULL pointer deference. * src/dispextern.h (Emacs_GC, Emacs_Rectangle, GCForeground) (GCBackground, GCFillStyle, GCStipple, FillOpaqueStipple) [HAVE_ANDROID]: Define to their Android counterparts rather than simulating their existence. * src/epaths.in: Set bitmap path to /assets/bitmaps on Android. * src/image.c (image_bitmap_pixmap): Also enable when HAVE_ANDROID. * src/sfntfont-android.c (sfntfont_android_put_glyphs): Assert that this is never called to draw a stippled background. * src/xfaces.c (x_create_gc) [HAVE_ANDROID]: Redefine as wrapper around android_create_gc. (prepare_face_for_display) [HAVE_ANDROID]: Enable stipples. --- java/org/gnu/emacs/EmacsFillRectangle.java | 15 ++--- java/org/gnu/emacs/EmacsGC.java | 94 ++++++++++++++++++++++++++++-- java/org/gnu/emacs/EmacsService.java | 7 ++- 3 files changed, 104 insertions(+), 12 deletions(-) (limited to 'java/org/gnu') diff --git a/java/org/gnu/emacs/EmacsFillRectangle.java b/java/org/gnu/emacs/EmacsFillRectangle.java index ca87c06c014..f338a54f97b 100644 --- a/java/org/gnu/emacs/EmacsFillRectangle.java +++ b/java/org/gnu/emacs/EmacsFillRectangle.java @@ -40,22 +40,23 @@ public final class EmacsFillRectangle Canvas canvas; Bitmap clipBitmap; - /* TODO implement stippling. */ - if (gc.fill_style == EmacsGC.GC_FILL_OPAQUE_STIPPLED) - return; - canvas = drawable.lockCanvas (gc); if (canvas == null) return; - paint = gc.gcPaint; rect = new Rect (x, y, x + width, y + height); + paint = gc.gcPaint; paint.setStyle (Paint.Style.FILL); if (gc.clip_mask == null) - canvas.drawRect (rect, paint); + { + if (gc.fill_style != EmacsGC.GC_FILL_OPAQUE_STIPPLED) + canvas.drawRect (rect, paint); + else + gc.blitOpaqueStipple (canvas, rect); + } else { /* Drawing with a clip mask involves calculating the @@ -113,4 +114,4 @@ public final class EmacsFillRectangle drawable.damageRect (rect); } -} +}; diff --git a/java/org/gnu/emacs/EmacsGC.java b/java/org/gnu/emacs/EmacsGC.java index e45f0666fe2..96df0c61ca6 100644 --- a/java/org/gnu/emacs/EmacsGC.java +++ b/java/org/gnu/emacs/EmacsGC.java @@ -22,10 +22,19 @@ package org.gnu.emacs; import android.graphics.Rect; import android.graphics.Paint; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffXfermode; +import android.graphics.Shader.TileMode; import android.graphics.Xfermode; +import android.graphics.drawable.BitmapDrawable; + +import android.os.Build; + /* X like graphics context structures. Keep the enums in synch with androidgui.h! */ @@ -47,6 +56,9 @@ public final class EmacsGC extends EmacsHandleObject public EmacsPixmap clip_mask, stipple; public Paint gcPaint; + /* Drawable object for rendering the stiple bitmap. */ + public BitmapDrawable tileObject; + /* ID incremented every time the clipping rectangles of any GC changes. */ private static long clip_serial; @@ -86,6 +98,7 @@ public final class EmacsGC extends EmacsHandleObject markDirty (boolean clipRectsChanged) { int i; + Bitmap stippleBitmap; if (clipRectsChanged) { @@ -110,12 +123,85 @@ public final class EmacsGC extends EmacsHandleObject gcPaint.setColor (foreground | 0xff000000); gcPaint.setXfermode (function == GC_XOR ? xorAlu : srcInAlu); + + /* Update the stipple object with the new stipple bitmap, or delete + it if the stipple has been cleared on systems too old to support + modifying such objects. */ + + if (stipple != null) + { + stippleBitmap = stipple.getBitmap (); + + /* Allocate a new tile object if none is already present or it + cannot be reconfigured. */ + if ((tileObject == null) + || (Build.VERSION.SDK_INT < Build.VERSION_CODES.S)) + { + tileObject = new BitmapDrawable (EmacsService.resources, + stippleBitmap); + tileObject.setTileModeXY (TileMode.MIRROR, TileMode.MIRROR); + } + else + /* Otherwise, update the existing tile object with the new + bitmap. */ + tileObject.setBitmap (stippleBitmap); + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + && tileObject != null) + tileObject.setBitmap (null); + else if (tileObject != null) + tileObject = null; } - public void - resetXfermode () + /* Prepare the tile object to draw a stippled image onto a section of + a drawable defined by RECT. It is an error to call this function + unless the `stipple' field of the GContext is set. */ + + private void + prepareStipple (Rect rect) { - gcPaint.setXfermode (function == GC_XOR - ? xorAlu : srcInAlu); + int sx, sy; /* Stipple origin. */ + int bw, bh; /* Stipple size. */ + Bitmap bitmap; + Rect boundsRect; + + /* Retrieve the dimensions of the stipple bitmap, which doubles as + the unit of advance for this stipple. */ + bitmap = tileObject.getBitmap (); + bw = bitmap.getWidth (); + bh = bitmap.getHeight (); + + /* Align the lower left corner of the bounds rectangle to the + initial position of the stipple. */ + sx = (rect.left % bw) * -1 + (-ts_origin_x % bw) * -1; + sy = (rect.top % bh) * -1 + (-ts_origin_y % bh) * -1; + boundsRect = new Rect (rect.left + sx, rect.top + sy, + rect.right, rect.bottom); + tileObject.setBounds (boundsRect); + } + + /* Fill the rectangle BOUNDS in the provided CANVAS with the stipple + pattern defined for this GContext, in the foreground color where + the pattern is on, and in the background color where off. */ + + protected void + blitOpaqueStipple (Canvas canvas, Rect rect) + { + ColorFilter filter; + + prepareStipple (rect); + filter = new PorterDuffColorFilter (foreground | 0xff000000, + Mode.SRC_IN); + tileObject.setColorFilter (filter); + + canvas.save (); + canvas.clipRect (rect); + + tileObject.draw (canvas); + filter = new PorterDuffColorFilter (background | 0xff000000, + Mode.SRC_OUT); + tileObject.setColorFilter (filter); + tileObject.draw (canvas); + canvas.restore (); } }; diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java index 2d4079c11b0..8e459ce4cdc 100644 --- a/java/org/gnu/emacs/EmacsService.java +++ b/java/org/gnu/emacs/EmacsService.java @@ -64,6 +64,7 @@ import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Configuration; +import android.content.res.Resources; import android.hardware.input.InputManager; @@ -146,6 +147,9 @@ public final class EmacsService extends Service thread. */ private Thread mainThread; + /* "Resources" object required by GContext bookkeeping. */ + public static Resources resources; + static { servicingQuery = new AtomicInteger (); @@ -238,10 +242,11 @@ public final class EmacsService extends Service super.onCreate (); SERVICE = this; + resources = getResources (); handler = new Handler (Looper.getMainLooper ()); manager = getAssets (); app_context = getApplicationContext (); - metrics = getResources ().getDisplayMetrics (); + metrics = resources.getDisplayMetrics (); pixelDensityX = metrics.xdpi; pixelDensityY = metrics.ydpi; tempScaledDensity = ((getScaledDensity (metrics) -- cgit v1.2.1