MFC 捕捉桌面存成jpg案例代码

下面是关于截屏并保存成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;
}
相关推荐
superior tigre6 分钟前
C++学习:六个月从基础到就业——多线程编程:std::thread基础
c++·学习
姬公子52115 分钟前
leetcode hot100刷题日记——7.最大子数组和
c++·算法·leetcode
闻缺陷则喜何志丹19 分钟前
【回溯 剪支 状态压缩】# P10419 [蓝桥杯 2023 国 A] 01 游戏|普及+
c++·算法·蓝桥杯·剪枝·回溯·洛谷·状态压缩
Wneosy33 分钟前
第十六届C++B组easyQuestions
c++·算法·蓝桥杯
赵和范37 分钟前
C++:与7无关的数
开发语言·c++·算法
CodeWithMe1 小时前
【C/C++】C++返回值优化:RVO与NRVO全解析
c++
WiKiLeaks_successor2 小时前
error: forming reference to void
c++
shylyly_2 小时前
智能指针RAII
开发语言·c++·内存泄漏·智能指针·weak_ptr·rall·share_ptr
潇-xiao3 小时前
Qt enabled + geometry 属性(2)
c++·笔记·qt