使用Linux Framebuffer绘制32位真彩图形

Linux将显示器屏幕抽象成了一块连续的内存,这简直太棒了。因为 你可以通过写内存的方式在屏幕上作画了!

Linux是通过 逐行扫描 的方式布局这块内存的,即:

W × H W\times H W×H的屏幕上 P ( x , y ) P(x,y) P(x,y)坐标在这块内存 M M M中的位置是: I P = y × W + x I_P=y\times W+x IP=y×W+x

P ( x , y ) P(x,y) P(x,y) W × H W\times H W×H屏幕上的一个像素, M [ I P ] M[I_P] M[IP]处保存该像素的颜色值。

问题是 M [ I P ] M[I_P] M[IP]这个像素值设置成多少?它需要多少bit存储呢?

这就是RGB标准规定的了,比如常规的24bit三字节存储,每一个字节保存一个原色分量:

每像素24位(比特s per pixel,bpp)编码的RGB值:使用三个8位无符号整数(0到255)表示红色、绿色和蓝色的强度。这是当前主流的标准表示方法,用于真彩色和JPEG或者TIFF等图像文件格式里的通用颜色交换。它可以产生一千六百万种颜色组合,对人类的眼睛来说,其中有许多颜色已经是无法确切的分辨。

另外,32bit是最牛逼的,除了24bit保存三原色之外,还有一个8bit保存Alpha值,即透明度。这就是32bit真彩色!

如果每个像素通过3字节24bit来描述,那么整个 W × H W\times H W×H屏幕就需要 W × H × 3 W\times H\times 3 W×H×3字节这么大的内存,如果是4字节32bit描述一个真彩像素,那么整个屏幕所需的内存大小就是 W × H × 4 W\times H\times 4 W×H×4字节。


我们操作这块内存,按照屏幕的配置去设置 M [ I P ] M[I_P] M[IP]的值,就能实现屏幕作图。

如何获得这块内存呢? 映射/dev/fb0 即可!

如何得到 W W W H H H呢? ioctl 即可:

static struct fb_var_screeninfo info;
ioctl(fd, FBIOGET_VSCREENINFO, &info)
//W值:info.xres 
//H值:info.yres
//多少位存储一个像素信息:info.bits_per_pixel

现在让我们画一个矩形:

#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <stdio.h>

unsigned int *mem = 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) {
		printf("x:%d  y:%d\n", x, y);
		return -1;
	}

	idx = y*info.xres + x;
	mem[idx] = c;
}

int main()
{
	static int fd = -1;
	int i, j;

	fd = open("/dev/fb0", O_RDWR);
	if (ioctl(fd, FBIOGET_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) {
		exit(1);
	}
	for (i = info.xres/4; i < info.xres/2; i++) setPixel(i, info.yres/4, 0xffffffff);
	for (i = info.xres/4; i < info.xres/2; i++) setPixel(i, info.yres*3/4, 0xffffffff);
	for (i = info.yres/4; i < info.yres*3/4; i++) setPixel(info.xres/4, i, 0xffffffff);
	for (i = info.yres/4; i < info.yres*3/4; i++) setPixel(info.xres/2, i, 0xffffffff);
	return 0;
}

执行之,效果:
在这里插入图片描述

如何把背景去掉呢?

其实,说白了,屏幕上的所有东西,包括GUI也好,包括命令提示符也罢,都是 画出来 的,我们既然已经获得了/dev/fb0从而映射了内存,那么清空文件就是了:

[root@localhost frame]# dd if=/dev/zero of=/dev/fb0
dd: 正在写入"/dev/fb0": 设备上没有空间
记录了12289+0 的读入
记录了12288+0 的写出
6291456字节(6.3 MB)已复制,0.0118288 秒,532 MB/秒
[root@localhost frame]# ./a.out

在这里插入图片描述

清爽,干净!

我想从0到100做一个GUI系统,首先我希望这个屏幕是给力的,至少它能显示一种真彩色。我先用一个图片,看看能不能显示出来。

由于我不懂C如何编写图形处理程序,所以我用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);
	static {
		System.loadLibrary("setFB");
	}

	public static void main(String[] args) throws IOException {
		int i, j, width, height;
		int rgb, rgb1, rgb2, a1, a2, a3;
		src = new File(args[0]);
		img = ImageIO.read(src);
		width = img.getWidth();
		height = img.getHeight();

		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, j, rgb);
				}
			}
		}

	}
}

给出本地代码:

//setFB.c
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <stdio.h>
#include <jni.h>

unsigned char *mem = 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) {
		printf("x:%d  y:%d\n", x, y);
		return -1;
	}

	idx = y*info.xres + x;
	mem[idx] = c;
}

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);
		}
		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);
		}
	}
	setPixel(x, y, rgb);
}

在这里插入图片描述

这不是我想要的,这太Low了。我打印了info.xres和info.yres,发现它是:
1280 × 1024 ( 16 b i t ) 1280\times 1024(16bit) 1280×1024(16bit)

16bit不可能表示真彩的!我需要的是32bit!怎么办?

Linux内核有个VGA启动参数 vga=XXX 我也不知道怎么设置。所以我胡乱写一个,然后让系统提示我怎么做,我写 vga=12345

在这里插入图片描述

注意 1600 × 1200 × 32 1600\times 1200\times 32 1600×1200×32 这个,那么我就选 347 吧。

注意,修改了显示模式后,代码也要相应的修改。

毕竟我们现在用32bit来表示一个像素,所以我们要把mem的定义改一下,用4字节32位的int替代1个字节8位的char:

unsigned int *mem = NULL;

按照上述修改后的代码,执行,结果如下:
在这里插入图片描述

锃亮的!如果不想要背后的背景,那就 dd if=/dev/zero of=/dev/fb0 如果想要个纯色的背景,那就改下Java代码:

				rgb = img.getRGB(i, j);
				a1 = (rgb>>24)&0xFF;
				if (a1 != 0) {
					Drawimage.setFB(i, j, rgb);
				} else {
					rgb = 0xff123456;
					Drawimage.setFB(i, j, rgb);
				}

效果如下:
在这里插入图片描述

当奢华遇到暴力:
在这里插入图片描述

虽然没有什么难度,也没有什么意义,但确实好玩

在这里插入图片描述

在这里插入图片描述


如果加上以下技术,就可以自己实现一个GUI系统了:

  • 多线程
  • 鼠标键盘监控
  • 事件处理
  • Buffer重绘
  • 双缓冲

这里难度最大的就是Buffer重绘和双缓冲技术。


字符,命令提示符,GUI,这些都是画出来的,如何画是难点,如何重绘事难点的难点。


皮鞋?湿,不会胖。

  • 16
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值