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
#include
#include
#include
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
#include
#include
#include
#include
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重绘和双缓冲技术。
…
字符,命令提示符,GUI,这些都是画出来的,如何画是难点,如何重绘事难点的难点。
皮鞋?湿,不会胖。