JPG学习笔记1(附完整代码)
#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
JPG学习笔记1(附完整代码)的更多相关文章
- JPG学习笔记3(附完整代码)
#topics h2 { background: rgba(43, 102, 149, 1); border-radius: 6px; box-shadow: 0 0 1px rgba(95, 90, ...
- Android 监听双卡信号强度(附完整代码)
Android 监听双卡信号强度 监听单卡信号强度 监听单卡的信号强度非常简单直接用TelephonyManager.listen()去监听sim卡的信号强度. TelephonyManager = ...
- JPG学习笔记4(附完整代码)
#topics h2 { background: rgba(43, 102, 149, 1); border-radius: 6px; box-shadow: 0 0 1px rgba(95, 90, ...
- JPG学习笔记2(附完整代码)
#topics h2 { background: rgba(43, 102, 149, 1); border-radius: 6px; box-shadow: 0 0 1px rgba(95, 90, ...
- WebGL three.js学习笔记 创建three.js代码的基本框架
WebGL学习----Three.js学习笔记(1) webgl介绍 WebGL是一种3D绘图协议,它把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的 ...
- 学习笔记:python3,代码。小例子习作(2017)
http://www.cnblogs.com/qq21270/p/7634025.html 学习笔记:python3,一些基本语句(一些基础语法的代码,被挪到这里了) 日期和时间操作 http://b ...
- 学习笔记:python3,代码。小例子习作
http://www.cnblogs.com/qq21270/p/7634025.html 学习笔记:python3,一些基本语句(一些基础语法的代码,被挪到这里了) 日期和时间操作 http://b ...
- 雨痕 的《Python学习笔记》--附脑图(转)
原文:http://www.pythoner.com/148.html 近日,在某微博上看到有人推荐了 雨痕 的<Python学习笔记>,从github上下载下来看了下,确实很不错. 注意 ...
- Linux Shell输出颜色字符学习笔记(附Python脚本实现自动化定制生成)
齿轮发出咔嚓一声,向前进了一格.而一旦向前迈进,齿轮就不能倒退了.这就是世界的规则. 0x01背景 造了个轮子:御剑师傅的ipintervalmerge的Python版本.觉得打印的提示信息如果是普通 ...
- 基于C#的内网穿透学习笔记(附源码)
如何让两台处在不同内网的主机直接互连?你需要内网穿透! 上图是一个非完整版内外网通讯图由内网端先发起,内网设备192.168.1.2:6677发送数据到外网时候必须经过nat会转换成 ...
随机推荐
- windows下的:开始→运行→命令
开始→运行→命令 集锦 winver---------检查Windows版本wmimgmt.msc----打开windows管理体系结构(WMI)wu ...
- 入门OJ:Coin
题目描述 你有n个硬币,第i硬币面值为ai,现在总队长想知道如果丢掉了某个硬币,剩下的硬币能组成多少种价值?(0价值不算) 输入格式 第一行一个整数n 第二行n个整数.,a1,a2-an. 1< ...
- django 中连接mysql数据库的操作步骤
django中连接mysql数据库的操作步骤: 1 settings配置文件中 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mys ...
- Netty之ChannelHandler
一.概述 handler是控制socket io的各个生命周期的业务实现,netty实现了很多种协议所以有很多handler类,这儿主要关注Handler的设计.作用以及使用方法. 二.Channel ...
- (Oracle)取当前日期的最近工作日
描述:现有一需求,日期表中存放了日期和是否节假日(0-工作日,1-节假日),现在需要取日期表中的最近的工作日.如2017/07/23(周日)最近的工作日应该是2017/07/21(周五). ...
- .Vue-router跳转和location.href有什么区别
使用location.href='/url'来跳转,简单方便,但是刷新了页面:使用history.pushState('/url'),无刷新页面,静态跳转:引进router,然后使用router.pu ...
- power network 电网——POJ1459
Power Network Time Limit: 2000MS Memory Limit: 32768K Total Submissions: 27282 Accepted: 14179 D ...
- 《我想进大厂》之Zookeeper夺命连环9问
谈谈你对Zookeeper的理解? Zookeeper是一个开源的分布式协调服务,由雅虎公司创建,由于最初雅虎公司的内部研究小组的项目大多以动物的名字命名,所以后来就以Zookeeper(动物管理员) ...
- Java——反射机制
反射概念: Java反射是Java被视为动态(或准动态)语言的一个关键性质.这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifier ...
- 前台console调试技巧
前台console调试技巧 一.console.log() 二.console.warn() 三.console.dir() 四.console.table() 五.console.assert() ...