#topics h2 { background: rgba(43, 102, 149, 1); border-radius: 6px; box-shadow: 0 0 1px rgba(95, 90, 75, 1), 1px 1px 6px 1px rgba(10, 10, 0, 0.5); color: rgba(255, 255, 255, 1); font-family: "微软雅黑", "宋体", "黑体", Arial; font-size: 15px; font-weight: bold; height: 24px; line-height: 23px; margin: 12px 0 !important; padding: 5px 0 5px 10px; text-shadow: 2px 2px 3px rgba(34, 34, 34, 1) }
#topics h1 span { font-weight: bold; line-height: 1.5; font-family: "Helvetica Neue", Helvetica, Verdana, Arial, sans-serif; text-decoration: underline; color: rgba(201, 27, 67, 1); text-shadow: 2px 2px 3px rgba(34, 34, 34, 1) }

  在学习图象处理的过程中,JPEG是我的第一个拦路虎。一直很想手写一下JPG的压缩和解压的过程,我在网上找到了一些代码或者文章,很多都是没有注释或者是解释不够清楚的。所以特地写这篇文章记录自己从无到有写一个JPEG_Encoder的过程,也能帮助其他学习图形或者音视频的童鞋。对于不想看文章的同学,这边直接上代码 https://github.com/Cheemion/JPEG_COMPRESS。 以下是JPEG的压缩流程。采样->>离散傅里叶变化->>量化->>哈夫曼压缩->>写入jpg文件. 在进行这些流程之前,必须从BMP文件中读取待压缩的图片文件。

图片引用自"Compressed Image File Formats JPEG, PNG, GIF, XBM, BMP - John Miano"[4]

          

1.BMP文件Format

[1]BMP文件

一个文件头BITMAPFILEHEADER,里面包含了文件的各种信息。

一个图片头BITMAPINFOHEADER,里面包含了图片信息。

一个RGBQUAD array,里面包含了像素的对应关系,比如1 代表 RGB(1,1,1)。因为我们用的都是24位图片,所以我们不会不考虑这一项。

一个Color-index,就是我们的图片的像素了。我们只考虑24位图像

BMP文件的字节是大端存储的

BMP图片的每一行像素所占的字节数必须是4字节的的整数倍

BMP数据的第一行像素存储的是图片的最后一行的数据(相当于图片在BMP中是倒置的)

2.BITMAPFILEHEADER(头文件格式)

[2]头文件包含如下的字段

1 typedef struct tagBITMAPFILEHEADER {
2 WORD bfType; //文件类型 规定为'BM'
3 DWORD bfSize; //文件大小
4 WORD bfReserved1; //保留
5 WORD bfReserved2; //保留
6 DWORD bfOffBits; //数据起始地址距离首地址的位置
7 } BITMAPFILEHEADER, *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;

3.BITMAPINFOHEADER文件格式

[3]bitMapInfoHeader根据版本的不同包含了不同的结构,我们主要用到了BitMapInfoHeader和BitMapCoreHeader

以下是BitMapInfoHeader


1 typedef struct tagBITMAPINFOHEADER {
2 DWORD biSize; // infoHeader这个头文件的大小
3 LONG  biWidth; // 图片宽多少像素
4 LONG  biHeight; //高多少
5 WORD  biPlanes; //默认1
6 WORD  biBitCount; //一个像素点有多少位
7 DWORD biCompression; //是否压缩过
8 DWORD biSizeImage; //图片大小
9 LONG  biXPelsPerMeter; //never used
10 LONG  biYPelsPerMeter; //never used
11 DWORD biClrUsed; //never used
12 DWORD biClrImportant; //never used
13 } BITMAPINFOHEADER, *PBITMAPINFOHEADER;

以下是BitMapCoreHeader, coreHeader就简单多了,就是少了一些字段,其他一毛一样。

typedef struct tagBITMAPCOREHEADER {
DWORD bcSize;
WORD bcWidth;
WORD bcHeight;
WORD bcPlanes;
WORD bcBitCount;
} BITMAPCOREHEADER, *LPBITMAPCOREHEADER, *PBITMAPCOREHEADER;

4.读取图片数据

定义常用结构

using byte = unsigned char;
using uint = unsigned int;
struct RGB {
byte blue;
byte green;
byte red;
};

定义文件结构,和一个BMPReader(用来读取bmp文件)

 1 #pragma once
4 class BMPReader {
5 public:
6 BMPReader() = default;
7 ~BMPReader() {
8 if (data) {
9 delete[] data;
         data = nullptr;
10 }
11 }
12 bool open(std::string& path);
13 public:
14 uint height = 0;
15 uint width = 0;
16 uint paddingBytes = 0;
17 RGB* data = nullptr; //实际的数据
18 };
19
20 //2个字节对齐,
21 #pragma pack(2)
22 typedef struct {
23 unsigned short bfType = 0x424d;
24 unsigned int bfSize = 0;
25 unsigned short bfReserved1 = 0;
26 unsigned short bfReserved2 = 0;
27 unsigned int bfOffBits = 0;
28 } BitMapFileHeader;
29
30 typedef struct {
31 unsigned int biSize;
32 int biWidth;
33 int biHeight;
34 unsigned short biPlanes;
35 unsigned short biBitCount;
36 unsigned int biCompression;
37 unsigned int biSizeImage;
38 int biXPelsPerMeter;
39 int biYPelsPerMeter;
40 unsigned int biClrUsed;
41 unsigned int biClrImportant;
42 } BitMapInfoHeader;
43
44 typedef struct {
45 unsigned int bcSize;
46 unsigned short bcWidth;
47 unsigned short bcHeight;
48 unsigned short bcPlanes = 1;
49 unsigned short bcBitCount = 24;
50 } BitMapCoreHeader;
51 #pragma pack()

读取图片数据到BMPReader的data字段

bool BMPReader::open(std::string& path) {
FILE* file = nullptr;
   //判断是否打开图片成功
if ((file = fopen(path.c_str(), "rb")) == nullptr) {
printf("error occured when opening file:%s", path.c_str());
return false;
} BitMapFileHeader fileHeader;
//读取文件头
if (fread(&fileHeader, sizeof(fileHeader), 1, file) != 1) {
printf("Error - error occured when reading BITMAPFILEHEADER");
return false;
}
  //判断是不是BMP文件,通过判断'BM'
if(fileHeader.bfType != 0x4D42) {
printf("Error - this is not a BMP file that you're reading");
return false;
}
  

//读取infoHeader的大小,通过size来判断是哪个版本的BitMapInfoHeader
uint infoHeaderSize;
fread(&infoHeaderSize, sizeof(infoHeaderSize), 1, file);
fseek(file, -sizeof(infoHeaderSize), SEEK_CUR);
//2中infoHeader, 通过size来判断

//如果是BitMapCoreHeader的话
if (infoHeaderSize == sizeof(BitMapCoreHeader)) {
BitMapCoreHeader bitMapCoreHeader;
if (fread(&bitMapCoreHeader, sizeof(bitMapCoreHeader), 1, file) != 1) {
printf("Error - mal-structure BITMAPCOREHEADER");
return false;
}
this->width = bitMapCoreHeader.bcWidth;
this->height = bitMapCoreHeader.bcHeight;
if (bitMapCoreHeader.bcBitCount != 24) {
printf("Error - the picture format is not consistent with our programm");
return false;
}
} else {
BitMapInfoHeader bitMapInfoHeader;
if (fread(&bitMapInfoHeader, sizeof(bitMapInfoHeader), 1, file) != 1) {
printf("Error - mal-structure BITMAPINFOHEADER");
return false;
}
this->width = bitMapInfoHeader.biWidth;
this->height = bitMapInfoHeader.biHeight;
if (bitMapInfoHeader.biBitCount != 24) {
printf("Error - the picture format is not consistent with our programm");
return false;
}
}
// 必须是4byte的整数倍,算出需要padding多少字节,也就是需要填补多少字节// if width = 1, 1 * 3个像素 * 8位 = 24位, 差一个字节, paddingSize = 1
// if width = 2, 2 * 3个 * 8位 = 48位 paddingSize = 2
// if width = 3, paddingSize = 3
// if width = 4, paddingSize = 0
this->paddingBytes = width % 4;
  
//为像素数据 创建内存空间
data = new (std::nothrow) RGB[this->width * this->height];
if (!data) {
printf("Error - error when allocating memroy for RGB");
return false;
}
//跳到data数据的位置
fseek(file, fileHeader.bfOffBits, SEEK_SET);
for (uint i = 0; i < height; i++) {
//read data一行,一行的读取放入我们的data
if (width != fread(data + (height - 1 - i) * width , sizeof(RGB), width, file)) {
printf("Error - something wrong when reading data from BMP file");
delete data;
return false;
}
//因为可能有paddingSize,所以这边跳过PaddingSize
fseek(file, paddingBytes, SEEK_CUR);
}
fclose(file);
return true;
}

以上全部的代码在https://github.com/Cheemion/JPEG_COMPRESS/tree/main/Day1

完结

Thanks for reading, happy lunar new year.


参考资料

[1]https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-storage

[2]https://docs.microsoft.com/en-us/previous-versions//dd183376(v=vs.85)

[3]https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapcoreheader

[4]https://github.com/Cheemion/JPEG_COMPRESS/blob/main/resource/Compressed%20Image%20File%20Formats%20JPEG%2C%20PNG%2C%20GIF%2C%20XBM%2C%20BMP%20-%20John%20Miano.pdf

JPG学习笔记1(附完整代码)的更多相关文章

  1. JPG学习笔记3(附完整代码)

    #topics h2 { background: rgba(43, 102, 149, 1); border-radius: 6px; box-shadow: 0 0 1px rgba(95, 90, ...

  2. Android 监听双卡信号强度(附完整代码)

    Android 监听双卡信号强度 监听单卡信号强度 监听单卡的信号强度非常简单直接用TelephonyManager.listen()去监听sim卡的信号强度. TelephonyManager = ...

  3. JPG学习笔记4(附完整代码)

    #topics h2 { background: rgba(43, 102, 149, 1); border-radius: 6px; box-shadow: 0 0 1px rgba(95, 90, ...

  4. JPG学习笔记2(附完整代码)

    #topics h2 { background: rgba(43, 102, 149, 1); border-radius: 6px; box-shadow: 0 0 1px rgba(95, 90, ...

  5. WebGL three.js学习笔记 创建three.js代码的基本框架

    WebGL学习----Three.js学习笔记(1) webgl介绍 WebGL是一种3D绘图协议,它把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的 ...

  6. 学习笔记:python3,代码。小例子习作(2017)

    http://www.cnblogs.com/qq21270/p/7634025.html 学习笔记:python3,一些基本语句(一些基础语法的代码,被挪到这里了) 日期和时间操作 http://b ...

  7. 学习笔记:python3,代码。小例子习作

    http://www.cnblogs.com/qq21270/p/7634025.html 学习笔记:python3,一些基本语句(一些基础语法的代码,被挪到这里了) 日期和时间操作 http://b ...

  8. 雨痕 的《Python学习笔记》--附脑图(转)

    原文:http://www.pythoner.com/148.html 近日,在某微博上看到有人推荐了 雨痕 的<Python学习笔记>,从github上下载下来看了下,确实很不错. 注意 ...

  9. Linux Shell输出颜色字符学习笔记(附Python脚本实现自动化定制生成)

    齿轮发出咔嚓一声,向前进了一格.而一旦向前迈进,齿轮就不能倒退了.这就是世界的规则. 0x01背景 造了个轮子:御剑师傅的ipintervalmerge的Python版本.觉得打印的提示信息如果是普通 ...

  10. 基于C#的内网穿透学习笔记(附源码)

    如何让两台处在不同内网的主机直接互连?你需要内网穿透!          上图是一个非完整版内外网通讯图由内网端先发起,内网设备192.168.1.2:6677发送数据到外网时候必须经过nat会转换成 ...

随机推荐

  1. 实现简易版德州扑克|学习麻瓜编程以项目为导向入门前端 HTML+CSS+JS

    实现简易版德州扑克 1.先上达到网页效果图(简易版德州扑克) 网页分为发牌区和牌池,上面为发牌区,下面是牌池区 2. 代码实现 2.1 HTML和JS代码 ` <link rel="s ...

  2. 关于BAPI_TRANSACTION_COMMIT一点说明

    我们调用bapi做了相关的业务操作后,通常都要在后面调用 BAPI_TRANSACTION_COMMIT来提交所做得更改 然而,有时候,在程序中需要调用多个不同的BAPI实现不同的功能,那么这个时候就 ...

  3. 24v转3.3v稳压芯片,高效率DC-DC变换器3A输出电流

    PW6206系列是一个高精度,高输入电压低静态电流,高速,低功耗降线性稳压器具有高纹波抑制.输入电压高达40V,负载电流为在VOUT=5V和VIN=7V时高达300mA.该设备采用BCD工艺制造.PW ...

  4. CentOS系统内核升级(在线 离线)

    为什么要升级内核? Docker 在CentOS系统中需要安装在 CentOS 7 64 位的平台,并且内核版本不低于 3.10:CentOS 7.× 满足要求的最低内核版本要求,但由于 CentOS ...

  5. Windows Server 2012 R2 英文版汉化安装中文语言包教程更改为中文版

    是这样的,一台海外的windows机器默认是英文版的,但是特别费劲用起来,就更改为中文版,因为海外的供应商并不提供中文版镜像. 1.首先打开控制面板,找到add language,拉到底就是有中文,很 ...

  6. a.default.ERROR.httpAjax is not a function

    原因1: 使用的是jQuery的slim构建,它删除了一些东西,ajax就是其中之一. 解决方法: 在此处下载常规(压缩或非压缩)版本的jQuery并将其包含在您的项目中. 原因2: 使用其他库引起了 ...

  7. 公共错误码 - 支付宝开放平台 https://opendocs.alipay.com/open/common/105806

    公共错误码 - 支付宝开放平台 https://opendocs.alipay.com/open/common/105806

  8. DP中的树上边/点覆盖问题

    目录 树上边覆盖问题 例:luoguP2016 战略游戏 简述题意: Solution: Code 树上点覆盖问题 简述题意 Solution Code: 树上边覆盖问题 例:luoguP2016 战 ...

  9. 济南学习D1T5__HEAP

    死亡 [问题描述] 现在有个位置可以打sif,有个人在排队等着打sif.现在告诉你前个人每个人需要多长的时间打sif,问你第个人什么时候才能打sif.(前个人必须按照顺序来) [输入格式] 第一行两个 ...

  10. SpringMVC听课笔记(十五:SpringMVC 运行流程)

    1. 图 一般的会按照红线标注的方向去行进,但是请求静态资源,或者出现异常等,会出现其他路径 2.