#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. kubernetes之为每个命名空间的pod设置默认的requests以及limits

    一  为啥需要为命名空间里面添加pod添加默认的requests和limits? 通过前面的学习我们已经知道,如果节点上面的pod没有设置requests和limits,这些容器就会受那些设置了的控制 ...

  2. Java 迭代器的使用 Iterator

    Java的集合类可以使用for ... each循环 List Set Queue Deque 我们以List为例 其实一个java编译器并不知道如何遍历一个List 编译器只是把一个for ... ...

  3. 图解 ECDHE 密钥交换算法

    HTTPS 常用的密钥交换算法有两种,分别是 RSA 和 ECDHE 算法. 其中,RSA 是比较传统的密钥交换算法,它不具备前向安全的性质,因此现在很少服务器使用的.而 ECDHE 算法具有前向安全 ...

  4. H3C、Huawei、Cisco网络设备AAA TACACS认证配置

    TACACS技术白皮书 摘要:TACACS是实现AAA功能的一种安全协议,主要是通过TACACS客户端与TACACS服务器通信来实现多种用户的AAA功能. HWTACACS采用TCP协议承载报文,TC ...

  5. Slack 的想法很好啊,很有创新,牛。

    [原]https://www.leiphone.com/news/201411/aXHUpe4ZFI2sSwpb.html 由于以往一些用于办公的应用反响平平,因此对迅速崛起的办公交流应用Slack, ...

  6. 解决window10 和 ubuntu 双系统安装没有启动选项问题

    win10 和Ubuntu 双系统安装在网上已经有很多例子了,这里就不在赘述了. 今天新买的笔记本,想安装双系统.正常安装完ubuntu 重启后没有选项. 解决方法一 下载和解压以后,按照以下的步骤安 ...

  7. 结合python版本安装python-devel gcc和g++的区别 安装前做yum搜索

    [test@ecs autocloudservices]# yum install python-develLoaded plugins: fastestmirrorLoading mirror sp ...

  8. 浅谈自动化构建之gulp

    一.gulp的基本使用 gulp是目前最流行的前端自动化构建系统,核心特点高效易用.(这块不过多的废话了,直接上干货了,有兴趣的话,可以查下gulp简介) 步骤如下: yarn init -y yar ...

  9. 消息队列扫盲(RocketMQ 入门)

    消息队列扫盲 消息队列顾名思义就是存放消息的队列,队列我就不解释了,别告诉我你连队列都不知道似啥吧? 所以问题并不是消息队列是什么,而是 消息队列为什么会出现?消息队列能用来干什么?用它来干这些事会带 ...

  10. 非关系型数据库(NOSQL)和关系型数据库(SQL)区别详解

    前言: 在我们的日常开发中,关系型数据库和非关系型数据库的使用已经是一个成熟的软件产品开发过程中必不可却的存储数据的工具了.那么用了这么久的关系数据库和非关系型数据库你们都知道他们之间的区别了吗?下面 ...