文章目录
- 1.创建一个红色图片
- 2.加载bmp图片
- 3.加载png、jpg图片
前面的几个示例,我们已经展示过如果在Linux系统下使用xlib接口向窗口中绘制文本、线、矩形;并设置文本、线条的颜色。并利用xlib提供的接口结合事件处理机制完成了一个自绘按钮控件功能。有时我们可能需要将已有的图片贴到窗口中,以实现更炫丽的效果。在xlib窗口系统中我们可以使用XImage存储图片像素的RGB值(当前也有可能会有Alpha通道)值。
1.创建一个红色图片
第一个例子,我们使用XImage向窗口中放入一个红色块图片
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdlib.h>
void DoPaint(Display *display, Window window) {
int screen_num = DefaultScreen(display);
int width = 200;
int height = 100;
//为每个像素分配4个字节,用于存储像素的ARGB值。
unsigned char *pixelBuffer = (unsigned char *)malloc(width*height*4);
for (int i=0;i<height;i++) {
for (int j=0;j<width;j++) {
//创建XCreateImage,每个像素的存储采用的字节序是BGRA
unsigned char *pixel = pixelBuffer + (i*width+j)*4;
pixel[3] = 0xff;
pixel[2] = 0xff;
pixel[1] = 0x0;
pixel[0] = 0x0;
}
}
XImage *image = XCreateImage(display,DefaultVisual(display,screen_num),DefaultDepth(display,screen_num),ZPixmap,
0,
(char *)pixelBuffer,
width,height,32,0);
GC gc = XCreateGC(display,window,0,nullptr);
XPutImage(display,window,gc,image,0,0,30,30,200,100);
XFreeGC(display,gc);
XDestroyImage(image);
}
int main() {
Display *display = XOpenDisplay(NULL);
Window win = XCreateSimpleWindow(display, DefaultRootWindow(display), 10, 10, 300, 200, 1,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
XSelectInput(display, win, ExposureMask);
XMapWindow(display, win);
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == Expose) {
DoPaint(display,win);
}
}
XDestroyWindow(display,win);
XCloseDisplay(display);
return 0;
}
以上的示例中我们没有直接从文件中加载一个png或是bmp图片,然后显示到窗口上。而是分配了一块内存,通过程序设置每个像素的颜色。这样做的目的能够让我们了解到xlib把图片绘制到窗口的基本原理,在不了解原理情况,如果我们直接把png图片加载显示到窗口上,没有出现我们想要的效果;很分析问题出现在哪里。编译以上程序,运行结果如下:
2.加载bmp图片
所使用的bmp图片如下:
接下来我们来加载一个bmp图片。程序代码如下
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdlib.h>
#include <cstdint>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义 BMP 文件头结构体(Windows 标准)
#pragma pack(push, 1)
typedef struct {
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
} BITMAPFILEHEADER;
typedef struct {
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
} BITMAPINFOHEADER;
#pragma pack(pop)
// 加载 24 位 BMP 图像
unsigned char* load_bmp_24bit(const char *filename, int *out_width, int *out_height) {
FILE *file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Failed to open file: %s\n", filename);
return NULL;
}
BITMAPFILEHEADER file_header;
BITMAPINFOHEADER info_header;
fread(&file_header, sizeof(BITMAPFILEHEADER), 1, file);
fread(&info_header, sizeof(BITMAPINFOHEADER), 1, file);
if (file_header.bfType != 0x4D42 || info_header.biBitCount != 24 || info_header.biCompression != 0) {
fprintf(stderr, "Unsupported BMP format.\n");
fclose(file);
return NULL;
}
*out_width = info_header.biWidth;
*out_height = info_header.biHeight;
// 计算每行字节数(注意:每行要对齐 4 字节)
int row_padded = ((info_header.biWidth * 3 + 3) / 4) * 4;
int row_unpadded = info_header.biWidth * 3;
int size_data = row_padded * info_header.biHeight;
unsigned char *data = (unsigned char*)malloc(size_data);
unsigned char *img_data = (unsigned char *)malloc(info_header.biWidth * info_header.biHeight * 3);
fseek(file, file_header.bfOffBits, SEEK_SET);
for (int y = 0; y < info_header.biHeight; y++) {
fread(data, 1, row_padded, file);
memcpy(img_data + (info_header.biHeight - 1 - y) * row_unpadded, data, row_unpadded);
}
free(data);
fclose(file);
return img_data;
}
void DoPaint(Display *display, Window window) {
int screen_num = DefaultScreen(display);
int width = 0;
int height = 0;
unsigned char *buffer = load_bmp_24bit("flower.bmp",&width,&height);
unsigned char *imageData = (unsigned char*)malloc(width*height*4);
for (int i=0;i<height;i++) {
for (int j=0;j<width;j++) {
unsigned char *originPixel = buffer + (i*width+j)*3;
unsigned char *pixel = imageData + (i*width+j)*4;
pixel[3] = 0xff;
pixel[0] = originPixel[0];
pixel[1] = originPixel[1];
pixel[2] = originPixel[2];
}
}
free(buffer);
XImage *image = XCreateImage(display, DefaultVisual(display,screen_num), DefaultDepth(display,screen_num), ZPixmap, 0,
(char *)imageData, width, height, 32, 0);
GC gc = XCreateGC(display,window,0,nullptr);
XPutImage(display,window,gc,image,0,0,0,0,width,height);
XFreeGC(display,gc);
XDestroyImage(image);
}
int main() {
Display *display = XOpenDisplay(NULL);
Window win = XCreateSimpleWindow(display, DefaultRootWindow(display), 10, 10, 800, 600, 1,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
XSelectInput(display, win, ExposureMask);
XMapWindow(display, win);
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == Expose) {
DoPaint(display,win);
}
}
XDestroyWindow(display,win);
XCloseDisplay(display);
return 0;
}
这里我们把一个bmp图片加载到内存,并且没有使用任何第三方的图片加载库,这样我们编译代码就不需要再去考虑三方库的安装问题。在DoPaint函数,我们把从bmp文件加载的每个像素3个字节转换为使用每个像素使用4个字节存储,如果XCreateImage使用每个像素使用3个字节表示,不能得到我们想要的效果。使用g++编译以上代码运行,结果如下:
3.加载png、jpg图片
png、jpg类型的图片,是对原始的像素数据进行了压缩存储,所以我们首先需要将png或jpg真实的像素值还原加载到内存,再创建XImage图片,显示到窗口。对于png可以使用libpng、对于jpg可以使用libjpeg将真实的像素还加载到内存。这里给出一个使用libpng来加载png图片的示例。实现代码如下:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <png.h>
#include <stdlib.h>
// 加载 PNG 文件的函数
unsigned char *load_png(const char *filename, int *width, int *height) {
FILE *fp = fopen(filename, "rb");
if (!fp) {
fprintf(stderr, "无法打开文件: %s\n", filename);
return NULL;
}
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) {
fclose(fp);
return NULL;
}
png_infop info = png_create_info_struct(png);
if (!info) {
png_destroy_read_struct(&png, NULL, NULL);
fclose(fp);
return NULL;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, &info, NULL);
fclose(fp);
return NULL;
}
png_init_io(png, fp);
png_read_info(png, info);
*width = png_get_image_width(png, info);
*height = png_get_image_height(png, info);
int color_type = png_get_color_type(png, info);
int bit_depth = png_get_bit_depth(png, info);
// 转换为 8 位 RGB 或 RGBA 格式
if (bit_depth == 16)
png_set_strip_16(png);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png);
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
png_read_update_info(png, info);
int rowbytes = png_get_rowbytes(png, info);
unsigned char *image_data = static_cast<unsigned char *>(malloc(rowbytes * (*height)));
png_bytep *row_pointers = static_cast<png_bytep *>(malloc(sizeof(png_bytep) * (*height)));
for (int y = 0; y < *height; y++)
row_pointers[y] = image_data + y * rowbytes;
png_read_image(png, row_pointers);
free(row_pointers);
png_destroy_read_struct(&png, &info, NULL);
fclose(fp);
return image_data;
}
void DoPaint(Display *display, Window window) {
int screen_num = DefaultScreen(display);
int width = 0;
int height = 0;
unsigned char *buffer = load_png("icon.png",&width,&height);
unsigned char *imageData = (unsigned char*)malloc(width*height*4);
for (int i=0;i<height;i++) {
for (int j=0;j<width;j++) {
unsigned char *originPixel = buffer + (i*width+j)*4;
unsigned char *pixel = imageData + (i*width+j)*4;
pixel[3] = originPixel[3];
pixel[0] = originPixel[2];
pixel[1] = originPixel[1];
pixel[2] = originPixel[0];
}
}
free(buffer);
XImage *image = XCreateImage(display, DefaultVisual(display,screen_num), DefaultDepth(display,screen_num), ZPixmap, 0,
(char *)imageData, width, height, 32, 0);
GC gc = XCreateGC(display,window,0,nullptr);
XPutImage(display,window,gc,image,0,0,0,0,width,height);
XFreeGC(display,gc);
XDestroyImage(image);
}
int main() {
Display *display = XOpenDisplay(NULL);
Window win = XCreateSimpleWindow(display, DefaultRootWindow(display), 10, 10, 800, 600, 1,
BlackPixel(display, DefaultScreen(display)),
WhitePixel(display, DefaultScreen(display)));
XSelectInput(display, win, ExposureMask);
XMapWindow(display, win);
while (1) {
XEvent e;
XNextEvent(display, &e);
if (e.type == Expose) {
DoPaint(display,win);
}
}
XDestroyWindow(display,win);
XCloseDisplay(display);
return 0;
}
要编译以上代码我们需要安装libpng开发库。ubuntu下安装libpng开发库的脚本如下
sudo apt install libpng-dev
使用g++编译以上程序,需要加上链接X11、png库
g++ xxx.cpp -o app -lX11 -lpng
运行结果示例如下:
另外还有一些开源库用于加载到内存,如stb可以用来加载bmp、png、jpg图片,该项目的代码地址GitHub - nothings/stb: stb single-file public domain libraries for C/C++
我们只需要使用该项目中的stb_image.h头文件(是的,只需要包含一个头文件)。我们在项目中倾向使用stb_image在项目中只需要把这个头文件放到项目中,后续编译打包时不需要考虑开发库是否安装,发布时不需要考虑动态库依赖问题。