当前位置: 首页 > news >正文

解码Linux文件IO之BMP 图像原理与应用

BMP 基本概念

定义与核心特点

BMP(Bitmap,位图)是微软提出的图像文件格式,全称 “设备无关位图(DIB)”,核心特点如下:

  • 无压缩:像素数据直接存储,无需解码器即可读取,开发中操作简单;
  • 文件较大:无压缩导致文件体积大,不适合网络传输,适合本地开发(如 LCD 显示);
  • 设备无关:图像数据与显示设备无关,在不同硬件上显示效果一致;
  • 颜色支持全:可支持 1bit(2 色)、4bit(16 色)、8bit(256 色)、16bit(65536 色)、24bit(1678 万色)、32bit(增强真彩色),其中 24bit 是开发中最常用的类型。

image

与其他图像格式的区别

格式压缩方式解码难度文件大小适用场景
BMP无压缩无(直接读)本地开发、LCD 显示
JPEG有损压缩需解码器照片、网络传输
PNG无损压缩需解码器图标、透明图像、网络传输

BMP 内部结构

BMP 文件从开头到结尾依次分为 4 个部分,总结构可表示为:位图文件头(14字节) + 位图信息头(40字节) + 调色板(可选) + 位图数据,其中前两部分固定共 54 字节(需重点记忆)。

image

位图文件头(BITMAPFILEHEADER,14 字节)

作用:存储 BMP 文件的基础信息,用于识别文件类型和定位像素数据。结构体定义与字段说明(需注意:编译器默认会对结构体进行 “字节对齐”,需用#pragma pack(1)取消对齐,否则会读错数据):

#pragma pack(1) // 取消结构体字节对齐,确保14字节大小
// 位图文件头结构体(14字节)
typedef struct {unsigned short bfType;        // 2字节:文件标识,必须为0x4D42(即ASCII码“BM”)unsigned int   bfSize;        // 4字节:BMP文件总大小(单位:字节)unsigned short bfReserved1;   // 2字节:保留字,必须为0unsigned short bfReserved2;   // 2字节:保留字,必须为0unsigned int   bfOffBits;     // 4字节:从文件开头到“位图数据”的偏移量(单位:字节)
} BITMAPFILEHEADER;
#pragma pack() // 恢复默认对齐

字段示例(结合十六进制查看器):

  • bfType:0x4D42 → 存储为42 4D(小端);
  • bfSize:若文件总大小为 705 字节(0x000002C1)→ 存储为C1 02 00 00
  • bfOffBits:若偏移量为 54 字节(0x00000036)→ 存储为36 00 00 00(无调色板时,偏移量通常为 54)。

位图信息头(BITMAPINFOHEADER,40 字节)

作用:存储 BMP 图像的技术参数,如分辨率、色深、压缩方式等。结构体定义与字段说明

#pragma pack(1)// 位图信息头结构体(40字节)
typedef struct {unsigned int   biSize;         // 4字节:本结构体大小(固定为40字节,0x28)int            biWidth;        // 4字节:图像宽度(单位:像素,正数)int            biHeight;       // 4字节:图像高度(单位:像素;正数=从下到上存储,负数=从上到下存储)unsigned short biPlanes;       // 2字节:色彩平面数,必须为1unsigned short biBitCount;     // 2字节:色深(每个像素的bit数),常见1/4/8/24/32unsigned int   biCompression;  // 4字节:压缩方式,0=无压缩(开发中常用)unsigned int   biSizeImage;    // 4字节:位图数据总大小(单位:字节,=每行字节数×高度)int            biXPelsPerMeter;// 4字节:水平分辨率(像素/米,设备无关时填0)int            biYPelsPerMeter;// 4字节:垂直分辨率(像素/米,设备无关时填0)unsigned int   biClrUsed;      // 4字节:实际使用的颜色数(0=使用所有色深对应的颜色)unsigned int   biClrImportant; // 4字节:重要颜色数(0=所有颜色都重要)
} BITMAPINFOHEADER;
#pragma pack()

关键字段注意点

  • biHeight:正数表示像素 “从下到上” 存储(BMP 默认),负数表示 “从上到下”,开发中需根据此值判断是否需要翻转行;
  • biBitCount:24bit(真彩色)最常用,无调色板,像素数据直接为 BGR 格式;
  • biCompression:必须为 0(无压缩),否则需解码,开发中一般只处理无压缩 BMP。

调色板(可选,RGBQUAD)

作用:为低色深(1/4/8bit)BMP 提供颜色映射,高色深(16/24/32bit)BMP 无调色板。调色板结构(RGBQUAD,4 字节 / 个)

#pragma pack(1)// 调色板颜色项结构体(4字节/个)
typedef struct {unsigned char rgbBlue;     // 1字节:蓝色分量(B)unsigned char rgbGreen;    // 1字节:绿色分量(G)unsigned char rgbRed;      // 1字节:红色分量(R)unsigned char rgbReserved; // 1字节:保留位,必须为0
} RGBQUAD;
#pragma pack()

调色板数量计算

  • 1bit BMP:2 种颜色 → 2 个 RGBQUAD → 8 字节;
  • 4bit BMP:16 种颜色 → 16 个 RGBQUAD → 64 字节;
  • 8bit BMP:256 种颜色 → 256 个 RGBQUAD → 1024 字节;
  • 24/32bit BMP:无调色板 → 0 字节。

示例:8bit BMP 中,若某调色板项为00 FF 00 00,表示该索引对应的颜色是纯绿色(B=0,G=255,R=0)。

位图数据(核心)

作用:存储每个像素的颜色信息,格式由色深和是否有调色板决定。

数据格式

  • 有调色板(1/4/8bit):存储 “颜色索引”,每个索引对应调色板中的一个 RGBQUAD;
    • 1bit:每 8 个像素占 1 字节(每个像素 1bit,0/1 对应 2 种颜色);
    • 4bit:每 2 个像素占 1 字节(每个像素 4bit,0~15 对应 16 种颜色);
    • 8bit:每个像素占 1 字节(0~255 对应 256 种颜色);
  • 无调色板(24/32bit):直接存储颜色分量;
    • 24bit:每个像素 3 字节,顺序为B→G→R(重点!BMP 默认 BGR 顺序,非 RGB);
    • 32bit:每个像素 4 字节,顺序为B→G→R→A(A 为透明度,0 = 透明,255 = 不透明)。

行对齐规则

CPU 为提高读取效率,要求 BMP 每行的字节数必须是4 的倍数。若自然每行字节数(宽度 × 色深字节数)不是 4 的倍数,需在每行末尾补 “0” 字节,公式如下:

  • 色深字节数 = biBitCount / 8(如 24bit=3 字节,8bit=1 字节);
  • 自然每行字节数 = 宽度 × 色深字节数;
  • 补齐字节数 = (4 - (自然每行字节数 % 4)) % 4;
    • 注:要将自然每行字节数 % 4 = 0的情况考虑进去
  • 实际每行字节数 = 自然每行字节数 + 补齐字节数。

示例:10×10 的 24bit BMP(宽度 = 10,色深字节数 = 3):

  • 自然每行字节数 = 10×3 = 30;
  • 补齐字节数 = (4 - 30%4) %4 = (4-2)%4=2;
  • 实际每行字节数 = 30+2=32(是 4 的倍数);
  • 位图数据总大小 = 32×10=320 字节。

存储顺序

BMP 的像素数据是从下到上、从左到右存储的(biHeight 为正数时),例如 10×10 的图像:

  • 第 1 行数据(文件中)对应图像的 “第 10 行”(显示时的最下行);
  • 第 10 行数据(文件中)对应图像的 “第 1 行”(显示时的最上行);开发中需将行翻转,才能在 LCD 上正常显示(LCD 通常是从上到下显示)。

image

BMP 关键技术点

小端存储(必须掌握)

BMP 中多字节数据(如 int32、int16)采用小端字节序存储,即 “低字节存低地址,高字节存高地址”。示例:biWidth=800(十进制)=0x00000320(十六进制):

  • 小端存储顺序:20 03 00 00(低字节 20 存低地址,高字节 00 存高地址);
  • 读取时:需按小端解析,将字节组合为 0x00000320,即 800 像素。

验证工具:用十六进制查看器(如 Hex Workshop)打开 BMP,查看文件头的 bfSize(偏移 0x02~0x05),若显示 “93 1F 00 00”,则实际值为 0x00001F93=705 字节。

image

像素颜色转换(BMP→LCD)

LCD 通常使用 32bit ARGB 格式(A = 透明度,R = 红,G = 绿,B = 蓝),而 24bit BMP 是 32bit BGR 格式(无 A),需进行格式转换:

  • 24bit BMP 像素:B(1 字节)、G(1 字节)、R(1 字节);
  • 32bit LCD 颜色:A(1 字节,设为 0x00 = 不透明)、R(1 字节)、G(1 字节)、B(1 字节);
  • 转换公式:lcd_color = (R << 16) | (G << 8) | (B << 0) | (0x00 << 24)

示例:BMP 像素为 B=0x00、G=0x00、R=0xFF(纯红):

  • 转换后 LCD 颜色 = (0xFF<<16) | (0x00<<8) | (0x00<<0) = 0x00FF0000(ARGB)。

行翻转处理(BMP→LCD)

由于 BMP 是 “从下到上” 存储,LCD 是 “从上到下” 显示,需将 BMP 的行数据翻转:

  • 假设 BMP 高度为 H,读取 BMP 的第 i 行数据(0≤i<H),对应 LCD 的第(H-1 -i)行;
  • 示例:BMP 第 0 行(文件中)→ LCD 第 9 行(H=10 时),BMP 第 9 行→ LCD 第 0 行。

BMP 实战代码

读取 BMP 的宽、高、文件大小(命令行传参)

功能:通过命令行传入 BMP 文件路径,输出图像的宽度、高度、文件总大小。代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
// 取消结构体对齐,确保读取14字节和40字节
#pragma pack(1)
typedef struct {unsigned short bfType;        // 2字节:文件标识("BM")unsigned int   bfSize;        // 4字节:文件总大小(字节)unsigned short bfReserved1;   // 2字节:保留字(0)unsigned short bfReserved2;   // 2字节:保留字(0)unsigned int   bfOffBits;     // 4字节:位图数据偏移量(字节)
} BITMAPFILEHEADER;typedef struct {unsigned int   biSize;         // 4字节:结构体大小(40)int            biWidth;        // 4字节:宽度(像素)int            biHeight;       // 4字节:高度(像素)unsigned short biPlanes;       // 2字节:色彩平面数(1)unsigned short biBitCount;     // 2字节:色深(bit)unsigned int   biCompression;  // 4字节:压缩方式(0=无压缩)unsigned int   biSizeImage;    // 4字节:位图数据大小(字节)int            biXPelsPerMeter;// 4字节:水平分辨率(0)int            biYPelsPerMeter;// 4字节:垂直分辨率(0)unsigned int   biClrUsed;      // 4字节:使用颜色数(0)unsigned int   biClrImportant; // 4字节:重要颜色数(0)
} BITMAPINFOHEADER;
#pragma pack()
int main(int argc, char *argv[]) {// 检查命令行参数(需传入BMP文件路径)if (argc != 2) {printf("用法:%s <BMP文件路径>\n", argv[0]);printf("示例:%s test.bmp\n", argv[0]);exit(1);}// 打开BMP文件(只读模式)/*** @brief open函数:打开文件* @param argv[1]  命令行传入的BMP文件路径(如"test.bmp")* @param O_RDONLY 打开模式:只读* @return         成功返回文件描述符(非负整数);失败返回-1*/int bmp_fd = open(argv[1], O_RDONLY);if (bmp_fd == -1) {perror("open BMP文件失败"); // 打印错误原因(如文件不存在)exit(1);}// 读取位图文件头(14字节)BITMAPFILEHEADER file_header;/*** @brief read函数:从文件读取数据* @param bmp_fd    BMP文件描述符* @param &file_header 存储读取数据的缓冲区(文件头结构体)* @param sizeof(file_header) 读取的字节数(14)* @return          成功返回读取的字节数(14);失败返回-1*/int ret = read(bmp_fd, &file_header, sizeof(file_header));if (ret != sizeof(file_header)) {perror("read 文件头失败");close(bmp_fd);exit(1);}// 验证是否为BMP文件(bfType必须为0x4D42,即"BM")if (file_header.bfType != 0x4D42) {printf("错误:%s 不是BMP文件\n", argv[1]);close(bmp_fd);exit(1);}// 读取位图信息头(40字节)BITMAPINFOHEADER info_header;ret = read(bmp_fd, &info_header, sizeof(info_header));if (ret != sizeof(info_header)) {perror("read 信息头失败");close(bmp_fd);exit(1);}// 输出BMP关键信息printf("BMP文件路径:%s\n", argv[1]);printf("文件总大小:%u 字节\n", file_header.bfSize);printf("图像宽度:%d 像素\n", info_header.biWidth);printf("图像高度:%d 像素\n", abs(info_header.biHeight)); // 取绝对值,忽略存储方向printf("色深:%d bit\n", info_header.biBitCount);printf("压缩方式:%s(%u)\n", info_header.biCompression == 0 ? "无压缩" : "有压缩", info_header.biCompression);// 关闭文件,释放资源close(bmp_fd);return 0;
}

编译与运行

gcc bmp_info.c -o bmp_info
./bmp_info test.bmp

运行结果

BMP文件路径:test.bmp
文件总大小:xxx 字节
图像宽度:xxx 像素
图像高度:xxx 像素
色深:24 bit
压缩方式:无压缩(0)

在 LCD 上显示 24bit 无压缩 BMP

功能:打开 24bit 无压缩 BMP,在 800×480 的 LCD 上显示(需确保 BMP 分辨率与 LCD 一致)。

代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <string.h>
#pragma pack(1)
typedef struct {unsigned short bfType;        // 2字节:"BM"unsigned int   bfSize;        // 4字节:文件总大小unsigned short bfReserved1;   // 2字节:0unsigned short bfReserved2;   // 2字节:0unsigned int   bfOffBits;     // 4字节:位图数据偏移量
} BITMAPFILEHEADER;typedef struct {unsigned int   biSize;         // 4字节:40int            biWidth;        // 4字节:宽度int            biHeight;       // 4字节:高度unsigned short biPlanes;       // 2字节:1unsigned short biBitCount;     // 2字节:色深(24)unsigned int   biCompression;  // 4字节:0(无压缩)unsigned int   biSizeImage;    // 4字节:位图数据大小int            biXPelsPerMeter;// 4字节:0int            biYPelsPerMeter;// 4字节:0unsigned int   biClrUsed;      // 4字节:0unsigned int   biClrImportant; // 4字节:0
} BITMAPINFOHEADER;
#pragma pack()
int main(int argc, char *argv[]) {if (argc != 2) {printf("用法:%s <24bit BMP文件路径>\n", argv[0]);exit(1);}// -------------------------- 处理BMP文件 --------------------------int bmp_fd = open(argv[1], O_RDONLY);if (bmp_fd == -1) { perror("open BMP失败"); exit(1); }// 读文件头BITMAPFILEHEADER file_hdr;if (read(bmp_fd, &file_hdr, sizeof(file_hdr)) != sizeof(file_hdr)) {perror("read 文件头失败"); close(bmp_fd); exit(1);}if (file_hdr.bfType != 0x4D42) {printf("不是BMP文件\n"); close(bmp_fd); exit(1);}// 读取位图信息头(40字节),并校验读取是否完整BITMAPINFOHEADER info_hdr;if (read(bmp_fd, &info_hdr, sizeof(info_hdr)) != sizeof(info_hdr)) {perror("read 信息头失败"); close(bmp_fd); exit(1);}//判断是否为24bit无压缩if (info_hdr.biBitCount != 24 || info_hdr.biCompression != 0) {printf("仅支持24bit无压缩BMP\n"); close(bmp_fd); exit(1);}// 计算BMP每行参数(对齐处理)int bmp_width = info_hdr.biWidth;    // BMP宽度int bmp_height = abs(info_hdr.biHeight); // BMP高度(取绝对值)int bmp_pixel_bytes = 3;             // 24bit=3字节/像素int bmp_line_bytes = bmp_width * bmp_pixel_bytes; // 自然每行字节数int bmp_pad_bytes = (4 - (bmp_line_bytes % 4)) % 4; // 补齐字节数int bmp_line_total = bmp_line_bytes + bmp_pad_bytes; // 实际每行字节数// 定位到位图数据(跳过文件头、信息头、调色板)lseek(bmp_fd, file_hdr.bfOffBits, SEEK_SET);// -------------------------- 处理LCD设备 --------------------------// 打开LCD设备int lcd_fd = open("/dev/fb0", O_RDWR);if (lcd_fd == -1) { perror("open LCD失败"); close(bmp_fd); exit(1); }// 获取LCD参数(宽、高、色深)struct fb_var_screeninfo lcd_var;if (ioctl(lcd_fd, FBIOGET_VSCREENINFO, &lcd_var) == -1) {perror("ioctl 获取LCD参数失败"); close(lcd_fd); close(bmp_fd); exit(1);}int lcd_width = lcd_var.xres;    // LCD宽度(如800)int lcd_height = lcd_var.yres;  // LCD高度(如480)int lcd_pixel_bytes = lcd_var.bits_per_pixel / 8; // LCD字节/像素(32bit=4)// 内存映射LCD(直接操作内存控制LCD)size_t lcd_map_len = lcd_width * lcd_height * lcd_pixel_bytes;unsigned int *lcd_mem = (unsigned int *)mmap(NULL,               // 让系统分配地址lcd_map_len,        // 映射长度PROT_READ | PROT_WRITE, // 读写权限MAP_SHARED,         // 共享映射(修改同步到硬件)lcd_fd,             // LCD文件描述符0                   // 偏移量0);if (lcd_mem == MAP_FAILED) {perror("mmap LCD失败"); close(lcd_fd); close(bmp_fd); exit(1);}// -------------------------- BMP→LCD显示 --------------------------// 分配缓冲区存储BMP一行数据unsigned char *bmp_line_buf = (unsigned char *)malloc(bmp_line_total);if (bmp_line_buf == NULL) { perror("malloc 失败"); goto end; }// 读取BMP每行数据,转换后写入LCD(行翻转处理)for (int y = 0; y < bmp_height; y++) {// 读取BMP一行数据(包含补齐字节)read(bmp_fd, bmp_line_buf, bmp_line_total);// 计算LCD行号(BMP从下到上→LCD从上到下)int lcd_y = bmp_height - 1 - y; if (lcd_y >= lcd_height) break; // 超出LCD高度,跳过// 遍历每行每个像素,转换BGR→ARGB,写入LCDfor (int x = 0; x < bmp_width; x++) {if (x >= lcd_width) break; // 超出LCD宽度,跳过// 提取BMP像素的B、G、R分量(BGR顺序)unsigned char B = bmp_line_buf[x * 3 + 0];unsigned char G = bmp_line_buf[x * 3 + 1];unsigned char R = bmp_line_buf[x * 3 + 2];// 转换为LCD的ARGB格式(A=0x00不透明)unsigned int lcd_color = (R << 16) | (G << 8) | B | (0x00 << 24);// 写入LCD内存(LCD行号×LCD宽度 + LCD列号)lcd_mem[lcd_y * lcd_width + x] = lcd_color;}}// 等待按键退出(保持显示)printf("按任意键退出...\n");getchar();// -------------------------- 释放资源 --------------------------
end:free(bmp_line_buf);munmap(lcd_mem, lcd_map_len); // 解除LCD映射close(lcd_fd);close(bmp_fd);return 0;
}

等比例缩小 BMP 为 1/2(生成新文件)

功能:将任意尺寸 24bit 无压缩 BMP 等比例缩小为原来的 1/2(宽、高各减半),新文件路径通过命令行传递。核心思路

  • 读取原 BMP 的文件头、信息头;
  • 计算新 BMP 的宽(原宽 / 2)、高(原高 / 2),修改新文件头和信息头;
  • 读取原 BMP 的像素数据,每隔一个像素取一个(如原 (x,y)→新 (x/2,y/2));
  • 处理新 BMP 的行对齐,写入新文件。

代码框架(关键部分):

// 新BMP参数计算
int new_width = bmp_width / 2;    // 新宽度=原宽度/2
int new_height = bmp_height / 2;  // 新高度=原高度/2
int new_line_bytes = new_width * 3; // 新自然每行字节数
int new_pad_bytes = (4 - (new_line_bytes % 4)) % 4; // 新补齐字节数
int new_line_total = new_line_bytes + new_pad_bytes; // 新实际每行字节数
int new_data_size = new_line_total * new_height; // 新位图数据大小
int new_file_size = 54 + new_data_size; // 新文件总大小(无调色板,54字节头)// 构造新文件头
BITMAPFILEHEADER new_file_hdr;
new_file_hdr.bfType = 0x4D42;
new_file_hdr.bfSize = new_file_size;
new_file_hdr.bfReserved1 = 0;
new_file_hdr.bfReserved2 = 0;
new_file_hdr.bfOffBits = 54; // 无调色板,偏移54// 构造新信息头
BITMAPINFOHEADER new_info_hdr;
new_info_hdr.biSize = 40;
new_info_hdr.biWidth = new_width;
new_info_hdr.biHeight = -new_height; // 设为负数,从上到下存储(无需翻转)
new_info_hdr.biPlanes = 1;
new_info_hdr.biBitCount = 24;
new_info_hdr.biCompression = 0;
new_info_hdr.biSizeImage = new_data_size;
new_info_hdr.biXPelsPerMeter = 0;
new_info_hdr.biYPelsPerMeter = 0;
new_info_hdr.biClrUsed = 0;
new_info_hdr.biClrImportant = 0;// 读取原BMP数据,缩小后写入新文件
unsigned char *old_line = malloc(bmp_line_total);
unsigned char *new_line = malloc(new_line_total);
int new_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); // 新建文件
write(new_fd, &new_file_hdr, sizeof(new_file_hdr)); // 写新文件头
write(new_fd, &new_info_hdr, sizeof(new_info_hdr)); // 写新信息头for (int y = 0; y < new_height; y++) {// 读取原BMP的2行(缩小1/2,取偶数行)lseek(bmp_fd, file_hdr.bfOffBits + (y*2) * bmp_line_total, SEEK_SET);read(bmp_fd, old_line, bmp_line_total);// 提取原BMP的偶数列像素,组成新行for (int x = 0; x < new_width; x++) {int old_x = x * 2; // 取原BMP的偶数列new_line[x*3 + 0] = old_line[old_x*3 + 0]; // Bnew_line[x*3 + 1] = old_line[old_x*3 + 1]; // Gnew_line[x*3 + 2] = old_line[old_x*3 + 2]; // R}// 补0字节(对齐)memset(new_line + new_line_bytes, 0, new_pad_bytes);// 写入新BMP的一行write(new_fd, new_line, new_line_total);
}

注意事项

  • 结构体对齐问题:编译器默认会对结构体进行 “字节对齐”(如 int 按 4 字节对齐),导致 14 字节的文件头被扩展为 16 字节,必须用#pragma pack(1)取消对齐,否则读取数据错误;
  • biHeight 正负问题:biHeight 为正数时 BMP 从下到上存储,为负数时从上到下存储,开发中需用abs(biHeight)获取高度,根据正负判断是否需要行翻转;
  • LCD 与 BMP 分辨率不一致:若 BMP 分辨率小于 LCD,可指定显示位置(如居中);若大于 LCD,需进行缩放(如双线性插值),避免显示不全;
  • 调色板 BMP 处理:对于 8bit 以下 BMP,需先读取调色板,再根据像素索引查找对应的 BGR 颜色,再转换为 LCD 格式;
  • 权限问题:操作/dev/fb0需 root 权限,运行 LCD 相关程序时需用sudo ./xxx
http://www.dtcms.com/a/508835.html

相关文章:

  • 串口转以太网模块在电梯控制柜中的透明改造
  • Git 检出到HEAD 再修改提交commit 会消失解决方案
  • 徐州网站建设方案推广4399电脑版网页链接
  • 利用 Trie 树对仅由小写字母构成的多个字符串按字典序排序
  • 曲沃网站建设网站流量怎么提升
  • 从 KaTeX 到智能渲染:构建 Vue + LLM 的公式可视化体系
  • 大数据做网站流量分析seo推广的特点
  • 网站虚拟交易技术怎么做建站设计公司
  • 龙虎榜——20251020
  • 网站改版原因东莞专业网站推广策划
  • 扎根中亚十三年,科伦药业打造现代化综合性药厂
  • 基于C语言和Ncurses的俄罗斯方块游戏实现
  • 企业网站脚本语言网站代备案公司
  • 网站建设托管预算清单展厅设计培训
  • PCIe协议之 Equalization篇 之 关于 TxSwing 的理解
  • 海康域名网站有做门窗找活的网站吗
  • 福建省龙岩市建设培训中心网站网站内容一样影响收录
  • 流行网站类型大学网站建设宣传方案
  • 久久网站建设巴中市平昌县建设局网站
  • idea整合Git
  • 如何选择性价比高的中药饮片才能确保品质与效果?
  • 设计师网站图片重庆市建设工程信息网官网工程押证
  • 私人程序定制:纳什欺诈谈判
  • 呼和浩特市网站建设什么叫宣传类网站
  • 建设银行网站-个人业务泰州网站建设设计
  • MySQL的json处理相关方法
  • 兰州网站建设营销q479185700刷屏外贸网站排名
  • 网站加入地图企业网站维护工作
  • TDengine 数据函数 MOD 用户手册
  • 创业公司做网站免费开发游戏