diff --git a/JavaFrameBuffer/.settings/org.eclipse.jdt.core.prefs b/JavaFrameBuffer/.settings/org.eclipse.jdt.core.prefs deleted file mode 100755 index 0f8f6c5..0000000 --- a/JavaFrameBuffer/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,12 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.7 -org.eclipse.objectteams.otdt.compiler.option.pure_java=enabled diff --git a/JavaFrameBuffer/libFrameBufferJNI.jnilib b/JavaFrameBuffer/libFrameBufferJNI.jnilib deleted file mode 100755 index 3345cc7..0000000 Binary files a/JavaFrameBuffer/libFrameBufferJNI.jnilib and /dev/null differ diff --git a/JavaFrameBuffer/libFrameBufferJNI.so b/JavaFrameBuffer/libFrameBufferJNI.so deleted file mode 100755 index d30de06..0000000 Binary files a/JavaFrameBuffer/libFrameBufferJNI.so and /dev/null differ diff --git a/JavaFrameBuffer/src/main/c/FrameBuffer.c b/JavaFrameBuffer/src/main/c/FrameBuffer.c deleted file mode 100755 index d9be549..0000000 --- a/JavaFrameBuffer/src/main/c/FrameBuffer.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * This file is the JNI C part of a Raspberry Pi FrameBuffer project. - * - * Created 2013 by Thomas Welsch (ttww@gmx.de). - * - * Do whatever you want to do with it :-) - * - * This code transfers an Java BufferedImage ARGB data array to a FrameBuffer device - * (e.g. SPI-Displays like http://www.sainsmart.com/blog/ada/). - * - * For testing purpose a dummy device is supported (via the devicename "dummy_160x128" instead of "/dev/fb1"). - * -**/ - -#include -#include -#include -#include -#include - -#ifdef __linux - #include - #include -#endif - -#include - -#include "org_tw_pi_framebuffer_FrameBuffer.h" - -// --------------------------------------------------------------------------------------------------------------------- -// Handle structur from Java: -// --------------------------------------------------------------------------------------------------------------------- -struct deviceInfo { - char *deviceName; // Device-Name from Java ("/dev/fb1" or "dummy_240x180")... - int fbfd; // File descriptor, 0 for dummy devices - - int width; - int height; - int bpp; // BitsPerPixel, 0 for dummy devices - - long int screensize; // Buffer size in bytes - - char *fbp; // MemoryMapped buffer - - unsigned int *currentScreen; // Last screen -}; - -// --------------------------------------------------------------------------------------------------------------------- -// long openDevice(String device); -// --------------------------------------------------------------------------------------------------------------------- -JNIEXPORT jlong JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_openDevice( - JNIEnv *env, jobject obj, jstring device) { - - jboolean isCopy; - - struct deviceInfo *di; - - di = malloc(sizeof(*di)); - memset(di, 0, sizeof(*di)); - - const char *s = (*env)->GetStringUTFChars(env, device, &isCopy); - di->deviceName = strdup(s); - if (isCopy) - (*env)->ReleaseStringUTFChars(env, device, s); - - // Open the file for reading and writing - if (*di->deviceName == '/') { - -#ifndef __linux - printf("Error: Framebuffer only under linux, use dummy device (dummy_220x440) instead %s\n",di->deviceName); - return (1); -#else - - struct fb_var_screeninfo vinfo; - struct fb_fix_screeninfo finfo; - - di->fbfd = open(di->deviceName, O_RDWR); - if (!di->fbfd) { - printf("Error: cannot open framebuffer device. %s\n", - di->deviceName); - return (1); - } - printf("The framebuffer device %s was opened successfully.\n", - di->deviceName); - - // Get fixed screen information - if (ioctl(di->fbfd, FBIOGET_FSCREENINFO, &finfo)) { - printf("Error reading fixed information.\n"); - return (2); - } - - // Get variable screen information - if (ioctl(di->fbfd, FBIOGET_VSCREENINFO, &vinfo)) { - printf("Error reading variable information.\n"); - return (3); - } - - di->width = vinfo.xres; - di->height = vinfo.yres; - di->bpp = vinfo.bits_per_pixel; - di->currentScreen = malloc(vinfo.xres * vinfo.yres * sizeof(int)); - - printf("%dx%d, %d bpp %ld bytes\n", vinfo.xres, vinfo.yres, - vinfo.bits_per_pixel, (long) finfo.smem_len); - - // map framebuffer to user memory - di->screensize = finfo.smem_len; - - di->fbp = (char*) mmap(0, di->screensize, PROT_READ | PROT_WRITE, - MAP_SHARED, di->fbfd, 0); - - if ((int) di->fbp == -1) { - printf("Failed to mmap.\n"); - return (4); - } -#endif - } else { - // Parse dummy_123x343 - sscanf(di->deviceName, "dummy_%dx%d", &di->width, &di->height); - di->bpp = 0; - di->currentScreen = malloc(di->width * di->height * sizeof(int)); - } - return (jlong) (intptr_t) di; -} - -// --------------------------------------------------------------------------------------------------------------------- -// void closeDevice(long di); -// --------------------------------------------------------------------------------------------------------------------- -JNIEXPORT void JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_closeDevice( - JNIEnv *env, jobject obj, jlong jdi) { - - struct deviceInfo *di = (struct deviceInfo *) (intptr_t) jdi; - - free(di->deviceName); - free(di->currentScreen); - - if (di->fbfd != 0) { - munmap(di->fbp, di->screensize); - close(di->fbfd); - } - - memset(di, 0, sizeof(*di)); // :-) -} - -// --------------------------------------------------------------------------------------------------------------------- -// int getDeviceWidth(long di); -// --------------------------------------------------------------------------------------------------------------------- -JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_getDeviceWidth( - JNIEnv *env, jobject obj, jlong jdi) { - - struct deviceInfo *di = (struct deviceInfo *) (intptr_t) jdi; - - return di->width; -} - -// --------------------------------------------------------------------------------------------------------------------- -// int getDeviceHeight(long di); -// --------------------------------------------------------------------------------------------------------------------- -JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_getDeviceHeight( - JNIEnv *env, jobject obj, jlong jdi) { - - struct deviceInfo *di = (struct deviceInfo *) (intptr_t) jdi; - - return di->height; -} - -// --------------------------------------------------------------------------------------------------------------------- -// int getDeviceBitsPerPixel(long di); -// --------------------------------------------------------------------------------------------------------------------- -JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_getDeviceBitsPerPixel( - JNIEnv *env, jobject obj, jlong jdi) { - - struct deviceInfo *di = (struct deviceInfo *) (intptr_t) jdi; - - return di->bpp; -} - -// --------------------------------------------------------------------------------------------------------------------- -// boolean updateDeviceBuffer(long di,int[] buffer); -// --------------------------------------------------------------------------------------------------------------------- -JNIEXPORT jboolean JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_updateDeviceBuffer( - JNIEnv *env, jobject obj, jlong jdi, jintArray buf) { - - struct deviceInfo *di = (struct deviceInfo *) (intptr_t) jdi; - int i; - jsize len = (*env)->GetArrayLength(env, buf); - unsigned int *current = di->currentScreen; - int updated = 0; - - -// See http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html -#define USE_CRITICAL // Avoid copy, but blocks gc.... -#ifdef USE_CRITICAL - jint *body = (*env)->GetPrimitiveArrayCritical(env, buf, 0); -#else - jboolean isCopy; - jint *body = (*env)->GetIntArrayElements(env, buf, &isCopy); -#endif - - switch (di->bpp) { - case 0: { - // Dummy Device - for (i = 0; i < len; i++) { - unsigned int u = body[i]; - - if (current[i] == u) - continue; - updated = 1; - current[i] = u; - } - } - break; - case 16: { - // Comment from: - // http://raspberrycompote.blogspot.de/2013/03/low-level-graphics-on-raspberry-pi-part_8.html - // - // The red value has 5 bits, so can be in the range 0-31, therefore divide the original 0-255 - // value by 8. It is stored in the first 5 bits, so multiply by 2048 or shift 11 bits left. - // The green has 6 bits, so can be in the range 0-63, divide by 4, and multiply by 32 or shift - // 5 bits left. Finally the blue has 5 bits and is stored at the last bits, so no need to move. - - unsigned short *p = (unsigned short *) di->fbp; - - for (i = 0; i < len; i++) { - unsigned int u = body[i]; - - if (current[i] == u) continue; - - updated = 1; - current[i] = u; - - unsigned char r = (u >> 16) & 0x0ff; - unsigned char g = (u >> 8) & 0x0ff; - unsigned char b = (u) & 0x0ff; - - //printf("%5d: %#x %3d %3d %3d %3d\n",i,u,a,r,g,b); - u = ((r / 8) << 11) + ((g / 4) << 5) + (b / 8); - - p[i] = u; - } - } - break; - /* - case 24: - { - // untested... - for (i=0; i>16) & 0x0ff; - unsigned char g = (u >> 8) & 0x0ff; - unsigned char b = (u) & 0x0ff; - - unsigned char *p = ((unsigned char *) di->fbp) + i + i + i; - *p++ = r; - *p++ = g; - *p = b; - } - } - break; - */ - default: - fprintf(stderr, "FrameBuffer depth %d not supported, use 16 !\n", - di->bpp); - } - -#ifdef USE_CRITICAL - (*env)->ReleasePrimitiveArrayCritical(env, buf, body, 0); -#else - if (isCopy) (*env)->ReleaseIntArrayElements(env, buf, body, 0); -#endif - - return updated; -} - diff --git a/JavaFrameBuffer/src/main/c/org_tw_pi_framebuffer_FrameBuffer.h b/JavaFrameBuffer/src/main/c/org_tw_pi_framebuffer_FrameBuffer.h deleted file mode 100755 index 903e132..0000000 --- a/JavaFrameBuffer/src/main/c/org_tw_pi_framebuffer_FrameBuffer.h +++ /dev/null @@ -1,63 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class org_tw_pi_framebuffer_FrameBuffer */ - -#ifndef _Included_org_tw_pi_framebuffer_FrameBuffer -#define _Included_org_tw_pi_framebuffer_FrameBuffer -#ifdef __cplusplus -extern "C" { -#endif -#undef org_tw_pi_framebuffer_FrameBuffer_FPS -#define org_tw_pi_framebuffer_FrameBuffer_FPS 50L -/* - * Class: org_tw_pi_framebuffer_FrameBuffer - * Method: openDevice - * Signature: (Ljava/lang/String;)J - */ -JNIEXPORT jlong JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_openDevice - (JNIEnv *, jobject, jstring); - -/* - * Class: org_tw_pi_framebuffer_FrameBuffer - * Method: closeDevice - * Signature: (J)V - */ -JNIEXPORT void JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_closeDevice - (JNIEnv *, jobject, jlong); - -/* - * Class: org_tw_pi_framebuffer_FrameBuffer - * Method: getDeviceWidth - * Signature: (J)I - */ -JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_getDeviceWidth - (JNIEnv *, jobject, jlong); - -/* - * Class: org_tw_pi_framebuffer_FrameBuffer - * Method: getDeviceHeight - * Signature: (J)I - */ -JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_getDeviceHeight - (JNIEnv *, jobject, jlong); - -/* - * Class: org_tw_pi_framebuffer_FrameBuffer - * Method: getDeviceBitsPerPixel - * Signature: (J)I - */ -JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_getDeviceBitsPerPixel - (JNIEnv *, jobject, jlong); - -/* - * Class: org_tw_pi_framebuffer_FrameBuffer - * Method: updateDeviceBuffer - * Signature: (J[I)Z - */ -JNIEXPORT jboolean JNICALL Java_org_tw_pi_framebuffer_FrameBuffer_updateDeviceBuffer - (JNIEnv *, jobject, jlong, jintArray); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/JavaFrameBuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffer.java b/JavaFrameBuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffer.java deleted file mode 100755 index 5ccfd13..0000000 --- a/JavaFrameBuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffer.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * This file is the JNI Java part of a Raspberry Pi FrameBuffer project. - * - * Created 2013 by Thomas Welsch (ttww@gmx.de). - * - * Do whatever you want to do with it :-) - * - **/ - -package org.tw.pi.framebuffer; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; - -import javax.swing.JPanel; - - -/** - * This class is the Java front end for a simple to use FrameBuffer driver. - * Simple draw in the BufferedImage and all changes are transfered to the FrameBuffer device.

- * For testing purpose a dummy device is supported (via the devicename "dummy_160x128" instead of "/dev/fb1"). - * It's used to drive small bit mapped screens connected via SPI, see - * http://www.sainsmart.com/blog/ada/ - *

- *

- * My Linux kernel config for SPI display was: - *

- * CONFIG_FB_ST7735=y
- * CONFIG_FB_ST7735_PANEL_TYPE_RED_TAB=y
- * CONFIG_FB_ST7735_RGB_ORDER_REVERSED=y
- * CONFIG_FB_ST7735_MAP=y
- * CONFIG_FB_ST7735_MAP_RST_GPIO=25
- * CONFIG_FB_ST7735_MAP_DC_GPIO=24
- * CONFIG_FB_ST7735_MAP_SPI_BUS_NUM=0
- * CONFIG_FB_ST7735_MAP_SPI_BUS_CS=0
- * CONFIG_FB_ST7735_MAP_SPI_BUS_SPEED=16000000
- * CONFIG_FB_ST7735_MAP_SPI_BUS_MODE=0
- * 
- * CONFIG_FB_ST7735_MAP_SPI_BUS_SPEED gives faster updates :-) - *

- * If you get the wrong colors, try the CONFIG_FB_ST7735_RGB_ORDER_REVERSED option ! - */ -public class FrameBuffer { - - private static final int FPS = 60; // Max. update rate - - private String deviceName; - - private long deviceInfo; // Private data from JNI C - - private int width,height; - private int bits; - - private BufferedImage img; - private int[] imgBuffer; - - // ----------------------------------------------------------------------------------------------------------------- - - private native long openDevice(String device); - private native void closeDevice(long di); - private native int getDeviceWidth(long di); - private native int getDeviceHeight(long di); - private native int getDeviceBitsPerPixel(long di); - private native boolean updateDeviceBuffer(long di,int[] buffer); - - static { - System.loadLibrary("FrameBufferJNI"); // FrameBufferJNI.dll (Windows) or FrameBufferJNI.so (Unixes) - } - - // ----------------------------------------------------------------------------------------------------------------- - - /** - * Open the named frame buffer device and starts the automatic update thread between the internal - * BufferedImage and the device. - * - * @param deviceName e.g. /dev/fb1 or dummy_320x200 - */ - public FrameBuffer(String deviceName) { - this(deviceName,true); - } - - // ----------------------------------------------------------------------------------------------------------------- - - /** - * Open the named frame buffer device. - * - * @param deviceName e.g. /dev/fb1 or dummy_320x200 - * @param autoUpdate if true, starts the automatic update thread between the internal - * BufferedImage and the device. - */ - public FrameBuffer(String deviceName, boolean autoUpdate) { - - this.deviceName = deviceName; - - deviceInfo = openDevice(deviceName); - - if (deviceInfo < 10) { - throw new IllegalArgumentException("Init. for frame buffer "+deviceName+" failed with error code "+deviceInfo); - } - - this.width = getDeviceWidth(deviceInfo); - this.height = getDeviceHeight(deviceInfo); - - System.err.println("Open with "+deviceName+" ("+deviceInfo+")"); - System.err.println(" width "+getDeviceWidth(deviceInfo)); - System.err.println(" height "+getDeviceHeight(deviceInfo)); - System.err.println(" bpp "+getDeviceBitsPerPixel(deviceInfo)); - - // We always use ARGB image type. - img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - imgBuffer = ((DataBufferInt) img.getRaster().getDataBuffer()).getBankData()[0]; - - if (autoUpdate) new UpdateThread().start(); - } - - // ----------------------------------------------------------------------------------------------------------------- - - private ScreenPanel screenPanel; - - /** - * Returns a ScreenPanel (JPanel) which represents the actual frame buffer device. - * - * @return ScreenPanel... - */ - public ScreenPanel getScreenPanel() { - synchronized (deviceName) { - if (screenPanel != null) throw new IllegalStateException("Only one screen panel supported"); - - screenPanel = new ScreenPanel(); - - return screenPanel; - } - } - - // ----------------------------------------------------------------------------------------------------------------- - - /** - * Internal helper class for displaying the current frame buffer image via a JPanel. - */ - @SuppressWarnings("serial") - public class ScreenPanel extends JPanel { - - private int scale = 1; - - public ScreenPanel() { - setPreferredSize(new Dimension(FrameBuffer.this.width,FrameBuffer.this.height)); - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - int w = this.getWidth(); - int h = this.getHeight(); - int wi = img.getWidth() * scale; - int hi = img.getHeight() * scale; - - Graphics2D g2 = (Graphics2D) g; - g2.translate(w / 2 - wi / 2, h / 2 - hi / 2); - g2.scale(scale,scale); - - g.setColor(Color.BLACK); - g.fillRect(0,0,img.getWidth(), img.getHeight() ); - g.drawImage(img, 0, 0, null); - } - - public void setScale(int scale) { - this.scale = scale; - repaint(); - } - } - - // ----------------------------------------------------------------------------------------------------------------- - - /** - * Internal helper class for refreshing the frame buffer display and/or JPanel. - */ - private class UpdateThread extends Thread { - - UpdateThread() { - setDaemon(true); - setName("FB "+deviceName+ " update"); - } - - @Override - public void run() { - final int SLEEP_TIME = 1000 / FPS; - - while (deviceInfo != 0) { - - if (updateScreen()) { - if (screenPanel != null) { - screenPanel.repaint(); - } - } - - try { - sleep(SLEEP_TIME); - } catch (InterruptedException e) { - break; - } - - } // while - - } - - } // class UpdateThread - - // ----------------------------------------------------------------------------------------------------------------- - - /** - * Returns the BufferedImage for drawing. Anything your draw here is synchronized to the frame buffer. - * - * @return BufferedImage of type ARGB. - */ - public BufferedImage getScreen() { - return img; - } - - // ----------------------------------------------------------------------------------------------------------------- - - /** - * Close the device. - */ - public void close() { - synchronized (deviceName) { - closeDevice(deviceInfo); - deviceInfo = 0; - img = null; - imgBuffer = null; - } - } - - // ----------------------------------------------------------------------------------------------------------------- - - /** - * Update the screen if no automatic sync is used (see constructor autoUpdate flag). - * This method is normally called by the autoUpdate thread. - * - * @return true if the BufferedImage was changed since the last call. - */ - public boolean updateScreen() { - synchronized (deviceName) { - if (deviceInfo == 0) return false; - return updateDeviceBuffer(deviceInfo,imgBuffer); - } - } - - -} // of class diff --git a/JavaFrameBuffer/src/main/java/org/tw/pi/framebuffer/TestFrameBuffer.java b/JavaFrameBuffer/src/main/java/org/tw/pi/framebuffer/TestFrameBuffer.java deleted file mode 100755 index 9a8f1f8..0000000 --- a/JavaFrameBuffer/src/main/java/org/tw/pi/framebuffer/TestFrameBuffer.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * This file is the JNI Java part of a Raspberry Pi FrameBuffer project. - * - * Created 2013 by Thomas Welsch (ttww@gmx.de). - * - * Do whatever you want to do with it :-) - * - **/ -package org.tw.pi.framebuffer; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Font; -import java.awt.GradientPaint; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.util.Random; - -import javax.swing.JFrame; -import javax.swing.SwingUtilities; - - -/** - * Simple test file class for demonstrate the FrameBuffer class. - */ -public class TestFrameBuffer { - - private FrameBuffer fb; - - // ----------------------------------------------------------------------------------------------------------------- - - public TestFrameBuffer(String deviceName) { - fb = new FrameBuffer(deviceName); - } - - // ----------------------------------------------------------------------------------------------------------------- - - private void startTests() { - - new Thread("Test") { - @Override - public void run() { - BufferedImage img = fb.getScreen(); - - int w = img.getWidth(); - int h = img.getHeight(); - - Graphics2D g = img.createGraphics(); - - // RenderingHints.VALUE_ANTIALIAS_ON must before rotate ! - // Rotated font drawing behaves strange without that.... - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - g.setColor(Color.LIGHT_GRAY); - g.fillRect(0, 0, w, h); - - g.setColor(Color.WHITE); - g.drawString("Hello world !", 22, 45); - - int y = 17; - g.setColor(Color.RED); - g.fillRect(0, y, 20,20); - y += 21; - - g.setColor(Color.GREEN); - g.fillRect(0, y, 20,20); - y += 21; - - g.setColor(Color.BLUE); - g.fillRect(0, y, 20,20); - y += 21; - - AffineTransform st = g.getTransform(); - g.translate(w/2, h/2+5); - - AffineTransform stt = g.getTransform(); - - for (int i=0; i<360; i += 4) { - - g.rotate(Math.toRadians(i)); - - g.setColor(Color.WHITE); - g.drawString("Nice !!!", 0,0); - - try { - sleep(150); - } catch (InterruptedException e) { - return; - } - - g.setColor(Color.LIGHT_GRAY); - g.drawString("Nice !!!", 0,0); - - g.setTransform(stt); -// g.rotate(Math.toRadians(-i)); - } - g.setTransform(st); - - - g.setFont(new Font("Serif", Font.BOLD, 30)); - Color c1 = new Color(0, 0, 0, 0); - Color c2 = new Color(0, 0, 0, 100); - GradientPaint gradient = new GradientPaint(10, 8, c1, 10, 40, c2, true); - - g.setColor(Color.GREEN); - g.fillRect(0, 0, w, h); - g.setColor(Color.BLACK); - g.setPaint(gradient); - g.fillRoundRect(100, 100, 200, 50, 25, 25); - g.setPaint(Color.BLACK); - g.drawRoundRect(100, 100, 200, 50, 25, 25); - g.drawString("Hello World!", 118, 135); - - try { - sleep(2000); - } catch (InterruptedException e) { - return; - } - - - Random r = new Random(); - - while (true) { - int x1 = r.nextInt(w); - int x2 = r.nextInt(w); - int y1 = r.nextInt(h); - int y2 = r.nextInt(h); - - g.setColor(new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255))); - g.drawLine(x1, y1, x2, y2); - } - } - }.start(); - } - - // ----------------------------------------------------------------------------------------------------------------- - - public static void main(String[] args) { - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { -// TestFrameBuffer mt = new TestFrameBuffer("/dev/fb1"); - TestFrameBuffer mt = new TestFrameBuffer("dummy_200x330"); - - if (true) { - JFrame f = new JFrame("Frame Buffer Test"); - f.setSize(400, 400); - f.setLocation(300,200); - f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - f.getContentPane().add(BorderLayout.CENTER, mt.fb.getScreenPanel()); - f.setVisible(true); - } - - mt.startTests(); - } - }); - } - -} // of TestFrameBuffer diff --git a/README.md b/README.md index fb21c99..fd465a9 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,8 @@ -JavaFrameBuffer -=============== +# org.tw.pi:framebuffer:0.1.0-SNAPSHOT -Acces to Linux frame buffer devices from Java. E.g. for driving LCD SPI displays on the Raspberry Pi +Access to Linux frame buffer devices from Java. E.g. for driving LCD SPI displays on the Raspberry Pi -See the run.sh script for setting up the LD_LIBRARY path for the JNI part and the example test class. - -I didn't make a jar for on file, so you have to copy the Java file and the JNI to your Java project. - -If someone can compile the Windows JNI parts, please send me the script and the library, I will add it :-) - -"Do what ever you want to do with it" licence... - -Ciao, - Thomas +Originally based off code from [ttww/JavaFrameBuffer](https://github.com/ttww/JavaFrameBuffer). +Modified to build with Apache Maven, then API rewritten to provide direct-write access +to linux framebuffers as java.awt.image.BuffferedImage + \ No newline at end of file diff --git a/framebuffer-parent/.gitignore b/framebuffer-parent/.gitignore new file mode 100644 index 0000000..d4111ff --- /dev/null +++ b/framebuffer-parent/.gitignore @@ -0,0 +1,2 @@ +/.settings/ +/target/ diff --git a/framebuffer-parent/.project b/framebuffer-parent/.project new file mode 100644 index 0000000..2d51b9c --- /dev/null +++ b/framebuffer-parent/.project @@ -0,0 +1,17 @@ + + + framebuffer-parent + + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + + diff --git a/framebuffer-parent/pom.xml b/framebuffer-parent/pom.xml new file mode 100644 index 0000000..d0eae37 --- /dev/null +++ b/framebuffer-parent/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + org.tw.pi + framebuffer-parent + 0.1.0-SNAPSHOT + pom + + ../framebuffer + ../framebuffer-shaded + + + + + + + maven-compiler-plugin + 3.3 + + 1.7 + 1.7 + UTF-8 + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + UTF-8 + + + + + + + \ No newline at end of file diff --git a/framebuffer-shaded/.classpath b/framebuffer-shaded/.classpath new file mode 100644 index 0000000..e43402f --- /dev/null +++ b/framebuffer-shaded/.classpath @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framebuffer-shaded/.gitignore b/framebuffer-shaded/.gitignore new file mode 100644 index 0000000..5d665db --- /dev/null +++ b/framebuffer-shaded/.gitignore @@ -0,0 +1,3 @@ +/target/ +/.settings/ +/dependency-reduced-pom.xml diff --git a/framebuffer-shaded/.project b/framebuffer-shaded/.project new file mode 100644 index 0000000..72c8cb0 --- /dev/null +++ b/framebuffer-shaded/.project @@ -0,0 +1,23 @@ + + + framebuffer-shaded + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/framebuffer-shaded/pom.xml b/framebuffer-shaded/pom.xml new file mode 100644 index 0000000..9b7fd72 --- /dev/null +++ b/framebuffer-shaded/pom.xml @@ -0,0 +1,45 @@ + + 4.0.0 + framebuffer-shaded + + org.tw.pi + framebuffer-parent + 0.1.0-SNAPSHOT + ../framebuffer-parent + + + + org.tw.pi + framebuffer + 0.1.0-SNAPSHOT + nar + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.2 + + + shade + package + + true + false + true + + + org.scijava.nativelib + org.tw.pi.framebuffer.org.scijava.nativelib + + + + + + + + + \ No newline at end of file diff --git a/framebuffer/.classpath b/framebuffer/.classpath new file mode 100644 index 0000000..fc6d660 --- /dev/null +++ b/framebuffer/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framebuffer/.gitignore b/framebuffer/.gitignore new file mode 100644 index 0000000..755cdc3 --- /dev/null +++ b/framebuffer/.gitignore @@ -0,0 +1,3 @@ +/bin/ +/target/ +/.settings/ diff --git a/framebuffer/.project b/framebuffer/.project new file mode 100644 index 0000000..8c183f3 --- /dev/null +++ b/framebuffer/.project @@ -0,0 +1,23 @@ + + + framebuffer + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/framebuffer/pom.xml b/framebuffer/pom.xml new file mode 100644 index 0000000..1f4b92d --- /dev/null +++ b/framebuffer/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + framebuffer + + nar + + + + org.scijava + native-lib-loader + 2.1.2 + + + junit + junit + 4.12 + test + + + + + + + com.github.maven-nar + nar-maven-plugin + 3.2.3 + true + + + + jni + org.tw.pi + + + + + + + + org.tw.pi + framebuffer-parent + 0.1.0-SNAPSHOT + ../framebuffer-parent + + \ No newline at end of file diff --git a/framebuffer/src/main/c/FrameBuffers.c b/framebuffer/src/main/c/FrameBuffers.c new file mode 100755 index 0000000..8f1c93d --- /dev/null +++ b/framebuffer/src/main/c/FrameBuffers.c @@ -0,0 +1,239 @@ + +#include +#include +#include +#include +#include + +#ifdef __linux +#include +#include +#endif + +#include + +#include + +#include "org_tw_pi_framebuffer_FrameBuffers.h" + +/* + * struct to keep track of an open linux framebuffer + */ +static struct FrameBufferData { + /* + * The path to the framebuffer, such as /dev/fb1 + */ + char *deviceName; + + /* + * file descriptor for the framebuffer + */ + int fbfd; + + /* + * Width of the framebuffer + */ + int width; + /* + * Height of the framebuffer + */ + int height; + /* + * Color depth of the framebuffer + */ + int bpp; + + /* + * Size of the memory-mapped framebuffer device + */ + long int screensize; + + char *fbp; + + char *cache; +}; + +/* + * Open a linux framebuffer, returning a pointer to struct FrameBufferData, + * or an error code defined in FrameBuffers.java + */ +JNIEXPORT jlong JNICALL Java_org_tw_pi_framebuffer_FrameBuffers_openDevice0( + JNIEnv *env, jobject obj, jstring device) { + + jboolean isCopy; + + struct FrameBufferData *di; + +#ifndef __linux + // not supported on not linux + return org_tw_pi_framebuffer_FrameBuffers_ERR_NOT_SUPPORTED; +#else + + di = malloc(sizeof(*di)); + memset(di, 0, sizeof(*di)); + + const char *s = (*env)->GetStringUTFChars(env, device, &isCopy); + di->deviceName = strdup(s); + if (isCopy) + (*env)->ReleaseStringUTFChars(env, device, s); + + // Open the file for reading and writing + struct fb_var_screeninfo vinfo; + struct fb_fix_screeninfo finfo; + + di->fbfd = open(di->deviceName, O_RDWR); + if (!di->fbfd) { + free(di->deviceName); + return org_tw_pi_framebuffer_FrameBuffers_ERR_OPEN; + } + + // Get fixed screen information + if (ioctl(di->fbfd, FBIOGET_FSCREENINFO, &finfo)) { + close(di->fbfd); + free(di->deviceName); + return org_tw_pi_framebuffer_FrameBuffers_ERR_FIXED; + } + + // Get variable screen information + if (ioctl(di->fbfd, FBIOGET_VSCREENINFO, &vinfo)) { + close(di->fbfd); + free(di->deviceName); + return org_tw_pi_framebuffer_FrameBuffers_ERR_VARIABLE; + } + + di->width = vinfo.xres; + di->height = vinfo.yres; + di->bpp = vinfo.bits_per_pixel; + + if(di->bpp != 8 && di->bpp != 16 && di->bpp != 24) { + close(di->fbfd); + free(di->deviceName); + return org_tw_pi_framebuffer_FrameBuffers_ERR_BITS; + } + + // map framebuffer to user memory + di->screensize = finfo.smem_len; + + di->fbp = (char*) mmap(0, di->screensize, PROT_READ | PROT_WRITE, MAP_SHARED, di->fbfd, 0); + + if (((int) di->fbp == -1) || ((di->cache = malloc(di->screensize)) == NULL)) { + close(di->fbfd); + free(di->deviceName); + return org_tw_pi_framebuffer_FrameBuffers_ERR_MMAP; + } + + return (jlong) (intptr_t) di; +#endif +} + +/* + * Close a framebuffer tracked by a pointer to struct FrameBufferData + */ +JNIEXPORT void JNICALL Java_org_tw_pi_framebuffer_FrameBuffers_closeDevice0( + JNIEnv *env, jobject obj, jlong jdi) { + + struct FrameBufferData *di = (struct FrameBufferData *) (intptr_t) jdi; + + free(di->deviceName); + free(di->cache); + + munmap(di->fbp, di->screensize); + close(di->fbfd); + + memset(di, 0, sizeof(*di)); +} + +/* + * Return the tracked framebuffer width by a pointer to struct FrameBufferData + */ +JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffers_getDeviceWidth0( + JNIEnv *env, jobject obj, jlong jdi) { + + struct FrameBufferData *di = (struct FrameBufferData *) (intptr_t) jdi; + + return di->width; +} + +/* + * Return the tracked framebuffer height by a pointer to struct FrameBufferData + */ +JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffers_getDeviceHeight0( + JNIEnv *env, jobject obj, jlong jdi) { + + struct FrameBufferData *di = (struct FrameBufferData *) (intptr_t) jdi; + + return di->height; +} + +/* + * Return the tracked framebuffer color depth by a pointer to struct FrameBufferData + */ +JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffers_getDeviceBitsPerPixel0( + JNIEnv *env, jobject obj, jlong jdi) { + + struct FrameBufferData *di = (struct FrameBufferData *) (intptr_t) jdi; + + return di->bpp; +} + +/* + * Write a pixel to a framebuffer + */ +JNIEXPORT void JNICALL Java_org_tw_pi_framebuffer_FrameBuffers_writeRGB0 +(JNIEnv *env, jclass clazz, jlong ptr, jint idx, jint rgb) { + struct FrameBufferData *di = (struct FrameBufferData *) (intptr_t) ptr; + unsigned char *p = (unsigned char *) di->fbp; + unsigned char *q = (unsigned char *) di->cache; + unsigned int width; + + if(di->bpp == 8) + width = 1; + else if(di->bpp == 16) + width = 2; + else if(di->bpp == 24) + width = 3; + else + return; + + p += (width * idx); + q += (width * idx); + while(width > 0) { + if(*q != (unsigned char)(0xFF & rgb)) + *q = *p = (unsigned char)(0xFF & rgb); + rgb = rgb >> 8; + width--; + p++; + q++; + } +} + +/* + * Read a pixel from a framebuffer + */ +JNIEXPORT jint JNICALL Java_org_tw_pi_framebuffer_FrameBuffers_readRGB0 +(JNIEnv *env, jclass clazz, jlong ptr, jint idx) { + struct FrameBufferData *di = (struct FrameBufferData *) (intptr_t) ptr; + unsigned char *q = (unsigned char *) di->cache; + unsigned int width; + + if(di->bpp == 8) + width = 1; + else if(di->bpp == 16) + width = 2; + else if(di->bpp == 24) + width = 3; + else + return -1; + + unsigned int rgb = 0; + + q += (width * idx); + q += width - 1; + while(width > 0) { + rgb = (rgb << 8) + *q; + width--; + q--; + } + + return rgb; +} diff --git a/framebuffer/src/main/java/org/tw/pi/framebuffer/ColorEndian.java b/framebuffer/src/main/java/org/tw/pi/framebuffer/ColorEndian.java new file mode 100644 index 0000000..bc334a0 --- /dev/null +++ b/framebuffer/src/main/java/org/tw/pi/framebuffer/ColorEndian.java @@ -0,0 +1,139 @@ +package org.tw.pi.framebuffer; + +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; + +/** + * Utility enum useful as an argument to select between RGB and BGR framebuffers.

+ * + * Not sure if BGR framebuffers exist, but oh well. + * @author Robin Kirkman + * + */ +public enum ColorEndian { + /** + * Color is represented as R, G, B, in ascending address order + */ + RGB(new int[]{ + 0x000000e0, + 0x0000001c, + 0x00000003, + }, new int[]{ + 0x0000f800, + 0x000007e0, + 0x0000001f, + }, new int[] { + 0x00ff0000, + 0x0000ff00, + 0x000000ff, + }), + /** + * Color is represented as B, G, R, in ascending address order + */ + BGR(new int[]{ + 0x00000003, + 0x0000001c, + 0x000000e0, + }, new int[]{ + 0x0000001f, + 0x000007e0, + 0x0000f800, + }, new int[] { + 0x000000ff, + 0x0000ff00, + 0x00ff0000, + }), + ; + + /** + * The red component offset in a bitmask array + */ + private static final int RED_COMPONENT = 0; + /** + * The green component offset in a bitmask array + */ + private static final int GREEN_COMPONENT = 1; + /** + * The blue component offset in a bitmask array + */ + private static final int BLUE_COMPONENT = 2; + + /** + * 8-bit color bitmasks for red, green, blue + */ + private int[] mask8; + /** + * 16-bit color bitmasks for red, green, blue + */ + private int[] mask16; + /** + * 24-bit color bitmasks for red, green, blue + */ + private int[] mask24; + + private ColorEndian(int[] mask8, int[] mask16, int[] mask24) { + this.mask8 = mask8; + this.mask16 = mask16; + this.mask24 = mask24; + } + + /** + * Get the bitmask for a component at a particular color depth + * @param bpp The color depth: {@code 8}, {@code 16}, or {@code 24} + * @param component The color component offset + * @return The bitmask for that color at that color depth + * @see #RED_COMPONENT + * @see #GREEN_COMPONENT + * @see #BLUE_COMPONENT + */ + private int getComponentMask(int bpp, int component) { + switch(bpp) { + case 8: return mask8[component]; + case 16: return mask16[component]; + case 24: return mask24[component]; + } + throw new IllegalArgumentException("Invalid color depth for " + this + ": " + bpp); + } + + /** + * Create and return a new {@link ColorModel} appropriate for use in a {@link FrameBufferedImage} + * at the argument color depth + * @param bpp The color depth: {@code 8}, {@code 16}, or {@code 24} + * @return A new {@link ColorModel} + */ + public ColorModel createColorModel(int bpp) { + return new DirectColorModel( + ColorSpace.getInstance(ColorSpace.CS_sRGB), + bpp, + getComponentMask(bpp, ColorEndian.RED_COMPONENT), + getComponentMask(bpp, ColorEndian.GREEN_COMPONENT), + getComponentMask(bpp, ColorEndian.BLUE_COMPONENT), + 0x0, + false, + DataBuffer.TYPE_INT); + } + + /** + * Create and return a new {@link SampleModel} appropriate for use in a {@link FrameBufferedImage} + * with the argument width, height, and color depth + * @param w The width + * @param h The height + * @param bpp The color depth: {@code 8}, {@code 16}, or {@code 24} + * @return A new {@link SampleModel} + */ + public SampleModel createSampleModel(int w, int h, int bpp) { + return new SinglePixelPackedSampleModel( + DataBuffer.TYPE_INT, + w, + h, + new int[] { + getComponentMask(bpp, ColorEndian.RED_COMPONENT), + getComponentMask(bpp, ColorEndian.GREEN_COMPONENT), + getComponentMask(bpp, ColorEndian.BLUE_COMPONENT), + }); + } +} \ No newline at end of file diff --git a/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffer.java b/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffer.java new file mode 100644 index 0000000..a1759bd --- /dev/null +++ b/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffer.java @@ -0,0 +1,208 @@ +package org.tw.pi.framebuffer; + +import java.awt.image.DataBuffer; +import java.io.Closeable; +import java.io.IOException; + +/** + * {@link DataBuffer} for use in a {@link FrameBufferedImage} that is usually + * backed by a linux framebuffer device. May also be instantiated using a + * "dummy" framebuffer, just an array of ints. + * @author Robin Kirkman + * + */ +public class FrameBuffer extends DataBuffer implements Closeable { + /** + * Default width of a dummy framebuffer + */ + public static final int DEFAULT_DUMMY_WIDTH = 320; + /** + * Default height of a dummy framebuffer + */ + public static final int DEFAULT_DUMMY_HEIGHT = 240; + /** + * Default color depth of a dummy framebuffer + */ + public static final int DEFAULT_COLOR_DEPTH = 24; + + /** + * Pointer to a {@code struct FrameBufferData} from the JNI layer. + */ + private final long ptr; + /** + * Width of the frame buffer + */ + private final int w; + /** + * Height of the frame buffer + */ + private final int h; + /** + * Color depth of the frame buffer + */ + private final int bpp; + + /** + * {@code int} array used to hold data for a dummy buffer. is {@code null} if + * this {@link FrameBuffer} is backed by an actual framebuffer device. + */ + private final int[] dummy; + + /** + * Has this {@link FrameBuffer} been closed? + * @see #close() + */ + private boolean closed; + + /** + * Create a new dummy {@link FrameBuffer} with the default width, height, and color depth. + * @see #DEFAULT_DUMMY_WIDTH + * @see #DEFAULT_DUMMY_HEIGHT + * @see #DEFAULT_COLOR_DEPTH + */ + public FrameBuffer() { + this(FrameBuffers.DUMMY); + } + + /** + * Open a framebuffer with the argument device path, such as {@code "/dev/fb1"}. + * @param fbdev The path to the framebuffer device + * @throws IOException If the framebuffer could not be opened, such as on a non-linux system, + * or if the current user lacks sufficient privileges. + */ + public FrameBuffer(String fbdev) throws IOException { + this(FrameBuffers.openDevice(fbdev)); + } + + /** + * Create a new dummy {@link FrameBuffer} with the specified width, height, and color depth. + * @param w The width + * @param h The height + * @param bpp The color depth: {@code 8}, {@code 16}, or {@code 24} + */ + public FrameBuffer(int w, int h, int bpp) { + this(FrameBuffers.DUMMY, w, h, bpp); + } + + /** + * Create a new dummy {@link FrameBuffer} with the specified width and height, and + * a default color depth. + * @param w The width + * @param h The height + * @see #DEFAULT_COLOR_DEPTH + */ + public FrameBuffer(int w, int h) { + this(FrameBuffers.DUMMY, w, h, DEFAULT_COLOR_DEPTH); + } + + /** + * Create a {@link FrameBuffer} by wrapping a {@code struct FrameBufferData*} returned + * by {@link FrameBuffers#openDevice0(String)} + * @param ptr The pointer from the JNI, or {@link FrameBuffers#DUMMY} for a default dummy {@link FrameBuffer} + */ + private FrameBuffer(long ptr) { + this( + ptr, + (ptr == FrameBuffers.DUMMY ? DEFAULT_DUMMY_WIDTH : FrameBuffers.getDeviceWidth0(ptr)), + (ptr == FrameBuffers.DUMMY ? DEFAULT_DUMMY_HEIGHT : FrameBuffers.getDeviceHeight0(ptr)), + (ptr == FrameBuffers.DUMMY ? DEFAULT_COLOR_DEPTH : FrameBuffers.getDeviceBitsPerPixel0(ptr))); + } + + /** + * Create a {@link FrameBuffer} wrapping a {@code struct FrameBufferData*} returned + * by {@link FrameBuffers#openDevice0(String)}, with supplied width, height, and + * color depth. + * @param ptr The pointer from the JNI, or {@link FrameBuffers#DUMMY} for a dummy {@link FrameBuffer} + * @param w The width + * @param h The height + * @param bpp The color depth: {@code 8}, {@code 16}, or {@code 24} + */ + private FrameBuffer(long ptr, int w, int h, int bpp) { + super(DataBuffer.TYPE_INT, w * h); + if(!FrameBuffers.isValidColorDepth(bpp)) + throw new IllegalArgumentException("Illegal color depth: " + bpp); + this.ptr = ptr; + this.w = w; + this.h = h; + this.bpp = bpp; + if(ptr == FrameBuffers.DUMMY) { + this.dummy = new int[w * h]; + } else { + this.dummy = null; + } + this.closed = false; + } + + /* + * (non-Javadoc) + * @see java.awt.image.DataBuffer#getElem(int, int) + * + * Return an int taken from the framebuffer device, or the dummy array for a dummy FrameBuffer + */ + @Override + public int getElem(int bank, int i) { + if(closed) // cannot get if closed + throw new IllegalStateException(); + int val; + if(ptr == FrameBuffers.DUMMY) + val = dummy[i]; + else + val = FrameBuffers.readRGB0(ptr, i); + return val & 0x00FFFFFF; // ensure 24bit unsigned int + } + + /* + * (non-Javadoc) + * @see java.awt.image.DataBuffer#setElem(int, int, int) + * + * Set an int in the framebuffer device, or the dummy array for a dummy FrameBuffer + */ + @Override + public void setElem(int bank, int i, int val) { + if(closed) // cannot set if closed + throw new IllegalStateException(); + val = val & 0x00FFFFFF; // ensure 24bit unsigned int + if(ptr == FrameBuffers.DUMMY) + dummy[i] = val; + else + FrameBuffers.writeRGB0(ptr, i, val); + } + + /** + * Close this {@link FrameBuffer}, releasing any JNI resources (such as file + * handles or memory maps) for a non-dummy {@link FrameBuffer}.

+ * + * Subsequent calls to {@link #close()} have no effect. + */ + public void close() { + if(!closed) { + if(ptr != FrameBuffers.DUMMY) + FrameBuffers.closeDevice0(ptr); + closed = true; + } + } + + /** + * Returns the width of this {@link FrameBuffer} + * @return The width in pixels + */ + public int getWidth() { + return w; + } + + /** + * Returns the height of this {@link FrameBuffer} + * @return The height in pixels + */ + public int getHeight() { + return h; + } + + /** + * Returns the color depth of this {@link FrameBuffer} + * @return The color depth, in bits + */ + public int getColorDepth() { + return bpp; + } +} diff --git a/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBufferedImage.java b/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBufferedImage.java new file mode 100644 index 0000000..bc0b8c7 --- /dev/null +++ b/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBufferedImage.java @@ -0,0 +1,82 @@ +package org.tw.pi.framebuffer; + +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.io.Closeable; +import java.io.IOException; + +/** + * {@link BufferedImage} whose data is backed by a {@link FrameBuffer}, itself + * either a dummy or backed by a linux framebuffer device. + * @author Robin Kirkman + * + */ +public class FrameBufferedImage extends BufferedImage implements Closeable { + /** + * The {@link FrameBuffer} backing this {@link FrameBufferedImage} + */ + final private FrameBuffer fb; + + /** + * Create a new {@link FrameBufferedImage} by opening a linux framebuffer device + * @param fbdev The path to the framebuffer, such as {@code "/dev/fb1"} + * @throws IOException If the framebuffer could not be opened + */ + public FrameBufferedImage(String fbdev) throws IOException { + this(new FrameBuffer(fbdev)); + } + + /** + * Create a new {@link FrameBufferedImage} by opening a linux framebuffer device + * with a specified color endian-ness + * @param ce The color endian-ness: {@link ColorEndian#RGB} or {@link ColorEndian#BGR} + * @param fbdev The path to the framebuffer, such as {@code "/dev/fb1"} + * @throws IOException If the framebuffer could not be opened + */ + public FrameBufferedImage(ColorEndian ce, String fbdev) throws IOException { + this(ce, new FrameBuffer(fbdev)); + } + + /** + * Create a new {@link FrameBufferedImage} by wrapping an existing {@link FrameBuffer} + * @param fb The {@link FrameBuffer} to wrap + */ + public FrameBufferedImage(FrameBuffer fb) { + this(ColorEndian.RGB, fb); + } + + /** + * Create a new {@link FrameBufferedImage} by wrapping an existing {@link FrameBuffer} with a + * specified color endian-ness + * @param ce The color endian-ness: {@link ColorEndian#RGB} or {@link ColorEndian#BGR} + * @param fb The {@link FrameBuffer} to wrap + */ + public FrameBufferedImage(ColorEndian ce, FrameBuffer fb) { + this( + new FrameBufferedRaster(ce, fb), + ce.createColorModel(fb.getColorDepth())); + } + + /** + * Create a new {@link FrameBufferedImage} wrapping a {@link FrameBufferedRaster} + * and a {@link ColorModel} returned by {@link ColorEndian#createColorModel(int)} + * @param raster The {@link FrameBufferedRaster} to wrap + * @param colorModel The {@link ColorModel} to use + */ + private FrameBufferedImage(FrameBufferedRaster raster, ColorModel colorModel) { + super(colorModel, raster, true, null); + fb = raster.getFrameBuffer(); + } + + public void close() { + fb.close(); + } + + /** + * Returns the {@link FrameBuffer} backing this {@link FrameBufferedImage} + * @return + */ + public FrameBuffer getFrameBuffer() { + return fb; + } +} diff --git a/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBufferedRaster.java b/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBufferedRaster.java new file mode 100644 index 0000000..cc094a8 --- /dev/null +++ b/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBufferedRaster.java @@ -0,0 +1,62 @@ +package org.tw.pi.framebuffer; + +import java.awt.Point; +import java.awt.image.DataBuffer; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.Closeable; + +/** + * {@link WritableRaster} that uses a {@link FrameBuffer} as its {@link DataBuffer}. + * @author Robin Kirkman + * + */ +public class FrameBufferedRaster extends WritableRaster implements Closeable { + + /** + * The {@link FrameBuffer} backing this {@link FrameBufferedRaster} + */ + final private FrameBuffer fb; + + /** + * Create a new {@link FrameBufferedRaster} using RGB color endian-ness + * @param fb The {@link FrameBuffer} to use + * @see ColorEndian#RGB + */ + public FrameBufferedRaster(FrameBuffer fb) { + this(ColorEndian.RGB, fb); + } + + /** + * Create a new {@link FrameBufferedRaster} + * @param ce The {@link ColorEndian} to use: {@link ColorEndian#RGB} or {@link ColorEndian#BGR} + * @param fb The {@link FrameBuffer} to use + */ + public FrameBufferedRaster(ColorEndian ce, FrameBuffer fb) { + this(fb, ce.createSampleModel(fb.getWidth(), fb.getHeight(), fb.getColorDepth())); + } + + /** + * Create a new {@link FrameBufferedRaster} using a {@link FrameBuffer} and + * a {@link SampleModel} returned by {@link ColorEndian#createSampleModel(int, int, int)} + * @param fb The {@link FrameBuffer} to use + * @param sampleModel The {@link SampleModel} to use + */ + private FrameBufferedRaster(FrameBuffer fb, SampleModel sampleModel) { + super(sampleModel, fb, new Point(0,0)); + this.fb = fb; + } + + /** + * Returns the {@link FrameBuffer} used by this {@link FrameBufferedRaster} + * @return The {@link FrameBuffer} + */ + public FrameBuffer getFrameBuffer() { + return fb; + } + + @Override + public void close() { + fb.close(); + } +} diff --git a/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffers.java b/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffers.java new file mode 100755 index 0000000..81a75df --- /dev/null +++ b/framebuffer/src/main/java/org/tw/pi/framebuffer/FrameBuffers.java @@ -0,0 +1,171 @@ + +package org.tw.pi.framebuffer; + +import java.awt.image.BufferedImage; +import java.io.IOException; + +import org.tw.pi.NarSystem; + +/** + * Utilities and JNI used by {@link FrameBuffer}.

+ * + * Also has utility methods {@link #open(String)} and {@link #open(ColorEndian, String)} to + * return a new {@link BufferedImage} backed by a linux framebuffer. + * @author Robin Kirkman + * + */ +public abstract class FrameBuffers { + /** + * Open a linux framebuffer device and return a new {@link BufferedImage} backed by it, + * using RGB color endian-ness + * @param fbdev The path to the framebuffer, such as {@code "/dev/fb1"} + * @return A new {@link BufferedImage} backed by the framebuffer + * @throws IOException If the linux framebuffer could not be opened + * @see ColorEndian#RGB + * @see FrameBufferedImage + */ + public static BufferedImage open(String fbdev) throws IOException { + return open(ColorEndian.RGB, fbdev); + } + + /** + * Open a linux framebuffer device and return a new {@link BufferedImage} backed by it, + * using a specified color endian-ness and framebuffer path + * @param ce The color endianness: {@link ColorEndian#RGB} or {@link ColorEndian#BGR} + * @param fbdev The path the the framebuffer, such as {@code "/dev/fb1"} + * @return A new {@link BufferedImage} backed by the framebuffer + * @throws IOException If the linux framebuffer could not be opened + * @see FrameBufferedImage + */ + public static BufferedImage open(ColorEndian ce, String fbdev) throws IOException { + return new FrameBufferedImage(fbdev); + } + + /** + * magic JNI struct pointer value to indicate a dummy {@link FrameBuffer} + */ + static final long DUMMY = -1; + /** + * Returned by {@link #openDevice0(String)} on non-linux systems + */ + static final long ERR_NOT_SUPPORTED = 0; + /** + * Returned by {@link #openDevice0(String)} if the framebuffer could not be opened + */ + static final long ERR_OPEN = 1; + /** + * Returned by {@link #openDevice0(String)} if the "fixed" framebuffer data could not be read + */ + static final long ERR_FIXED = 2; + /** + * Returned by {@link #openDevice0(String)} if the "variable" framebuffer data could not be read + */ + static final long ERR_VARIABLE = 3; + /** + * Returned by {@link #openDevice0(String)} if the framebuffer's color depth is not 8, 16, or 24 + */ + static final long ERR_BITS = 4; + /** + * Returned by {@link #openDevice0(String)} if the framebuffer could not be memory mapped + */ + static final long ERR_MMAP = 5; + + /** + * Open a framebuffer by path and return a pointer of type {@code struct FrameBufferData*} + * @param fbdev The path to the framebuffer, such as {@code "/dev/fb1"} + * @return A JNI pointer, or one of the listed error codes. + * @see #ERR_NOT_SUPPORTED + * @see #ERR_OPEN + * @see #ERR_FIXED + * @see #ERR_VARIABLE + * @see #ERR_BITS + * @see #ERR_MMAP + */ + static native long openDevice0(String fbdev); + + /** + * Close a framebuffer referenced by a pointer of type {@code struct FrameBufferData*}. + * Will probably crash the JVM if the pointer is invalid. + * @param ptr A valid JNI pointer returned by {@link #openDevice0(String)} + */ + static native void closeDevice0(long ptr); + + /** + * Returns the width of a framebuffer + * @param ptr A valid JNI pointer returned by {@link #openDevice0(String)} + * @return The width in pixels + */ + static native int getDeviceWidth0(long ptr); + /** + * Returns the height of a framebuffer + * @param ptr A valid JNI pointer returned by {@link #openDevice0(String)} + * @return The height in pixels + */ + static native int getDeviceHeight0(long ptr); + /** + * Returns the color depth of a framebuffer + * @param ptr A valid JNI pointer returned by {@link #openDevice0(String)} + * @return The native framebuffer's color depth + */ + static native int getDeviceBitsPerPixel0(long ptr); + + /** + * Write a pixel to a native framebuffer + * @param ptr A valid JNI pointer returned by {@link #openDevice0(String)} + * @param idx The pixel number; {@code idx == y * width + x} + * @param rgb The int representation of the new pixel color + */ + static native void writeRGB0(long ptr, int idx, int rgb); + /** + * Read a pixel from a native framebuffer + * @param ptr A valid JNI pointer returned by {@link #openDevice0(String)} + * @param idx The pixel number; {@code idx == y * width + x} + * @return The int representation of the specified pixel color + */ + static native int readRGB0(long ptr, int idx); + + /** + * Try to open a linux framebuffer by path, throwing {@link IOException} if unable. + * @param fbdev The path to the framebuffer, such as {@code "/dev/fb1"} + * @return A valid JNI pointer returned by {@link #openDevice0(String)} + * @throws IOException If the framebuffer could not be opened + */ + static long openDevice(String fbdev) throws IOException { + long ptr = FrameBuffers.openDevice0(fbdev); + + if(ptr == FrameBuffers.ERR_NOT_SUPPORTED) + throw new IOException("Linux framebuffers are not supported on this computer"); + if(ptr == FrameBuffers.ERR_OPEN) + throw new IOException("Unable to open framebuffer device: " + fbdev); + if(ptr == FrameBuffers.ERR_FIXED) + throw new IOException("Error reading fixed screen info for: " + fbdev); + if(ptr == FrameBuffers.ERR_VARIABLE) + throw new IOException("Error reading variable screen info for: " + fbdev); + if(ptr == FrameBuffers.ERR_BITS) + throw new IOException("Invalid color depth (" + FrameBuffers.getDeviceBitsPerPixel0(ptr) + "); 8, 16, and 24 supported; for: " + fbdev); + if(ptr == FrameBuffers.ERR_MMAP) + throw new IOException("Unable to mmap for: " + fbdev); + + return ptr; + } + + /** + * Return whether the argument is a valid color depth ({@code 8}, {@code 16}, or {@code 24}) + * @param bpp The color depth to test + * @return {@code true} if the color depth is valid, e.g. is one of {@code 8}, {@code 16}, or {@code 24} + */ + static boolean isValidColorDepth(int bpp) { + return bpp == 8 || bpp == 16 || bpp == 24; + } + + /* + * Can't instantiate this class + */ + private FrameBuffers() { + } + + static { + // load the JNI + NarSystem.loadLibrary(); + } +} diff --git a/framebuffer/src/main/java/org/tw/pi/framebuffer/package-info.java b/framebuffer/src/main/java/org/tw/pi/framebuffer/package-info.java new file mode 100644 index 0000000..78451d8 --- /dev/null +++ b/framebuffer/src/main/java/org/tw/pi/framebuffer/package-info.java @@ -0,0 +1,14 @@ +package org.tw.pi.framebuffer; +/** + * Java classes for accessing a linux framebuffer device as a {@link java.awt.image.BufferedImage}.

+ * + * The framebuffer is updated immediately when the {@link java.awt.image.BufferedImage} is changed + * by using a custom {@link java.awt.image.DataBuffer} subclass, {@link org.tw.pi.framebuffer.FrameBuffer}, + * which uses a JNI-accessed framebuffer device as its backing store.

+ * + * This package makes use of the Java AWT image classes, so anything a {@link java.awt.image.BufferedImage} + * can do can be done with a framebuffer device. + * + * @author Robin Kirkman + */ + diff --git a/JavaFrameBuffer/compileLinux.sh b/framebuffer/src/main/sh/compileLinux.sh similarity index 100% rename from JavaFrameBuffer/compileLinux.sh rename to framebuffer/src/main/sh/compileLinux.sh diff --git a/JavaFrameBuffer/compileMac.sh b/framebuffer/src/main/sh/compileMac.sh similarity index 100% rename from JavaFrameBuffer/compileMac.sh rename to framebuffer/src/main/sh/compileMac.sh diff --git a/framebuffer/src/test/java/org/tw/pi/framebuffer/FrameBufferTest.java b/framebuffer/src/test/java/org/tw/pi/framebuffer/FrameBufferTest.java new file mode 100644 index 0000000..d76df20 --- /dev/null +++ b/framebuffer/src/test/java/org/tw/pi/framebuffer/FrameBufferTest.java @@ -0,0 +1,131 @@ +package org.tw.pi.framebuffer; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.font.LineMetrics; +import java.util.Calendar; + +import org.junit.Test; + +public class FrameBufferTest { + @Test + public void testLoad() throws Exception { + Class.forName(FrameBuffers.class.getName()); + } + + private static Point project(Point center, double value, double max, int length) { + double radians = Math.PI / 2 - (value / (double) max) * (2 * Math.PI); + Point p = new Point(center.x, center.y); + p.x += length * Math.cos(radians); + p.y -= length * Math.sin(radians); + return p; + } + + private static Dimension bounds(FontMetrics fm, String s) { + Dimension d = new Dimension(); + d.height += fm.getAscent(); + d.width += fm.stringWidth(s); + return d; + } + + public static void main(String[] args) throws Exception { + FrameBufferedImage fb = new FrameBufferedImage(args[0]); + try { + Graphics2D g = (Graphics2D) fb.getGraphics(); + Dimension dim = new Dimension(fb.getWidth(), fb.getHeight()); + g.setColor(Color.BLACK); + g.fillRect(0, 0, dim.width, dim.height); + + Point center = new Point(dim.width / 2, dim.height / 2); + int radius = Math.min(dim.width, dim.height) / 2 - 3; + + g.setStroke(new BasicStroke(2f)); + + Calendar c = Calendar.getInstance(); + + g.setColor(Color.MAGENTA); + g.fillOval(center.x - radius, center.y - radius, radius * 2, radius * 2); + + g.setColor(Color.WHITE); + g.drawOval(center.x - radius, center.y - radius, radius * 2, radius * 2); + + for(int i = 0; i < 12; i++) { + Point p1 = project(center, i, 12, radius * 8 / 9); + Point p2 = project(center, i, 12, radius * 19 / 20); + g.drawLine(p1.x, p1.y, p2.x, p2.y); + } + + g.setFont(new Font("Monospace", Font.BOLD, 12)); + FontMetrics fm = g.getFontMetrics(); + + while(true) { + c.setTimeInMillis(System.currentTimeMillis()); + + double ms = c.get(Calendar.MILLISECOND); + double second = c.get(Calendar.SECOND) + ms / 1000; + double minute = c.get(Calendar.MINUTE) + second / 60; + double hour = c.get(Calendar.HOUR) + minute / 60; + + if(((int) hour) == 0) + hour += 12; + + Point p; + Dimension d; + + g.setColor(Color.WHITE); + + p = project(center, hour, 12, radius * 1 / 2 - 12); + g.drawLine(center.x, center.y, p.x, p.y); + d = bounds(fm, String.valueOf((int) hour)); + p = project(center, hour, 12, radius * 1 / 2); + g.drawString(String.valueOf((int) hour), p.x - d.width / 2, p.y + d.height / 2); + + + p = project(center, minute, 60, radius * 2 / 3 - 12); + g.drawLine(center.x, center.y, p.x, p.y); + d = bounds(fm, String.valueOf((int) minute)); + p = project(center, minute, 60, radius * 2 / 3); + g.drawString(String.valueOf((int) minute), p.x - d.width / 2, p.y + d.height / 2); + + p = project(center, second, 60, radius * 3 / 4 - 12); + g.drawLine(center.x, center.y, p.x, p.y); + d = bounds(fm, String.valueOf((int) second)); + p = project(center, second, 60, radius * 3 / 4); + g.drawString(String.valueOf((int) second), p.x - d.width / 2, p.y + d.height / 2); + + + Thread.sleep(125); + + g.setColor(Color.MAGENTA); + + p = project(center, hour, 12, radius * 1 / 2 - 12); + g.drawLine(center.x, center.y, p.x, p.y); + d = bounds(fm, String.valueOf((int) hour)); + p = project(center, hour, 12, radius * 1 / 2); + g.drawString(String.valueOf((int) hour), p.x - d.width / 2, p.y + d.height / 2); + + + p = project(center, minute, 60, radius * 2 / 3 - 12); + g.drawLine(center.x, center.y, p.x, p.y); + d = bounds(fm, String.valueOf((int) minute)); + p = project(center, minute, 60, radius * 2 / 3); + g.drawString(String.valueOf((int) minute), p.x - d.width / 2, p.y + d.height / 2); + + p = project(center, second, 60, radius * 3 / 4 - 12); + g.drawLine(center.x, center.y, p.x, p.y); + d = bounds(fm, String.valueOf((int) second)); + p = project(center, second, 60, radius * 3 / 4); + g.drawString(String.valueOf((int) second), p.x - d.width / 2, p.y + d.height / 2); + + } + } finally { + fb.close(); + } + } +} diff --git a/framebuffer/src/test/java/org/tw/pi/framebuffer/FrameBufferedImageTest.java b/framebuffer/src/test/java/org/tw/pi/framebuffer/FrameBufferedImageTest.java new file mode 100644 index 0000000..487ff20 --- /dev/null +++ b/framebuffer/src/test/java/org/tw/pi/framebuffer/FrameBufferedImageTest.java @@ -0,0 +1,39 @@ +package org.tw.pi.framebuffer; + +import org.junit.Assert; +import org.junit.Test; + +public class FrameBufferedImageTest { + + @Test + public void testCompatibility16bpp() { + FrameBufferedRaster r = null; + FrameBuffer fb = null; + ColorEndian ce = ColorEndian.RGB; + Assert.assertTrue(ce.createColorModel(16).isCompatibleSampleModel(ce.createSampleModel(1, 1, 16))); + Assert.assertTrue(ce.createColorModel(16).isCompatibleRaster(r = new FrameBufferedRaster(ce, fb = new FrameBuffer(1, 1, 16)))); + r.close(); + fb.close(); + ce = ColorEndian.BGR; + Assert.assertTrue(ce.createColorModel(16).isCompatibleSampleModel(ce.createSampleModel(1, 1, 16))); + Assert.assertTrue(ce.createColorModel(16).isCompatibleRaster(r = new FrameBufferedRaster(ce, fb = new FrameBuffer(1, 1, 16)))); + r.close(); + fb.close(); + } + + @Test + public void testCompatibility24bpp() { + FrameBufferedRaster r = null; + FrameBuffer fb = null; + ColorEndian ce = ColorEndian.RGB; + Assert.assertTrue(ce.createColorModel(24).isCompatibleSampleModel(ce.createSampleModel(1, 1, 24))); + Assert.assertTrue(ce.createColorModel(24).isCompatibleRaster(r = new FrameBufferedRaster(ce, fb = new FrameBuffer(1, 1, 24)))); + r.close(); + fb.close(); + ce = ColorEndian.BGR; + Assert.assertTrue(ce.createColorModel(24).isCompatibleSampleModel(ce.createSampleModel(1, 1, 24))); + Assert.assertTrue(ce.createColorModel(24).isCompatibleRaster(r = new FrameBufferedRaster(ce, fb = new FrameBuffer(1, 1, 24)))); + r.close(); + fb.close(); + } +} diff --git a/JavaFrameBuffer/run.sh b/framebuffer/src/test/sh/run.sh similarity index 100% rename from JavaFrameBuffer/run.sh rename to framebuffer/src/test/sh/run.sh