[LINUX] Input and output display images directly using the frame buffer (/ dev / fb0) on Raspberry Pi

About this article

Try reading and writing the framebuffer (/ dev / fbXX) on Linux. This article specifically mentions it for the Raspberry Pi, but I think it's the same for other Linux. The confirmed environment is Raspberry Pi 2, the image output is HDMI, and the resolution is DMT mode 9 (800x600). Main use:

――I want to output the screen of Raspberry Pi to my own display device ――It's easier to use https://github.com/notro/fbtft/ for this purpose. --Send the Raspberry Pi screen over the network and try a similar non-remote desktop ――I'm thinking of making an app, but it's troublesome to use a graphic engine. I want to output directly to an HDMI display without thinking about difficult things.

About frame buffer

On Linux, you can access the framebuffer through the device file (/ dev / fbXX). It is prepared as / dev / fb0 in Raspberry Pi. This content is output to the display or HDMI.

Play with commands

Get RAW image data from the framebuffer

You can save the image data by typing the following from the command line (same as screen capture). If you do the opposite, you can write (draw on the display).

cat /dev/fb0 > aaa.raw

Get framebuffer information

The acquired RAW image data is composed of 4 bytes (32 bits) such as ARGB per pixel. This is raw pixel data, unlike JPEG and BMP. Therefore, it is necessary to specify the size etc. when playing. You can get the information you need with the following command.

fbset

mode "800x600"
    geometry 800 600 800 600 32
    timings 0 0 0 0 0 0 0
    rgba 8/16,8/

Alternatively, you can get the same result with the following command.

cat /sys/class/graphics/fb0/bits_per_pixel
32

cat /sys/class/graphics/fb0/virtual_size
800,600

View with image viewer

This is a setting example when viewing with IrfanView. You can make various settings by dragging and dropping an image with a RAW extension. If your display has the above settings, you should be able to see it correctly with the following settings. (The setting value changes depending on the environment)

irfan.jpg

Note

--Is the size of the framebuffer halfway? --Set the type to DMT in the Raspy screen resolution setting. CEA is smaller than the specified size (because of the blanking area for TV data information?) --Does the saved file size not fit the calculation? --Probably because the horizontal and vertical sizes are rounded up to 32 pixels. --File size = horizontal (px) x vertical (px) x 4Byte --On a remote desktop such as VNC, writing to the framebuffer and drawing something on the display may not update anything.

It should have been painted in red, but with VNC it will not be fully updated. Looking at HDMI, it is filled properly. image.png

Access the framebuffer from a C / C ++ program

Get framebuffer information

This is the code to get the framebuffer information. There are two ways to do it. The result is the same regardless of which one you do. getFrameBufferSize () reads the information as a string from the above / sys / class / graphics / fb0 / bits_per_pixel. getFrameBufferSize2 () uses ʻioctl ()` to get device information.

getSizeFrameBuffer.cpp


#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <linux/omapfb.h>

void getFrameBufferSize(int* width, int* height, int* colorWidth)
{
	int fd;
	int n;
	char str[64];

	/* Get Bit Per Pixel (usually 32-bit(4Byte) */
	fd = open("/sys/class/graphics/fb0/bits_per_pixel", O_RDONLY);
	n = read(fd, str, sizeof(str));
	str[n] = '\0';
	*colorWidth = atoi(str);
	close(fd);

	/* Get Frame Buffer Size */
	fd = open("/sys/class/graphics/fb0/virtual_size", O_RDONLY);
	n = read(fd, str, sizeof(str));
	str[n] = '\0';
	/* divide "800,600" into "800" and "600" */
	*width  = atoi(strtok(str, ","));
	*height = atoi(strtok(NULL, ","));
	close(fd);
}

void getFrameBufferSize2(int* width, int* height, int* colorWidth)
{
	struct fb_var_screeninfo var;
	int fd;
	fd = open("/dev/fb0", O_RDWR);
	ioctl(fd, FBIOGET_VSCREENINFO, &var);
	*colorWidth = var.bits_per_pixel;
	*width      = var.xres_virtual;
	*height     = var.yres_virtual;
	close(fd);
}

Read the image data in the frame buffer

Code that reads image data from the frame buffer. It does the same thing as cat above by opening, reading and closing to / dev / fb0. You can also read by mmaping / dev / fb0. They are readBuffer () and readBuffer2 (), respectively. You can read the data using either one, but depending on how you use it, useless memory copy may occur, so it is better to use it according to the purpose.

readFrameBuffer.cpp


#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <linux/omapfb.h>

void readBuffer(int width, int height, void* dstBuffer)
{
	int fd;
	fd = open("/dev/fb0", O_RDONLY);
	read(fd, dstBuffer, width * height * 4);
	close(fd);
}

void readBuffer2(int width, int height, void* dstBuffer)
{
	int fd;
	fd = open("/dev/fb0", O_RDONLY);
	uint32_t *frameBuffer = (uint32_t *)mmap(NULL, width * height * 4, PROT_READ, MAP_SHARED, fd, 0);
	for(int y = 0; y < height; y++) {
		for(int x = 0; x < width; x++) {
			((uint32_t*)dstBuffer)[x + y * width] = frameBuffer[x + y * width];	
		}
	}

	munmap(frameBuffer, width * height * 4);
	close(fd);
}

Write image data to the framebuffer

Code that writes image data to the framebuffer. This allows you to draw directly on the display (HDMI output). However,

--The screen may not be updated with VNC --In both GUI and CUI, if there is a screen drawing on the main body (OS?) Side, that part (not the entire screen) will be overwritten.

There is a problem such as. It would be nice to have a way to completely hijack access to the framebuffer, but we're investigating.

You can write by opening, writing, and closing to / dev / fb0. Here, as an example, a buffer filled with a single color is output. You can do the same with mmap as well as with reading. I think that mmap will be more efficient depending on the application because it can write directly. However, when using mmap, be aware that the value may not actually be written until you call msync.

writeFrameBuffer.cpp


#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <linux/omapfb.h>

// color = AARRGGBB
void drawColor(int width, int height, int color)
{
	uint32_t *canvas = new uint32_t[width * height];
	for(int y = 0; y < height; y++) {
		for(int x = 0; x < width; x++) {
			canvas[x + y * width] = color;	
		}
	}

	int fd;
	fd = open("/dev/fb0", O_WRONLY);
	write(fd, canvas, width * height * 4);
	close(fd);
	delete[] canvas;
}

// color = AARRGGBB
void drawColor2(int width, int height, int color)
{
	int fd;
	fd = open("/dev/fb0", O_RDWR);
	uint32_t *frameBuffer = (uint32_t *)mmap(NULL, width * height * 4, PROT_WRITE, MAP_SHARED, fd, 0);
	for(int y = 0; y < height; y++) {
		for(int x = 0; x < width; x++) {
			frameBuffer[x + y * width] = color;	
		}
	}

	msync(frameBuffer, width * height * 4, 0);
	munmap(frameBuffer, width * height * 4);
	close(fd);
}


Demo program

An example is reading the framebuffer information, taking a screen capture, and filling the entire screen in red. However, even if it is painted in red, the area near the cursor in CUI mode and the area near the menu bar in GUI mode will be overwritten by the OS (it seems that the drawing is updated only where there is a change on the screen).

Compile with g ++ main.cpp getSizeFrameBuffer.cpp readFrameBuffer.cpp writeFrameBuffer.cpp

main.cpp


#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void getFrameBufferSize(int* width, int* height, int* colorWidth);
void getFrameBufferSize2(int* width, int* height, int* colorWidth);
void drawColor(int width, int height, int color);
void drawColor2(int width, int height, int color);
void readBuffer(int width, int height, void* dstBuffer);
void readBuffer2(int width, int height, void* dstBuffer);
void saveFileBinary(const char* filename, void* data, int size);

// g++ main.cpp getSizeFrameBuffer.cpp readFrameBuffer.cpp writeFrameBuffer.cpp

int main()
{
	int colorWidth;
	int width;
	int height;

	/* get frame buffer size */
	getFrameBufferSize(&width, &height, &colorWidth);
	printf("%d(W) x %d(H) x %d(Bit)\n", width, height, colorWidth);

	/* screen capture */
	uint32_t *buffer = new uint32_t[width * height];
	readBuffer(width, height, buffer);
	saveFileBinary("capture.raw", buffer, width * height * 4);
	delete[] buffer;

	/* Fill solid color */
	drawColor(width, height, 0xFFFF0000);	// RED
}


void saveFileBinary(const char* filename, void* data, int size)
{
	FILE *fp;
	fp = fopen(filename, "wb");
	fwrite(data, 1, size, fp);
	fclose(fp);
}

Todo --Investigation of how to completely hijack the framebuffer (currently, it is partially overwritten by the OS) --I think the correct way is to create a separate framebuffer (/ dev / fb1) and use that for HDMI output. ――I think it's a double buffer, but what is it like in / dev / fb?

Reference material

http://archive.linux.or.jp/JF/JFdocs/kernel-docs-2.6/fb/framebuffer.txt.html

Recommended Posts

Input and output display images directly using the frame buffer (/ dev / fb0) on Raspberry Pi
Weighing instrument using raspberry pi and hx711 (GUI display on Tkinter)
Sound the buzzer using python on Raspberry Pi 3!
Display images taken with the Raspberry Pi camera module
Try using the temperature sensor (LM75B) on the Raspberry Pi.
Output to "7-segment LED" using python on Raspberry Pi 3!
Get the weather using the API and let the Raspberry Pi speak!
Control the motor with a motor driver using python on Raspberry Pi 3!
MQTT on Raspberry Pi and Mac
Try using ArUco on Raspberry Pi
Initial settings for using GrovePi + starter kit and camera on Raspberry Pi
Detect "brightness" using python on Raspberry Pi 3!
Use the Grove sensor on the Raspberry Pi
Run servomotor on Raspberry Pi 3 using python
Detect temperature using python on Raspberry Pi 3!
Make a thermometer with Raspberry Pi and make it visible on the browser Part 3
Connect to the console of Raspberry PI and display local IP and SD information
I tried running Flask on Raspberry Pi 3 Model B + using Nginx and uWSGI
Using the 1-Wire Digital Temperature Sensor DS18B20 from Python on a Raspberry Pi
Use python on Raspberry Pi 3 and turn on the LED when it gets dark!
Detect slide switches using python on Raspberry Pi 3!
Try using a QR code on a Raspberry Pi
Detect magnet switches using python on Raspberry Pi 3!
Display CPU temperature every 5 seconds on Raspberry Pi 4
Install PyCall on Raspberry PI and try using GPIO's library for Python from Ruby
The story when I was using IntelliJ on Linux and could not input Japanese