下面是关于截屏并保存成jpg文件的代码。由主函数OnCapScreenJpg()、DDBToDIB()、JpegFromDib()、DibToSamps()以及QuadFromWord()函数组成。这些函数的功能包括截取屏幕、将截取的屏幕转成设备无关bmp、再进一步压缩成jpeg格式。这些代码是从网上得到的,得到的代码没有注释,调试、阅读比较费劲。因此我在调试过程中,加上了大量的注释,对不能运行的语句进行了修正,现发到网上,方便读者。这些代码我已在基于对话框的MFC项目(VS2022)中调试通过。
cpp
//捕捉屏幕存成jpg主函数代码实现
void CScrToJpeg2Dlg::OnCapScreenJpg(CString m_strTempBmp){ //参数是保存jpg文件路径及文件名
//1、声明CDC类对象、CBitmap类对象、BITMAP变量等
CDC dc, tdc; //声明两个CDC对象
CBitmap bm; //声明bmp对象(该对象被CDC对象tdc选入)
BITMAP btm; //声明bmp结构体变量
BITMAPINFOHEADER bih; //声明位图信息头结构体变量
BITMAPFILEHEADER bfh; //声明位图文件头结构体变量
DWORD size; //无符号长整型(截图总像素数)
LPSTR lpData; //指向存储截图数据内存区的指针,LPSTR本质就是char*
int Width = GetSystemMetrics(SM_CXSCREEN); //获取屏幕宽度(像素)
int Height = GetSystemMetrics(SM_CYSCREEN); //获取屏幕高度
//2、关联显示器生成CDC类对象dc,以兼容dc方式生成位图类对象bm
dc.CreateDC("DISPLAY", NULL, NULL, NULL); //DISPLAY指显示器(还可指向打印机等)
bm.CreateCompatibleBitmap(&dc, Width, Height); //兼容dc生成位图对象bm
//3、继续以兼容dc的方式生成CDC对象tdc
tdc.CreateCompatibleDC(&dc);
//4、CDC对象tdc调用拷贝屏幕函数并最终将截屏数据存入BITMAP类型的btm
CBitmap* pOld = tdc.SelectObject(&bm); //将CBitmap对象bm选入tdc中
tdc.BitBlt(0, 0, Width, Height, &dc, 0, 0, SRCCOPY); //截屏到tdc中(实际进入bm)
tdc.SelectObject(pOld); //tdc恢复缺省位图(与bm分离)
bm.GetBitmap(&btm); //将截图转存到btm中(bm是类对象,btm是结构体变量)
//5、计算截图像素数据并按此数据申请全局缓存
size = btm.bmWidthBytes * btm.bmHeight; //计算截图总像素数
lpData = (LPSTR)GlobalAlloc(GPTR, size); //申请全局内存指针(类似剪贴板)
//GPTR含义:表示分配固定的内存,返回值是一个指针,同时将所申请内存初始化为0
//size申请的字节数
//6、对位图信息头结构体对象bih填写信息(数据来自于btm)
bih.biBitCount = btm.bmBitsPixel; //颜色位数
bih.biClrImportant = 0; //指定重要颜色数。0代表都重要
bih.biClrUsed = 0; //指定实际颜色数,如果为零,则用到的颜色数为2biBitCount
bih.biCompression = 0; //压缩方式,0不压缩
bih.biHeight = btm.bmHeight; //图象的高度,单位是象素
bih.biPlanes = 1; //只能填写1
bih.biSize = sizeof(BITMAPINFOHEADER); //该结构体大小
bih.biSizeImage = size; //图像大小(像素部分)
bih.biWidth = btm.bmWidth; //图像宽度
bih.biXPelsPerMeter = 0; //水平分辨率(0表示图像没有关联具体的物理尺寸信息)
bih.biYPelsPerMeter = 0; //垂直分辨率
//7、调用GetDIBits将bm中的数据存入lpData,存入按照位图信息头结构体变量bih中规定格式
GetDIBits(dc, bm, 0, bih.biHeight, lpData, (BITMAPINFO*)&bih, DIB_RGB_COLORS);
//GetDIBits功能:检取指定位图的信息,并将其以指定格式复制到一个缓冲区中
//参1 设备环境句柄 参2 位图类对象 参3 起始扫描线位置从0开始 参4 扫描线数
//参5 指向用来检索位图数据的缓冲区指针 参6 指向位图信息头结构的指针
//参7 DIB_RGB_COLORS表示颜色表由红、绿、蓝(RGB)三个直接值构成;还可以是颜色格式 //DIB_PAL_COLORS表示颜色表由指向当前逻辑调色板的16位索引值数组构成
//9、定义位图文件头结构
bfh.bfReserved1 = 0; //预留1 必须为0
bfh.bfReserved2 = 0; //预留2 必须为0
bfh.bfType = ((WORD)('M' << 8) | 'B'); //文件类型:填写BM或者十六进制的0x4d42;
bfh.bfSize = 54 + size; //位图文件大小(单位:字节)
bfh.bfOffBits = 54; //位图数据起始位置(让开文件头部分)
//10、如果设备支持调色板则创建逻辑调色板
CPalette pal; //CPalette类创建和管理调色板(含建立LOGPALETTE结构并创建逻辑调色板)
if (dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE) {
//GetDeviceCaps(RASTERCAPS)功能:获取dc的格栅能力
//RC_PALETTE:标志位,表示支持调色板
//括号中如果为真,则创建和选择调色板到设备上下文中
UINT nSize = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 256);
//PALETTEENTRY结构体指定逻辑调色板中条目的颜色和用法
//LOGPALETTE结构体定义了逻辑调色板
LOGPALETTE* pLP = (LOGPALETTE*) new BYTE[nSize];
//声明一个逻辑调色板指针(new方式)
pLP->palVersion = 0x300; //逻辑调色板版本号
pLP->palNumEntries = GetSystemPaletteEntries(dc, 0, 255, pLP->palPalEntry);
//palNumEntries逻辑调色板中的条目数
pal.CreatePalette(pLP); //完成创建调色板
delete[] pLP; //清理pLP
}
//11、位图转换为DIB(设备无关位图)(Convert the bitmap to a DIB)
HANDLE hDIB = DDBToDIB(bm, BI_RGB, &pal);
//DDBToDIB这个函数是自定义的(源码见后)
//DDBToDIB函数将设备依赖位图转换为设备无关位图
//参1 位图类对象 参2 压缩模式BI_RGB的值是0表示不压缩 参3 调色板指针
//12、设备无关图DIB再转成jpeg
CString strError; //声明错误码字符串
JpegFromDib(hDIB, 100, m_strTempBmp, &strError);
//这个函数也是自定义的(源码见后)
//参1 设备无关图句柄 参2 保存的文件名 参3 报错字符串
GlobalFree(hDIB); //该函数是释放指定的全局内存块
GlobalFree(lpData); //释放共享内存(new方式声明的内存要自己清理)
MessageBox("Screenshot saved as file successfully!");
}
//主程序到此结束
//设备相关位图转成设备无关位图子函数代码实现
HANDLE CScrToJpeg2Dlg::DDBToDIB(
CBitmap& bitmap, //参1 CBitmap类对象 实际传入的就是bm
DWORD dwCompression, //参2 无符号整形 压缩模式 实际输入的是0(无压缩)
CPalette* pPal) //参3 调色板指针 实际传入的是对pal的取址
{
//1、定义变量
BITMAP btm; //声明一个BITMAP结构体变量
BITMAPINFOHEADER bi; //声明位图信息头结构体变量
LPBITMAPINFOHEADER lpbi; //指向位图信息头结构体的指针
DWORD dwLen; //无符号长整形(信息头和颜色表的字节长度)
HANDLE hDIB = NULL; //设备无关图句柄
HANDLE handle; //句柄类型
HDC hDC; //设备上下文句柄(=CDC*中的m_hDc)
HPALETTE hPal; //调色板指针
//2、断言判断(如果获取bitmap的安全句柄失败,则结束程序)
ASSERT(bitmap.GetSafeHandle());//GetSafeHandle()用于获取GDI对象的句柄。
//3、如参数中未传入合适的关于压缩类型的参数
if (dwCompression == BI_BITFIELDS) return NULL; //结束程序
//BI_BITFIELDS这个标识用在16位和32位的BMP上,有三个DWORD作为图片数据的MASK
//4、如参数中没有传入调色板则使用缺省的调色板
hPal = (HPALETTE)pPal->GetSafeHandle(); //获取传入的调色板句柄
if (hPal == NULL) //获取调色板句柄不成功
hPal = (HPALETTE)GetStockObject(DEFAULT_PALETTE); //载入缺省调色板
//5、获取作为参数传入的位图信息并写入btm中
bitmap.GetObject(sizeof(btm), (LPSTR)&btm);
//参1 写入位图信息的字节数
//参2 指向写入缓冲区的指针
//6、初始化位图信息头bi(在本函数中定义的)
bi.biSize = sizeof(BITMAPINFOHEADER); //指定这个结构的长度,一般为40
bi.biWidth = btm.bmWidth; //指定图象的宽度,单位是象素
bi.biHeight = btm.bmHeight; //指定图像的高度
bi.biPlanes = 1; //只能是1
bi.biBitCount = btm.bmPlanes * btm.bmBitsPixel;
//biBitCount表示颜色时要用到的位数
//biBitCount常用的值为1(黑白图)、4(16色图)、8(256色)、24(真彩色图)、32位色
//bmPlanes指定调色板数目,是一种"面"的数目,通常为1,
//bmBitsPixel一个点在每个调色板上接近的颜色位数
bi.biCompression = dwCompression;
//指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS
bi.biSizeImage = 0; //指定实际的位图数据占用的字节数(先写成0,后边再填入)
bi.biXPelsPerMeter = 0; //指定目标设备的水平分辨率,单位是每米的象素个数
bi.biYPelsPerMeter = 0; //指定目标设备的垂直分辨率
bi.biClrUsed = 0; //指定图象实际用到的颜色数,该值为零表示用到的颜色数为2biBitCount
bi.biClrImportant = 0; //指定本图象中重要的颜色数,如果该值为零,则所有颜色都是重要的
//7、计算信息头和颜色表字节数
int nColors = (1 << bi.biBitCount); //将1做左移24或32位会大于256
if (nColors > 256) nColors = 0; //判定是否真色彩
dwLen = bi.biSize + nColors * sizeof(RGBQUAD);
//bi.biSize 位图信息头结构字节数
//RGBQUAD是一个结构体,由四个字节组成,分别表示蓝、绿、红、预留
//8、需要一个设备上下文,以从其得到DIB
hDC = ::GetDC(NULL); //获得当前设备上下文句柄
hPal = SelectPalette(hDC, hPal, FALSE);
//参1 当前设备上下文句柄
//参2 要选择的逻辑调色板的句柄
//参3 指定逻辑调色板是否强制为背景调色板。
/*说明:FALSE表示当应用程序位于前台时,RealizePalette会将逻辑调色板复制到设备调色板中。如果hdc是内存设备上下文,则忽略此参数。*/
//返回值 如果函数成功,则返回值是设备上下文上一个逻辑调色板的句柄
//9、从当前逻辑调色板中映射调色板(入口点)到系统调色板中
RealizePalette(hDC); //已在其中选择逻辑调色板的设备上下文的句柄
//10、位图信息头及颜色表申请内存(暂时,后边再扩大)
hDIB = GlobalAlloc(GMEM_FIXED, dwLen); //申请全局内存(位图信息头及颜色表)
//参1 GMEM_FIXED(返回的是句柄也是地址),GMEM_MOVEABLE(返回的是句柄)
//用后要用GlobalFree函数来释放内存块
if (!hDIB) { //申请失败的处理
SelectPalette(hDC, hPal, FALSE);
::ReleaseDC(NULL, hDC);
return NULL;
}
lpbi = (LPBITMAPINFOHEADER)hDIB; //将hDIB强转为位图信息头结构指针
*lpbi = bi; //将前面定义的bi赋值给*lpbi(此时bi中只有位图信息头内容)
//11、调用GetDIBits函数,并将lpBits参数设置为NULL,这样程序将自动计算biSizeImage字段
GetDIBits( //该函数获取指定兼容位图的位信息,将其按DIB指定格式复制到一个缓冲区中
hDC, //参1 当前设备上下文句柄
(HBITMAP)bitmap.GetSafeHandle(), //参2 传入位图句柄
0L, //参3 扫描开始位置
(DWORD)bi.biHeight,//参4 扫描线数
(LPBYTE)NULL, //参5 指向用来检索位图信息缓冲区的指针
//为NULL时函数把位图维数与格式传递给lpbi指向的BITMAPINFO结构
(LPBITMAPINFO)lpbi, //参6 BITMAPINFO结构的指针,此结构确定传入位图的数据格式
(DWORD)DIB_RGB_COLORS); //参7 指定BITMAPINFO结构的bmiColors成员的格式。
bi = *lpbi; //把获得内容的lpbi中的内容重新赋值给bi
//12、如果驱动程序没有填写biSizeImage字段,则计算图像的每一行边界都按DWORD(32位)
if (bi.biSizeImage == 0) { //初始化定义的是0,说明还没有赋值
bi.biSizeImage = (((bi.biWidth * bi.biBitCount) + 31) & ~31) * bi.biHeight;
//biWidth位图宽度
//biBitCount表示颜色用的位数 二者相乘表示一行像素用的位数
//"+31)&~31"与每行字节数是4个字节(32位)的倍数相关
//biHeight图像行数
//13、如果使用压缩方案,结果实际上可能会更大;增加大小以考虑这一点。
if (dwCompression != BI_RGB) bi.biSizeImage = (bi.biSizeImage * 3) / 2;
}
//14、重新分配缓冲区,使其能够容纳所有位
dwLen += bi.biSizeImage; //位图信息头、颜色表、位图内容都加到dwLen中
if (handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE)) { //GMEM_MOVEABLE返回句柄
hDIB = handle;
} //此时hDIB指向整个位图(包括位图头信息、颜色表及内容)
else {
GlobalFree(hDIB); //不成功时释放hDIB
SelectPalette(hDC, hPal, FALSE); //恢复缺省调色板
::ReleaseDC(NULL, hDC);
return NULL;
}
//15、获得位图位句柄并最终获得设备无关位图(Get the bitmap bits,FINALLY get the DIB)
lpbi = (LPBITMAPINFOHEADER)hDIB;
BOOL bGotBits = GetDIBits(
hDC, //参1 当前设备上下文句柄
(HBITMAP)bitmap.GetSafeHandle(),//参2 传入位图句柄
0L, //参3 扫描开始位置
(DWORD)bi.biHeight, //参4 扫描线数
(LPBYTE)lpbi //参5 指向用来写入位图数据缓冲区的指针
+ (bi.biSize + nColors * sizeof(RGBQUAD)), //仍是参5 指针偏移量(让开信息头部分)
(LPBITMAPINFO)lpbi, //参6 指向一个BITMAPINFO结构的指针
(DWORD)DIB_RGB_COLORS); //参7 指定BITMAPINFO结构的bmiColors成员的格式
if (!bGotBits) { //获得设备无关图不成功的处理
GlobalFree(hDIB); //释放hDIB
SelectPalette(hDC, hPal, FALSE); //恢复缺省调色板
::ReleaseDC(NULL, hDC); //释放设备上下文句柄
return NULL; //返回空指针
}
//16、获得设备无关位图成功的处理
SelectPalette(hDC, hPal, FALSE); //恢复缺省调色板
::ReleaseDC(NULL, hDC);//释放设备上下文句柄
return hDIB; //返回设备无关位图句柄
}
//DDBToDIB子函数至此结束
//设备无关位图转jpeg函数代码实现
BOOL CScrToJpeg2Dlg::JpegFromDib( //该函数在主程序中被调用的
HANDLE hDib, //参1 由上一个无关位图函数获得的句柄hDIB
int nQuality, //参2 JPEG质量(0-100)
CString csJpeg, //参3 路径及文件名
CString* pcsMsg) //参4 返回的错误信息
{
//1、基本信息检查
if (nQuality < 0 || nQuality >100 || hDib == NULL || pcsMsg == NULL || csJpeg == "")
{
if (pcsMsg != NULL)
*pcsMsg = "Invalid input data";
return FALSE; //结束程序
}
*pcsMsg = "";
//2、使用libjpeg库定义压缩对象及错误处理器
struct jpeg_compress_struct cinfo; //定义jpeg压缩对象
struct jpeg_error_mgr jerr; //定义jpeg错误处理器
//3、定义文件指针(jpeg文件指针)及相关变量
FILE* pOutFile; //目标文件Target file
int nSampsPerRow; //图像每行的采样点数(样本数)
JSAMPARRAY jsmpArray; //存储jpeg文件中像素(RGB值)的缓冲区
/*一个JSAMPLE类型数组代表一条扫描线,JSAMPARRAY表示2维的JSAMPLE数组。nSampsPerRow与像素之间的关系:像素宽度 = SampsPerRow / Bands波段数;波段也称为通道(Channel),波段数决定了图像性质,如单波段(如灰度图)、三波段(如RGB彩色图)等。样本数在在数值上要大于像素数。*/
//4、将错误处理器关联压缩对象,并生成压缩对象(初始化压缩对象)
cinfo.err = jpeg_std_error(&jerr); //指定错误处理器
jpeg_create_compress(&cinfo);//初始化jpeg压缩对象
//5、打开用于存放jpeg的文件获得文件指针,并对可能打开文件失败进行处理
if ((fopen_s(&pOutFile, csJpeg, "wb")) != 0) //打开文件必须二进制模式
{
*pcsMsg = "Cannot open"; //给错误信息字符串赋值
*pcsMsg += csJpeg; //把声明的路径及文件名加载Cannot open之后
jpeg_destroy_compress(&cinfo); //释放压缩工作过程中所申请的资源
return FALSE; //结束函数
}
//6、文件指针与压缩对象关联
jpeg_stdio_dest(&cinfo, pOutFile); //指定压缩图像存放目标文件
//7、给cinfo结构体对象添加参数
LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)hDib; //hDib是作为参数传入的位图句柄
cinfo.image_width = lpbi->biWidth; //图像的宽度(像素)
cinfo.image_height = lpbi->biHeight; //图像的高度(像素)
info.input_components = 3; //每个像素的颜色分量
cinfo.in_color_space = JCS_RGB; //JCS_RGB表示彩色图像、JCS_GRAYSCALE表示灰度图
int nRowSize = lpbi->biWidth;
//8、导入设置的压缩参数
jpeg_set_defaults(&cinfo);
/* 需要注意的是,jpeg_set_defaults函数一定要等设置好图像宽、高、色彩通道数计色彩空间四个参数后才能调用,因为这个函数要用到这四个值,调用jpeg_set_defaults函数后,jpeglib库采用默认的设置对图像进行压缩,如果需要改变设置,如压缩质量,调用这个函数后,可以调用其它设置函数,如jpeg_set_quality函数。其实图像压缩时有好多参数可以设置,但大部分我们都用不着设置,只需调用jpeg_set_defaults函数值为默认值即可。*/
//9、调整压缩参数
jpeg_set_quality(
&cinfo, //压缩对象
nQuality, //压缩质量值(0-100)
TRUE);
//10、压缩开始
jpeg_start_compress(&cinfo, TRUE);
//11、输出缓冲区中每行的样本数(JSAMPLEs per row in output buffer)
nSampsPerRow = cinfo.image_width * cinfo.input_components;
//image_width像素宽度
//input_components表示JPEG图像中每个像素由多少个独立的颜色构成;
//如灰度图只有一个颜色分量,RGB图有三个颜色分量(与波段数概念相同)。
//12、为图像处理分配内存空间,分配一个JSAMPARRAY类型的宽度为nSampsPerRow、高度为image_height//的二维数组空间,可以像二维数组一样访问:jsmpArray[x][y]。
jsmpArray = (*cinfo.mem->alloc_sarray)( //内置的内存分配函数指针(用于分配二维样本数)
(j_common_ptr)&cinfo, //cinfo是包含压缩参数和状态信息JPEG压缩结构体
//这里cinfo被强转为libjpeg的通用结构体指针
JPOOL_IMAGE, //指定内存区域类型(图像处理期间使用的内存类型)
nSampsPerRow, //每行的样本数(通常是width*components,如RGB图像是width*3)
cinfo.image_height);//图像的高度(行数,单位像素)
//13、通过libjpeg的内存管理器分配一个二维数组,用于存储一行(或几行)JPEG图像数据。
JSAMPARRAY pBuffer = (*cinfo.mem->alloc_sarray)(
//SAMPARRAY 是libjpeg类型,表示"样本数组的数组",通常用于存储图像的行数据
//pBuffer是一个指向JSAMPARRAY的指针,用于存储分配的内存
//(*cinfo.mem->alloc_sarray)是一个函数指针,专门用于分配采样数组(RGB分量)内存
(j_common_ptr)&cinfo,//将cinfo转换为通用的jpeg通用结构指针
1, //表示分配1个JSAMPARRAY(即一个二维数组)
nRowSize,//表示每行的大小(以像素为单位)
1); //表示每个样本的大小(通常是1字节)
//14、无关位图转成样本数据,成功时将扫描线数据写入文件
if (DibToSamps( //(DibToSamps是自定义函数,详细源码见后)
hDib, //在上一级函数中作为参数传入的位图
nSampsPerRow,//每行样本数
cinfo, //压缩对象
jsmpArray,//存储二维图像数据的内存区指针
pcsMsg)) //错误信息
{
//将扫描线数组写入JPEG文件
JDIMENSION lines_written = 0;
while (cinfo.next_scanline < cinfo.image_height) {
lines_written += jpeg_write_scanlines(&cinfo, &jsmpArray[cinfo.next_scanline],
cinfo.image_height - cinfo.next_scanline);
}
}
//15、结束压缩时的处理
jpeg_finish_compress(&cinfo); //Always finish
fclose(pOutFile); //关闭文件指针
jpeg_destroy_compress(&cinfo); //Free resources
if (*pcsMsg != "") {
return FALSE; //返回失败
}
else {
return TRUE; //返回成功
}
}
//----设备无关位图转jpeg函数JpegFromDib()结束----
//无关位图像素转样本函数代码(在JpegFromDib中被调用)
BOOL CScrToJpeg2Dlg::DibToSamps(
HANDLE hDib, //无关设备位图的句柄
int nSampsPerRow, //输出缓冲区中每行的样本数
struct jpeg_compress_struct cinfo, //关于压缩结构的结构体
JSAMPARRAY jsmpPixels, //JPEG文件的像素RGB缓冲区
CString* pcsMsg) //错误信息
{
//1、一般性检查(如果无关位图句柄、每行的样本数、错误码指针有问题时直接返回不成功
if (hDib == NULL || nSampsPerRow <= 0 || pcsMsg == NULL) {
if (pcsMsg != NULL) {
*pcsMsg = "Invalid input data";
return FALSE;
}
}
//2、声明变量并赋初值
int r = 0, p = 0, q = 0, b = 0, n = 0, nUnused = 0, nBytesWide = 0, nUsed = 0;
int nLastBits = 0, nLastNibs = 0, nCTEntries = 0, nRow = 0, nByte = 0, nPixel = 0;
BYTE bytCTEnt = 0;
LPBITMAPINFOHEADER pbBmHdr = (LPBITMAPINFOHEADER)hDib; //强转
//3、解析无关位图文件头中的biBitCount,并根据像素的位深度确定颜色表的条目数
switch (pbBmHdr->biBitCount) { //biBitCount表示单个像素位数,它决定了图像的颜色模式
case 1: //每个像素用1个位存储,表示两种颜色
nCTEntries = 2; //Monochrome,颜色表需要2个条目(黑和白)
break;
case 4: //4位图,每个像素用4位存储,可以表示 2⁴ = 16 种颜色
nCTEntries = 16; //颜色表需16条目,存储具体的颜色值(调色板索引)
break;
case 8: //8位图(每个像素用8位表示,256色灰度/索引色图)
nCTEntries = 256; //颜色表需要256个条目,适用于灰度图或索引彩色图
break;
case 16: //通常用 RGB565格式(5位红、6位绿、5位蓝)直接编码颜色值
case 24: //标准 RGB888(每通道8位),直接存储颜色。
case 32: //RGB+Alpha通道(如ARGB8888)。
nCTEntries = 0; //真彩色图像不需要颜色表,像素值直接表示颜色(非调色板索引)。
break;
default:
*pcsMsg = "Invalid bitmap bit count";
return FALSE; //不支持的格式
}
/*补充:位图中颜色表(Color Table)仅对索引色图像(1/4/8位)有效,存储调色板颜色值。真彩色图像(16/24/32位)直接存储RGB值,无需颜色表。nCTEntries 的含义:表示颜色表的条目数,由像素位深度决定(如8位图需要256条目)。BMP文件结构用于确定如何读取像素数据(是否依赖颜色表)。颜色表格式:每个条目是一个 RGBQUAD 结构(4字节:B/G/R/保留),但实际颜色顺序可能因文件而异。*/
//4、指向颜色表和像素
DWORD dwCTab = (DWORD)pbBmHdr + pbBmHdr->biSize;
//pbBmHdr(即hDib)传入的无关位图的句柄
//biSize位图信息头结构占字节数(偏移量)
LPRGBQUAD pCTab = (LPRGBQUAD)(dwCTab); //强转成RGB颜色表类型指针
LPSTR lpBits = (LPSTR)pbBmHdr + (WORD)pbBmHdr->biSize + (WORD)(nCTEntries * sizeof(RGBQUAD));
//进一步强转成字符数组指针类型,再进一步偏移颜色表所占字节数,使指针指向颜色值区域
//5、确定图像位的不同格式
LPBYTE lpPixels = (LPBYTE)lpBits; //LPBYTE相当于无符号char*(单字节)
RGBQUAD* pRgbQs = (RGBQUAD*)lpBits; //RGBQUAD结构用于定义调色板数组元素的类型
WORD* wPixels = (WORD*)lpBits; //无符号双字节
//6、根据位图的格式设置jsamps,请注意,行按从下到上的顺序处理,因为这是位图创建的方式
switch (pbBmHdr->biBitCount) // 根据位图的位深度进行不同处理
{
case 1: //黑白图处理逻辑(每个像素占1位)
nUsed = (pbBmHdr->biWidth + 7) / 8; //每行有效像素所占的字节数(向上取整)
nUnused = (((nUsed + 3) / 4) * 4) - nUsed;//每行填充字节数(使每行字节数为4的倍数)
nBytesWide = nUsed + nUnused; //每行字节总数(含填充)
nLastBits = 8 - ((nUsed * 1) - pbBmHdr->biWidth); //最后字节中实际使用的位数
for (r = 0; r < pbBmHdr->biHeight; r++) { //遍历每一行(倒序,实质是从下到上)
for (p = 0, q = 0; p < nUsed; p++) { //遍历每行中的每个字节
nRow = (pbBmHdr->biHeight - r - 1) * nBytesWide; //计算当前行在数据中的偏移
nByte = nRow + p; //当前字节的绝对位置
int nBUsed = (p < (nUsed >> 1)) ? 8 : nLastBits; //当前字节实际使用的位数
for (b = 0; b < nBUsed; b++) { //处理当前字节的每一位
bytCTEnt = lpPixels[nByte] << b; //左移b位获取当前位
bytCTEnt = bytCTEnt >> 7; //右移7位始当前位成为最低位
//使用颜色表(pCTab)获取RGB值并存储到jsmpPixels数组
jsmpPixels[r][q + 0] = pCTab[bytCTEnt].rgbRed;
jsmpPixels[r][q + 1] = pCTab[bytCTEnt].rgbGreen;
jsmpPixels[r][q + 2] = pCTab[bytCTEnt].rgbBlue;
q += 3;// 移动到下一个像素的RGB位置
}
}
}
break;
case 4: //4位16色图处理逻辑(每个像素占4位)
nUsed = (pbBmHdr->biWidth + 1) / 2;// 每行有效像素所占的字节数(2像素/字节)
nUnused = (((nUsed + 3) / 4) * 4) - nUsed;// 每行填充字节数
nBytesWide = nUsed + nUnused;// 每行总字节数
nLastNibs = 2 - ((nUsed * 2) - pbBmHdr->biWidth);// 最后字节中实际使用的半字节数
for (r = 0; r < pbBmHdr->biHeight; r++) {
for (p = 0, q = 0; p < nUsed; p++) {
nRow = (pbBmHdr->biHeight - r - 1) * nBytesWide;
nByte = nRow + p;
int nNibbles = (p < (nUsed - 1)) ? 2 : nLastNibs; //当前字节实际使用的半字节数
jsmpPixels[r][q + 0] = pCTab[bytCTEnt].rgbRed;
jsmpPixels[r][q + 1] = pCTab[bytCTEnt].rgbGreen;
jsmpPixels[r][q + 2] = pCTab[bytCTEnt].rgbBlue;
q += 3;
}
}
break;
case 8: //8位256色图处理逻辑(每个像素占1字节),每个字节都是一个像素颜色的指针
nUnused = (((pbBmHdr->biWidth + 3) / 4) * 4) - pbBmHdr->biWidth;// 每行填充字节数
for (r = 0; r < pbBmHdr->biHeight; r++) {
for (p = 0, q = 0; p < pbBmHdr->biWidth; p++, q += 3) {
nRow = (pbBmHdr->biHeight - r - 1) * (pbBmHdr->biWidth + nUnused);
nPixel = nRow + p;
// 直接使用像素值作为颜色表索引
jsmpPixels[r][q + 0] = pCTab[lpPixels[nPixel]].rgbRed;
jsmpPixels[r][q + 1] = pCTab[lpPixels[nPixel]].rgbGreen;
jsmpPixels[r][q + 2] = pCTab[lpPixels[nPixel]].rgbBlue;
}
}
break;
case 16: //16位高彩色(每个像素占2字节)
for (r = 0; r < pbBmHdr->biHeight; r++) {
for (p = 0, q = 0; p < pbBmHdr->biWidth; p++, q += 3) {
nRow = (pbBmHdr->biHeight - r - 1) * pbBmHdr->biWidth;
nPixel = nRow + p;
//将16位颜色值转换为RGBQUAD结构
RGBQUAD quad = QuadFromWord(wPixels[nPixel]);//该函数为自定义,见后
//存储RGB值
jsmpPixels[r][q + 0] = quad.rgbRed;
jsmpPixels[r][q + 1] = quad.rgbGreen;
jsmpPixels[r][q + 2] = quad.rgbBlue;
}
}
break;
case 24: // 24位真彩色(每个像素占3字节)
nBytesWide = (pbBmHdr->biWidth * 3);// 每行字节数(无填充)
nUnused = (((nBytesWide + 3) / 4) * 4) - nBytesWide;// 每行填充字节数
nBytesWide += nUnused;// 每行总字节数(含填充)
for (r = 0; r < pbBmHdr->biHeight; r++) {
for (p = 0, q = 0; p < (nBytesWide - nUnused); p += 3, q += 3) {
nRow = (pbBmHdr->biHeight - r - 1) * nBytesWide;
nPixel = nRow + p;
// 直接读取BGR顺序的像素值(BMP存储顺序为BGR)
jsmpPixels[r][q + 0] = lpPixels[nPixel + 2]; //Red
jsmpPixels[r][q + 1] = lpPixels[nPixel + 1]; //Green
jsmpPixels[r][q + 2] = lpPixels[nPixel + 0]; //Blue
}
}
break;
case 32: //32位真色彩(每个像素占4字节)
for (r = 0; r < pbBmHdr->biHeight; r++) {
for (p = 0, q = 0; p < pbBmHdr->biWidth; p++, q += 3) {
nRow = (pbBmHdr->biHeight - r - 1) * pbBmHdr->biWidth;
nPixel = nRow + p;
// 直接读取RGBQUAD结构中的RGB值(忽略alpha通道)
jsmpPixels[r][q + 0] = pRgbQs[nPixel].rgbRed;
jsmpPixels[r][q + 1] = pRgbQs[nPixel].rgbGreen;
jsmpPixels[r][q + 2] = pRgbQs[nPixel].rgbBlue;
}
}
break;
} //switch结束
return TRUE;
} //无关位图像素转样本函数DibToSamps()代码结束
//16位像素转换为RGBQUAD值函数代码(在DibToSamps()中被调用)
RGBQUAD CScrToJpeg2Dlg::QuadFromWord(WORD b16) {
BYTE bytVals[] = {
0, 16, 24, 32, 40, 48, 56, 64,
72, 80, 88, 96, 104,112,120,128,
136,144,152,160,168,176,184,192,
200,208,216,224,232,240,248,255
};
WORD wR = b16;
WORD wG = b16;
WORD wB = b16;
wR <<= 1; wR >>= 11; //wR = wR << 1;
wG <<= 6; wG >>= 11;
wB <<= 11; wB >>= 11;
RGBQUAD rgb;
rgb.rgbReserved = 0;
rgb.rgbBlue = bytVals[wB];
rgb.rgbGreen = bytVals[wG];
rgb.rgbRed = bytVals[wR];
return rgb;
}