RGB颜色空间
RGB可以分为两大类:一种是索引形式,一种是像素形式:
索引形式
:存储每个像素在调色板中的索引RGB1
:每个像素用1bit表示,调色板中只包含两种颜色(黑白)RGB4
:每个像素用4bit表示,调色板中包含16种颜色RGB8
:每个像素用4bit表示,调色板中包含255种颜色
像素形式
:存储每个像素的R、G、B值,需要注意存储顺序从低位到高位则是 B、G、RRGB 565
:每个像素占16bit(2字节),RGB分量分别使用5位、6位、5位
RGB 888
:每个像素24bit(3字节)
ARGB 8888
:每个像素32bit(4字节),A 表示透明度
BMP格式图片
BMP 全称 Bitmap-File,是微软出的图像文件格式,它没有进行任何压缩,所以可以直观看出每个像素在文件中如何存储。
存储可以分为如下四部分:
- BMP文件头(14 bytes) ,存放一些文件相关的信息。
- Bitmap 信息头,通常是 40 bytes 大小,存放一些图像相关的信息,例如宽高之类的数据。
- 调色板,在RGB索引形式时才会有,大小由颜色索引决定。
- 位图数据
BMP信息头
- 1-2字节:魔数,Windows中使用
BM
- 3-6字节:BMP文件大小
- 7-10字节:保留字节,均为0
- 11-14字节:位图数据的偏移值,即从哪个字节开始为位图数据
使用HXD
软件打开可以看到二进制数据:
Bitmap信息头
- 15-18字节:Bitmap信息头大小
- 19-22字节:图像的宽度
- 23-26字节:图像的高度
- 27-28字节:color planes,通常为1
- 29-30字节:每个像素用多少位存储
- 31-34字节:压缩类型,没有压缩用0表示
- 35-38字节:图像大小(宽 x 高 x 每像素位数)
- 39-42字节:水平分辨率
- 43-45字节:垂直分辨率
- 46-49字节:颜色索引表,只有RGB索引形式才使用
- 50-53字节:对图像显示有重要影响的颜色索引数码,只有RGB索引形式才使用
位图数据
存储顺序有两点注意:
- 使用小端序存储,所以排列顺序为 B、G、R,例如第一个像素的 B = 0x42,G = 0x39,R = 0x42
- 最开始的位置表示的图片左下角位置,逐行往上
代码实现
如下是一段命令行程序,作用是截取当前屏幕,并将其保存为BMP文件
#include <Windows.h>
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
// 获取屏幕尺寸
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
// 获取设备屏幕的上下文句柄
HDC hdcScreen = GetDC(NULL);
// 创建与屏幕兼容的上下文句柄
HDC hdcMem = CreateCompatibleDC(hdcScreen);
// 创建与屏幕兼容的 Bitmap
HBITMAP hbmScreen = CreateCompatibleBitmap(hdcScreen, screenWidth, screenHeight);
// 将 Bitmap 放入 hdcMem 中
SelectObject(hdcMem, hbmScreen);
// 截屏
BitBlt(hdcMem, 0, 0, screenWidth, screenHeight, hdcScreen, 0, 0, SRCCOPY);
// 每个像素占用位数
int bitCount = 24;
// bitmap 所有像素占用的字节数
int bitmapSize = screenWidth * screenHeight * (bitCount / 8);
// 创建 Bitmap Info Header
BITMAPINFOHEADER bitmapInfoHeader;
// 40字节
bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfoHeader.biWidth = screenWidth;
bitmapInfoHeader.biHeight = screenHeight;
// 颜色平面数,一般为1
bitmapInfoHeader.biPlanes = 1;
// 24位色彩深度
bitmapInfoHeader.biBitCount = bitCount;
bitmapInfoHeader.biCompression = BI_RGB;
// 图片数据大小
bitmapInfoHeader.biSizeImage = bitmapSize;
bitmapInfoHeader.biXPelsPerMeter = 0;
bitmapInfoHeader.biYPelsPerMeter = 0;
bitmapInfoHeader.biClrUsed = 0;
bitmapInfoHeader.biClrImportant = 0;
// 创建 Bitmap Info
BITMAPINFO bitmapInfo;
bitmapInfo.bmiHeader = bitmapInfoHeader;
// 获取与设备无关的 Bitmap
char* bitmapBuffer = (char*)malloc(bitmapSize);
GetDIBits(hdcScreen, hbmScreen, 0, screenHeight, bitmapBuffer, &bitmapInfo, DIB_RGB_COLORS);
// 创建 Bitmap file header
BITMAPFILEHEADER fileHeader;
// "BM"
fileHeader.bfType = 0x4d42;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
// 文件大小
fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bitmapSize;
// 位图数据的偏移量
fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
// 创建文件
ofstream bmpFile("screen_short.bmp", ios::binary | ios::app);
// 写入 Bitmap file header
bmpFile.write(reinterpret_cast<char*>(&fileHeader), sizeof(BITMAPFILEHEADER));
// 写入 Bitmap info header
bmpFile.write(reinterpret_cast<char*>(&bitmapInfoHeader), sizeof(BITMAPINFOHEADER));
// 写入图片像素数据
bmpFile.write(bitmapBuffer, bitmapSize);
bmpFile.close();
free(bitmapBuffer);
DeleteObject(hbmScreen);
ReleaseDC(NULL, hdcScreen);
ReleaseDC(NULL, hdcMem);
}