版权声明:本文为博主原创,无版权,未经博主允许可以随意转载,无需注明出处,随意修改或保持可作为原创! https://blog.csdn.net/dog250/article/details/90136682
昨天写了一篇文章:
使用Linux Framebuffer绘制32位真彩图形: https://blog.csdn.net/dog250/article/details/90113737
并发了朋友圈表示这件事结束了,玩了一天,玩恶心了。
但是我依然是想做出一个可以拖拽的不规则GUI界面(用皮鞋或者小小的照片做界面轮廓)来的。所以半夜就爬起来继续折腾。
无奈,没有找到获取鼠标焦点的好方法,都太复杂,要知道,我是希望在framebuffer上玩啊,不希望依赖那些已经集成在GUI里面的东西。
我不就想模拟个拖拽嘛,简单,用线程控制图片在屏幕上漂移,即:
// setPoint方法已经抽象独立了出来,成为一个static方法,以免main函数太长。 while(true) { setPoint(width, height, xoffset%200, yoffset%50); try { Thread.sleep(100); } catch (InterruptedException e) { } xoffset += 2; yoffset += 2; }
这个代码测试下来, 闪烁太厉害了! 根本就没法看:
怎么办?如果简单的图片漂移都这么闪烁,那如果鼠标拖拽移动图片,结局注定令人遗憾。怎么办?
找根源!根源就是 画图的时间太久了! 我可是一个像素一个像素画的啊!
当然了,我知道,如果Java通过JNI将一个像素数组传递到本地代码,然后本地代码直接 memcpy,那将是令人赛里布瑞特的。可是我并不知道如何从Java往本地代码传递大数组…另外,我注意是想把事情做纯粹些。 我不想把事情交给库去解决,我要自己解决! (可能赚钱的经理们又要笑我了,但我就是这样,鄙视业务逻辑。)
利用双缓冲来解决问题。
意思就是说, 逐像素点画图这件耗时的操作,不要直接操作显存,而是操作一块预先分配好的和显存一样大小的缓冲区,等逐点画图完成之后,一次性将该缓冲区的内容memcpy到事先mmap好的显存地址空间。
关于 双缓冲 技术我就不多说了,这技术的解释已经烂大街了,诸如什么流水线相关的形而上解释,看着都烦了,不过确实是那么回事。
直接上代码吧,先看Java代码 Drawimage.java :
import java.awt.image.*; import java.io.*; import javax.imageio.ImageIO; public class Drawimage { static File src = null; static BufferedImage img = null; native static void setFB(int x, int y, int rgb); native static void show(int x); static { System.loadLibrary("setFB"); } static void setPoint(int width, int height, int xoffset, int yoffset) { int i, j, rgb, a1; for (i = 1; i < width - 1; i++) { for (j = 1; j < height-1; j++) { rgb = img.getRGB(i, j); a1 = (rgb>>24)&0xFF; if (a1 != 0) { Drawimage.setFB(i + xoffset, j + yoffset, rgb); } } } } public static void main(String[] args) throws IOException { int i, j, width, height, xoffset = 0, yoffset = 0; src = new File(args[0]); img = ImageIO.read(src); width = img.getWidth(); height = img.getHeight(); while(true) { setPoint(width, height, xoffset%200, yoffset%50); Drawimage.show(0); // 清空之前的图形 Drawimage.show(1); // 显示当下的图形 try { Thread.sleep(100); // 这个时间频率最好和你的显示器刷新频率切合。 } catch (InterruptedException e) { } xoffset += 2; // x漂移 yoffset += 2; // y漂移 } } }
再看本地代码 setFB.c :
#include <fcntl.h> #include <sys/mman.h> #include <linux/fb.h> #include <stdio.h> #include <jni.h> unsigned int *mem = NULL; // 定义第二缓冲区 unsigned int *back_buffer = NULL; static struct fb_var_screeninfo info; void setPixel(int x, int y, int c) { int idx; if (x < 0 || x >= info.xres || y < 0 || y >= info.yres) { return; } idx = y*info.xres + x; // 操作第二缓冲区,而不是直接操作显存 back_buffer[idx] = c; } JNIEXPORT void JNICALL Java_Drawimage_show (JNIEnv *env, jclass class, int a) { // show指令下达,说明画图操作已经完成,这里一次性替换显存的内容 // 注意,替换的时机最好是显示器刷新的时机,完美契合!! if (a) { memcpy(mem, back_buffer, info.xres*info.yres*info.bits_per_pixel/8); memset(back_buffer, 0, info.xres*info.yres*info.bits_per_pixel/8); } else { memset(mem, 0, info.xres*info.yres*info.bits_per_pixel/8); } } JNIEXPORT void JNICALL Java_Drawimage_setFB (JNIEnv *env, jclass class, jint x, jint y, int rgb) { static int fd = -1; if (fd == -1) { fd = open("/dev/fb0", O_RDWR); if (ioctl(fd, FBIOGET_VSCREENINFO, &info)) { exit(1); } info.bits_per_pixel = 8; info.xres = 800; info.yres = 600; if (ioctl(fd, FBIOPUT_VSCREENINFO, &info)) { exit(1); } mem = (unsigned int *)mmap(NULL, info.xres*info.yres*info.bits_per_pixel/8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (mem == NULL) { printf("exit/n"); exit(1); } back_buffer = (unsigned int *)mmap(NULL, info.xres*info.yres*info.bits_per_pixel/8, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); if (back_buffer == NULL) { printf("back_buffer exit/n"); exit(1); } } setPixel(x, y, rgb); }
动图不闪哦:
双缓冲的原理就是这么简单。10年前看的的那些冗长的Java代码,都是假的,及其虚假。
皮鞋:mans_shoe:下雨进水,一定会湿,问题的关键不是会不会湿,而是皮鞋进水湿了之后, 会不会胖!
浙江温州皮鞋湿,下雨进水不会胖。