MFC 捕捉桌面存成jpg案例代码
下面是关于截屏并保存成jpg文件的代码。由主函数OnCapScreenJpg()、DDBToDIB()、JpegFromDib()、DibToSamps()以及QuadFromWord()函数组成。这些函数的功能包括截取屏幕、将截取的屏幕转成设备无关bmp、再进一步压缩成jpeg格式。这些代码是从网上得到的,得到的代码没有注释,调试、阅读比较费劲。因此我在调试过程中,加上了大量的注释,对不能运行的语句进行了修正,现发到网上,方便读者。这些代码我已在基于对话框的MFC项目(VS2022)中调试通过。
//捕捉屏幕存成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方式生成位图类对象bmdc.CreateDC("DISPLAY", NULL, NULL, NULL); //DISPLAY指显示器(还可指向打印机等)bm.CreateCompatibleBitmap(&dc, Width, Height); //兼容dc生成位图对象bm
//3、继续以兼容dc的方式生成CDC对象tdctdc.CreateCompatibleDC(&dc);
//4、CDC对象tdc调用拷贝屏幕函数并最终将截屏数据存入BITMAP类型的btmCBitmap* 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; //指定实际颜色数,如果为零,则用到的颜色数为2biBitCountbih.biCompression = 0; //压缩方式,0不压缩bih.biHeight = btm.bmHeight; //图象的高度,单位是象素bih.biPlanes = 1; //只能填写1bih.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 必须为0bfh.bfReserved2 = 0; //预留2 必须为0bfh.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再转成jpegCString 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类对象 实际传入的就是bmDWORD 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); //指定这个结构的长度,一般为40bi.biWidth = btm.bmWidth; //指定图象的宽度,单位是象素bi.biHeight = btm.bmHeight; //指定图像的高度bi.biPlanes = 1; //只能是1bi.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_BITFIELDSbi.biSizeImage = 0; //指定实际的位图数据占用的字节数(先写成0,后边再填入)bi.biXPelsPerMeter = 0; //指定目标设备的水平分辨率,单位是每米的象素个数bi.biYPelsPerMeter = 0; //指定目标设备的垂直分辨率bi.biClrUsed = 0; //指定图象实际用到的颜色数,该值为零表示用到的颜色数为2biBitCountbi.biClrImportant = 0; //指定本图象中重要的颜色数,如果该值为零,则所有颜色都是重要的//7、计算信息头和颜色表字节数int nColors = (1 << bi.biBitCount); //将1做左移24或32位会大于256if (nColors > 256) nColors = 0; //判定是否真色彩dwLen = bi.biSize + nColors * sizeof(RGBQUAD);//bi.biSize 位图信息头结构字节数//RGBQUAD是一个结构体,由四个字节组成,分别表示蓝、绿、红、预留
//8、需要一个设备上下文,以从其得到DIBhDC = ::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); //不成功时释放hDIBSelectPalette(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); //释放hDIBSelectPalette(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 由上一个无关位图函数获得的句柄hDIBint 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 finishfclose(pOutFile); //关闭文件指针jpeg_destroy_compress(&cinfo); //Free resourcesif (*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]; //RedjsmpPixels[r][q + 1] = lpPixels[nPixel + 1]; //GreenjsmpPixels[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;
}