package org.tw.pi.framebuffer;
|
|
/*
|
* 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 :-)
|
*
|
**/
|
|
|
|
import java.awt.Dimension;
|
import java.awt.Graphics;
|
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.<p>
|
* For testing purpose a dummy device is supported (via the devicename "dummy_160x128" instead of "/dev/fb1").<p<
|
* The Java process needs write access to the frame buffer device file.
|
* <p>
|
* It's used to drive small bit mapped screens connected via SPI, see
|
* http://www.sainsmart.com/blog/ada/
|
* <p>
|
* <p>
|
* My Linux kernel config for SPI display was:
|
* <pre>
|
* 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
|
* </pre>
|
* CONFIG_FB_ST7735_MAP_SPI_BUS_SPEED gives faster updates :-)
|
* <p>
|
* 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 JPanel which represents the actual frame buffer device.
|
*
|
* @return JPanel...
|
*/
|
public JPanel 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")
|
private class ScreenPanel extends JPanel {
|
public ScreenPanel() {
|
setPreferredSize(new Dimension(FrameBuffer.this.width,FrameBuffer.this.height));
|
}
|
|
@Override
|
protected void paintComponent(Graphics g) {
|
super.paintComponent(g);
|
g.drawImage(img, 0, 0, null);
|
}
|
}
|
|
// -----------------------------------------------------------------------------------------------------------------
|
|
/**
|
* 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 synchronizet 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
|