BMP 和 JPEG

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情
BMP 和 JPEG

作者: 阙荣文
时间: 2016.5.29

1. 什么是 BMP
BMP 格式是最简单,最直观的位图数据格式.它的思想非常朴素:
用若干个位来保存一个像素的信息,由若干个像素组成一个像素流来表达一张图片.
通常我们会用1位(可以保存2种颜色,0 表示一种颜色,1表示另一种,不一定是黑白,也可以是蓝绿,总之是2种),4位 - 16种颜色,8位 - 256种颜色,16位 - 65535种颜色,24位 - 2^24种颜色,32位 - 前24位和24位位图一样可以保存2^24种颜色,最后8位用来保存这个像素的灰度(也就是这个像素的明暗程度),可以表示256种灰度.注意,目前的显示器在硬件上通常只能支持24位色,而且 libjpeg 也只能处理最多24位色的像素流.

对于24位以下的BMP,可以引入一个"调色板"来增强图片的表达能力,以8位,256色位图作为例子:
在8位位图中,每个像素的信息用一个字节存储,那么 DIB 数据就是一个 BYTE dibBuffer[] 数组, dibBuffer[0] 表示第一个像素的颜色,以此类推. 我们知道颜色是由RGB 3个分量组合而成的,编程中用 RGBQUAD 结构表示,那么8位除以3,每个分量只能用2位来存储(不使用编码压缩RLE的前提下),实际能够表现的颜色非常有限.现在我们引入一个长度为256的 RGBQUAD 类型的数组: RGBQUAD colorTable[256],我们在 DBI 数组 dibBuffer[] 中不再直接存放的每个像素的颜色值,而是存放该颜色值在 colorTable 中的索引,这样就可以充分利用 dibBuffer 中的每一位的存储空间.这个 "colorTable" 就是 Windows 中调色板的概念. 知道了这些,就可以理解为什么24位及以上色深的位图不需要调色板了.

2.1. BMP 文件格式和DIB
DIB就是"设备无关位图"的意思,我们可以理解为一个像素数组,这是编程时我们需要处理的数据,非常简单,就是一个定长数组,如果是一个24位的DIB数据,那么在编程时就可以认为是一个 BYTE dibBuffer[], dibBuffer[0],dibBuffer[2],dibBuffer[2]表示第一个像素的 RGB 值(实际上是 BGR), dibBuffer[3],[4],[5] 表示第二个像素的 RGB 值,以此类推.当然我们不能直接把这个 dibBuffer 数组写到磁盘作为 BMP 文件,缺少图片的调色板,宽,高等信息,所以我们需要一个特定的格式来存储 DIB 像素流.

BMP文件格式就是把DIB像素流存储到磁盘是需要遵循的相关约定. 关于BMP文件格式的详细说明在网上可以找到很多,比如这篇说的就很清楚: http://blog.csdn.net/lanbing510/article/details/8176231
从编程的角度来看,一个BMP文件是可以表述为以下结构:
typedef struct tagBITMAP_FILE
{
      BITMAPFILEHEADER bitmapheader;
      BITMAPINFOHEADER bitmapinfoheader;
      PALETTEENTRY palette[n]; // 调色板数据(可选,由BITMAPFILEHEADER::bOffBits计算 n 的值)
      UCHAR *dibBuffer;   // DIB 数据数组
} BITMAP_FILE;
BITMAPFILEHEADER, BITMAPINFOHEADER, PALETTEENTRY 结构的详细信息可以在 MSDN 中查到.

用自然语言简单描述一下:
文件头 - 固定长度,表示这个文件是一个 BMP 文件,版本号,文件长度等,最重要的时文件头结构中的 bfOffBits 字段,它表示 DIB 像素流数据在文件中的偏移位置,编程时,我们打开一个 BMP 文件,先读取固定长度的文件头,在根据这个字段就可以构造前面说的调色板数组 RGBQUAD colorTable[] 和 DIB 数组 BYTE dibBuffer[] 了.
BMP信息头 - 固定长度,存储位图的宽高等信息,需要注意的字段 biHeight, 如果它是正数则表示像素流的信息是倒序存储的,即位图的底下一行的像素存储在前;如果它是负数则表示像素流的信息是正序存储的,位图的第一行像素存储在 dibBuffer 开头.
调色板数组 - 可选,用文件头中的偏移地址减去文件头和信息头的长度就是调色板数组的长度.
DIB像素流 - 就是 dibBuffer[] 数组.
特别要注意的一点是,在实际编程中, 24位 DIB 数据的存放顺序是 BGR 即 dibBuffer[0] 存放的是最后一行的第一个像素的 B 分量, dibBuffer[1] 是 G 分量, dibBuffer[2] 是 R 分量, 而 JPG 压缩时要求输入顺序是 RGB, 所以把 dibBuffer 提供给 JPEG 压缩器前需要处理一下, dibBuffer[i] 和 dibBuffer[i + 2] 交换,否则得到的 JPG 图像颜色是不对的.

2.2. DDB
DDB 是"设备相关位图"的意思,把 DIB 数据写入设备之后,设备在内部会把 DIB 数据处理为内部数据格式, Windows GDI 中用 HBITMAP 表述一个 DDB,我们只需要调用相关的 API 就可以了,具体细节不用理会.

3. 什么是 JPEG
JPEG是 DIB 数据的一种编码规则,前面我们提到 BMP 文件,直接把 DIB 数组 dibBuffer[] 直接写到文件中,所以BMP文件是原始的,无损失的保存了内存中的图像数据.如果用某种算法把 dibBuffer 数组编码压缩,那么我们也许就没必要把整个 dibBuffer (通常是一个很大的数组) 直接写入文件中,从而大大节省磁盘空间. JPEG 就是这样一种算法.

4. libjpeg
C语言实现的 JPEG 库,官网地址: http://www.ijg.org/

4.1 编译
我写这篇文章的时候 JPEG 库的版本是 jpeg-9b,从官网上下载源码 jpegsr9b.zip 解压后,启动Visual Studio,进入命令行模式,切换到 jpeg 源码目录,输入: nmake /f makefile.vc 就会看到 jpeg.sln - VS工程文件出现了,用Visual Studio 打开编译即可.
如果执行 nmake 命令时提示找不到 win32.mak,就编辑一下 makefile.vc 把第12行 !include <win32.mak> 注释掉就可以,其实 nmake /f makefile.vc 并不是真正编译,这是重命名了几个文件而已.
编译完成后得到: jpeg.lib 这就是你需要的库文件了,再把 jconfig.h, jerror.h, jinclude.h, jmorecfg.h, jpeglib.h 复制到你的工程中就算配置完成了.

4.2 example.c
libjpeg 的使用实例在源码包中的 example.c 文件里, 我们只要把 write_JPEG_file / read_JPEG_file 两个函数看明白就可以应付大多数应用了.
 
4.3 内存 JPG 压缩解压缩及其它
example.c 中的实例是使用文件io的, 用 jpeg_mem_src / jpeg_mem_dest 函数代替 jpeg_stdio_src / jpeg_stdio_dest 就可以实现内存io了.

JPEG库是不知道调色板之类的东西的,它只是很单纯的把输入的 DIB 像素流压缩输出为一个更短的输出数据流.所以对于包含了调色板的 BMP 文件,由于 DIB 数组内保存的是调色板的索引号而并不是颜色值,在提交给 JPEG 库之前需要根据调色板查表构造一个真正的包含颜色信息的 DIB 像素流,这样 JPEG 库才能正常工作.

====================================================================================================
附录: 截取windows桌面,并保存为 .jpg 文件


int save_screen_to_jpeg(const char* filename, int quality)
{
 /*
 * 把屏幕内容保存为一个 HBITMAP DDB
 */
    HDC hScrnDC = CreateDC(_T("DISPLAY"), NULL, NULL, NULL);
    HDC hMemDC = CreateCompatibleDC(hScrnDC);




 // 获取屏幕分辨率
    int xScrn = GetDeviceCaps(hScrnDC, HORZRES);
    int yScrn = GetDeviceCaps(hScrnDC, VERTRES);




 // 创建位图,并选中
 HBITMAP hScrnBmp = CreateCompatibleBitmap(hScrnDC, xScrn, yScrn);
 SelectObject(hMemDC, hScrnBmp);




 // 复制屏幕内容
    BitBlt(hMemDC, 0, 0, xScrn, yScrn, hScrnDC, 0, 0, SRCCOPY);




 // 现在得到了一个 HBITMAP DDB - hScrnBmp




 /*
 * 通过 hScrnBmp DDB 取得 DIB 数据
 */
 // 获取色深 JPG 只能处理 24 位色,所以不管当前系统设置的色深是多少,我们都要求 GetDIBits 函数返回 24 位的 DIB 数据,同时也不需要调色板
 //int colorDeepBits = GetDeviceCaps(hScrnBmp, BITSPIXEL);
 //if(colorDeepBits > 24) colorDeepBits = 24;
 int colorDeepBits = 24;




 // 每行像素占用的字节数,每行要对齐4字节.
 int imageRowSize = (xScrn * colorDeepBits + 31) / 32 * 4; 




 // 分配 DIB 数组
 unsigned char* dibBuffer = new unsigned char[imageRowSize * yScrn];
 assert(dibBuffer);
 memset(dibBuffer, 0, imageRowSize * yScrn); // 清零是个好习惯    




 // 填充 BMP 信息头
 BITMAPINFO bmi = {0};
 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
 bmi.bmiHeader.biWidth = xScrn;
 bmi.bmiHeader.biHeight = yScrn * -1;  // JPG 压缩需要正序的 DIB 像素流,所以要负数.
 bmi.bmiHeader.biPlanes = 1;
 bmi.bmiHeader.biBitCount = colorDeepBits;
 bmi.bmiHeader.biCompression = BI_RGB;




 // 获取 DIB 像素数组(DIB_RGB_COLORS 表示获取 RGB 值而不是调色板索引,当然24位位图也没有调色板)
 int gdiRet = GetDIBits(hMemDC, hScrnBmp, 0, yScrn, dibBuffer, &bmi, DIB_RGB_COLORS);
 assert(gdiRet == yScrn);
 assert(bmi.bmiHeader.biSizeImage == imageRowSize * yScrn);




 // DIB 数据已经获取,所有的 GDI 对象可以释放了.
 DeleteDC(hScrnDC);
 DeleteDC(hMemDC);
 DeleteObject(hScrnBmp);




 /*
 * 把 DIB 数据压缩为 JPG 数据,用 example.c 中的代码
 */




 // DIB 中颜色的存放顺序是 BGR, 而 JPG 要求的顺序是 RGB, 所以要交换 R 和 B.
 // 由于有行对齐因素,所以逐行处理
 for(int row = 0; row < yScrn; ++row)
 {
  unsigned char* rowData = dibBuffer + imageRowSize * row;
  for(int col = 0; col < xScrn * 3; col += 3)
  {
   unsigned char swap = rowData[col];
   rowData[col] = rowData[col + 2];
   rowData[col + 2] = swap;
  }
 }




 //把位图数据压缩为 jpeg
 struct jpeg_compress_struct cinfo;
 struct jpeg_error_mgr jerr;
 FILE * outfile;  /* target file */
 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
 int row_stride;  /* physical row width in image buffer */
 int image_width = xScrn;
 int image_height = yScrn;
 JSAMPLE* image_buffer = dibBuffer;     // DIB buffer
 int image_buffer_len = imageRowSize * image_height; // DIB buffer 长度




 if(fopen_s(&outfile, filename, "wb"))
 //if ((outfile = fopen_s(filename, "wb")) == NULL) 
 {
  fprintf(stderr, "can't open %s\n", filename);
  assert(0);
 }
 else
 {
  /* Step 1: allocate and initialize JPEG compression object */
  cinfo.err = jpeg_std_error(&jerr);




  /* Now we can initialize the JPEG compression object. */
  jpeg_create_compress(&cinfo);




  /* Step 2: specify data destination (eg, a file) */
  /* Note: steps 2 and 3 can be done in either order. */
  jpeg_stdio_dest(&cinfo, outfile);




  /* Step 3: set parameters for compression */




  /* First we supply a description of the input image.
   * Four fields of the cinfo struct must be filled in:
   */
  cinfo.image_width = image_width;  /* image width and height, in pixels */
  cinfo.image_height = image_height;
  cinfo.input_components = 3;  /* # of color components per pixel */ // 因为DIB数据是24位的,所以每个像素占用3个字节
  cinfo.in_color_space = JCS_RGB;  /* colorspace of input image */
  /* Now use the library's routine to set default compression parameters.
   * (You must set at least cinfo.in_color_space before calling this,
   * since the defaults depend on the source color space.)
   */
  jpeg_set_defaults(&cinfo);
  /* Now you can set any non-default parameters you wish to.
   * Here we just illustrate the use of quality (quantization table) scaling:
   */
  jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);




  /* Step 4: Start compressor */




  /* TRUE ensures that we will write a complete interchange-JPEG file.
   * Pass TRUE unless you are very sure of what you're doing.
   */
  jpeg_start_compress(&cinfo, TRUE);




  /* Step 5: while (scan lines remain to be written) */
  /*           jpeg_write_scanlines(...); */




  /* Here we use the library's state variable cinfo.next_scanline as the
   * loop counter, so that we don't have to keep track ourselves.
   * To keep things simple, we pass one scanline per call; you can pass
   * more if you wish, though.
   */
  row_stride = imageRowSize;
  while (cinfo.next_scanline < cinfo.image_height) 
  {
   /* jpeg_write_scanlines expects an array of pointers to scanlines.
    * Here the array is only one element long, but you could pass
    * more than one scanline at a time if that's more convenient.
    */
   row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride];
   //row_pointer[0] = &image_buffer[image_buffer_len - (cinfo.next_scanline + 1) * row_stride];
   (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
  }




  /* Step 6: Finish compression */
  jpeg_finish_compress(&cinfo);




  /* After finish_compress, we can close the output file. */
  fclose(outfile);
  
  /* Step 7: release JPEG compression object */
  /* This is an important step since it will release a good deal of memory. */
  jpeg_destroy_compress(&cinfo);
 }




 // 释放 DIB 数组
 delete []dibBuffer;
 return 0;
}

PS: 最近一年半在忙一个项目,一直没时间更新博客,也没有回答网友们的提问,非常抱歉.大多数问题都是百度一下就可以处理的,只能说抱歉了.
截屏保存为JPG其实也是我在项目中用到的一个小功能,因为不涉及什么商业上的东西,就贴出来分享一下.
以后有机会我会把项目中的一些功能分解成可以复用的代码,大家交流一下.


VB6结合GDI+实现内存(Stream)压缩/解压缩JPG(JPEG)图像 CSDN论坛vb版中有一个讨论内存压缩JPEG的帖子,在此我贴出解决代码。代码原理原本就简单,所以注释也很简单。希望能帮助到苦苦思索的朋友们。(魏滔序原创,转帖请注明出处。) 首先要下载一个IStream库,用该库可以减少代码量,如果直接全部用API也未尝不可。IStream下载地址:IStream.zip 下面是代码,可以贴在一个模块中:Option Explic 阅读详情

相关推荐

BMP、GIFJPEG文件格式简介及相互转换

BMP、GIFJPEG文件格式简介及相互转换 作者:江平 发布时间:2000/11/27 文章摘要:  本文主要讨论的是位图的压缩方法。        正文: BMP、GIFJPEG文件格式简介及相互转换    图象文件就是描绘了一幅图象的计算机磁盘文件。形成数

CandyCat的专栏 4673

VB6图片压缩处理源码

vb6编写的图片处理软件源代码,可以将图片按设定值来压缩保存。图片编辑后保存等

选择正确的图片格式:JPEG、PNG、GIF、TIFF、BMPWebP的全面对比

选择正确的图片格式对于图像质量、文件大小适用场景至关重要。JPEG、PNG、GIF、TIFF、BMPWebP是目前常见的图片格式,每种格式都有其优势限制。了解这些格式的特点应用领域,可以帮助我们做出明智的选择,以满足不同的需求。在实际应用中,我们应根据具体情况综合考虑各种因素,选择最适合的图片格式。

https://blog.cmdragon.cn/ 3235

PicConver(GDIPlus).rar_gdiplus jpg_jpg tif_png jpg_vb 图片格式_图像 压缩

用VB6开发的一款图片批量压缩、格式转换工具,可将BMP、TIF、PNG、GIF、JPG等格式压缩并转换成PNG、GIF、JPG格式。支持按高等比缩放、按宽等比缩放、自定义尺寸等比缩放等模式,缩放后图像不会变形(等比嘛)。采用GDI+技术,是VB开发人员学习熟悉GDI+的绝佳例子。

VC GDI+ 窗口截图内存BMP转JPG,压缩、JPG再转IStream流,IStream流再转 BYTE

VC GDI+ 窗口截图内存BMP转JPG,压缩、JPG再转IStream流,IStream流再转 BYTE 上体见 http://blog.csdn.net/q610098308/article/details/78181933

BMPJPEG文件格式详解

文章目录六、BMP文件格式详解1、BMP文件格式2、BMP文件头3、位图信息头4、调色板5、位图数据6、对齐规则七、JPEG文件结构分析1、JPEG2、SOI ( Start of Image )2、APP0(Application 0)3、DQT ( Define Quantization Table )4、SOF0 (Start of Frame)5、DHT ( Define Huffman Table )6、SOS ( Start of Scan )7、EOI ( End of Image ) 六、B

m0_37633745的博客 5781

VC6怎样将bmp格式图象转换为jpg格式文件?

VC6怎样将bmp格式图象转换为jpg格式文件?   利用 IJG 的 jpeg 库。   关于如何使用 IJG jpeg 库,请参考前面的文章: VC6下编译IJG库(用于读取生成jpg格式图片文件)   函数原型: /************************************************************************** 压缩...

编程思考 www.vktone.com 394

C语言实现的BMPJPEG图片的解码

/* 这是read_picture.c 文件的代码 ,主要是对输入的图片的文件(BMPJPEG类型)进行解码,转换成在LCD中显示的数据; 使用该程序时,要先把jpeglib库文件头文件放到对应的标谁库标准头文件中;否则编译出错的 */ #include #include #include #include #include #include #include

YANGSENG1987的专栏 9030

bmp jpeg php,bmpjpg的区别

bmpjpg的区别:1、图像文件大小不同,BMP格式图片更大;2、图像文件格式不同、后缀名不同,bmp是Bitmap的缩写,后缀名是BMP,JPG又称为JPEG;3、应用广度不同,JPG应用更广。bmpjpg的区别:一、二者优劣势BMP:优点(无损压缩,图质最好),缺点(文件太大,不利于网络传输)JPG:优点(文件小,利于网络传输),缺点(画质损失)二、二者区别1)图像文件大小不同:BMP格式...

weixin_36215894的博客 635

通过BitmapImage JpegBitmapEncoder 将BMP图像转换成JPEG格式

一:通过BitmapImage JpegBitmapEncoder 将BMP图像转换成JPEG格式 核心代码如下: class Program { static void Main(string[] args) { string BMPFiles = "11.bmp"; BitmapImage bitImage = new BitmapImage(new Uri(BMPFiles,UriKind.Absolute)...

锡克步兵团团长的博客 1870

BMPJPEG的区别

BMP是一种与硬件设备无关的图像文件格式,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BblP文件所占用的空间很大。BMP文件的图像深度可选lbit、4bit、8bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。 由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图

C C++ VC 1984

vc6实现bmpjpeg不使用库文件有注释

在vc6.0上实现了bmpjpeg压缩流程,没有调用库文件,可以很容易地转为纯C语言,最重要的是这份源码注释详细,绝对适合配合学习jpeg压缩算法

VC++实现bmp转换为JPG

VC++实现bmp图片转换为JPG格式,完全可实现

BMP图像处理(jpegbmp,以及bmp图片缩放,附代码)_c语言的pngjpeg图片格式转换为bmp格式

/存储原本jpg格式的文件数据。//存储新的数据的变量。//打开原本jpg格式的文件。//生成目标bmp文件。printf(“BmpZoom~~调用开始!

2401_87255364的博客 911

BMP、GIFJPEG图片格式简介

BMP、GIFJPEG文件格式简介  图象文件就是描绘了一幅图象的计算机磁盘文件。形成数字图象数据后,将其存储在计算机里的方法有2种,即位映射向量处理方式。  我们这里主要讨论的是位图。不同图象软件几乎都用各种的方法处理图象,图象格式也多种多样,它主要包括文件识别头图象数据。文件识别头用来让计算机判断是哪种文件格式,图象数据包含了整个对图象描绘相关数据,包括调色板、位图映象等。根据压

yongzhewuwei_2008的专栏 1403

BMPJPEG - 附截屏保存为JPG源码

1. 什么是 BMP BMP 格式是最简单,最直观的位图数据格式.它的思想非常朴素: 用若干个位来保存一个像素的信息,由若干个像素组成一个像素流来表达一张图片. 通常我们会用1位(可以保存2种颜色,0 表示一种颜色,1表示另一种,不一定是黑白,也可以是蓝绿,总之是2种),4位 - 16种颜色,8位 - 256种颜色,16位 - 65535种颜色,24位 - 2^24种颜色,32位 - 前24

因热爱而执着,因执着而成功。 903

C代码生成图片:BMP、PNGJPEG

C代码生成图片:BMP、PNGJPEG

jacke121的专栏 7088
上一篇: 打造windows平台下的PHP开发环境
下一篇: 理解和使用zlib库 - 我个人的救赎
querw
博客等级 码龄22年 164粉丝 39原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值