六、绘制图片

前面的几个示例,我们已经展示过如果在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在项目中只需要把这个头文件放到项目中,后续编译打包时不需要考虑开发库是否安装,发布时不需要考虑动态库依赖问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值