原理
JPEG图像的存储压缩结构
参考链接:https://www.zhihu.com/question/22116845/answer/22998727
来源:知乎
JPEG文件的格式是分为一个一个的段来存储的(但并不是全部都是段),段的多少和长度并不是一定的。只要包含了足够的信息,该JPEG文件就能够被打开,呈现给人们。
JPEG文件的每个段都一定包含两部分:
① 段的标识,它由两个字节构成:第一个字节是十六进制0xFF,第二个字节对于不同的段,这个值是不同的。
② 紧接着的两个字节存放的是这个段的长度(除了前面的两个字节0xFF和0xXX,X表示不确定。他们是不算到段的长度中的。这个长度的表示方法是按照高位在前,低位在后的,与Intel的表示方法不同。比方说一个段的长度是0x12AB,那么它会按照0x12,0xAB的顺序存储。但是如果按照Intel的方式:高位在后,低位在前的方式会存储成0xAB,0x12,而这样的存储方法对于JPEG是不对的。
APP0段中主要存储的是图片的识别信息(字符串”JFIF\0”)、一些分辨率的信息以及缩略图的信息。在我的实际测试中,发现并不是所有的JPEG文件都有APP0段的,有的仅是有APP2之类的其他段,但是每个文件中肯定是包含APPX的段(X可以取得的值可以查阅相关文档)。
DQT段的内容是量化表的信息。众所周知,一个颜色可以分为RGB(红、绿、兰)三个分量,这三色光组成了我们可以见到的所有色彩。但是,在JPEG文件中,RGB色彩格式需要先转化为YUV的格式。Y分量代表了亮度信息,UV分量代表了色差信息。相比之下,人眼对于Y分量更为敏感。量化表的作用就是对于一些不需要的量进行去除,这也是JPEG有损压缩损失数据的关键。上面的输出可以看到两个量化表,一个给Y分量,另一个给UV分量。
SOF0段的内容是图像的大小信息,每个像素的位数信息,以及YUV每个分量分别得的采样信息(这部分如果读者想要进一步学习,请参考相应书籍和文档)。JPEG文件图像的编码是一个方块一个方块进行的,每块的大小为8x8大小(如果图像不是整数个方块的大小那么就对图像补齐为整数个大小)。简略地说采样信息,就是如何按组记录YUV的信息,即若干个Y方块,若干个U方块,若干个V方块经过量化的数据再次经过编码后组成一组记录,保存在SOS段结束后。
DHT段的内容是一个重头戏,如果没有它,JPEG压缩效率就不会那么高了。它内部定义的是一个Huffman表,不同的DHT段定义不同的Huffman表,有的是直流量的表,有的是交流量的表。
所以我从一张BMP图像中读取多组数据,例如图像高度,宽度,灰度等。将JPEG的段在代码中封装为类-class,类中定义数据组,然后从BMP图片中读取数据,不同的数据存在不同的类中的数据组中。
bmp文件
参考链接:https://zhuanlan.zhihu.com/p/260320604
BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选 1bit、4bit、8bit及24bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。在Windows系统平台上和Android手机上,直接使用系统默认的图片浏览器即可打开。
BMP文件总共由四个部分构成:
(1)BMP文件头(bmp file header):提供文件的格式、大小等信息
(2)位图信息头(bitmap information):提供图像数据的尺寸、位平面数、压缩方式、颜色索引等信息
(3)调色板(color palette):可选,如使用索引来表示图像,调色板就是索引与其对应的颜色的映射表,在使用 256位彩色、16位彩色等情况下用到
(4)位图数据(bitmap data):就是图像的像素数据
1、BMP文件头部分
固定占用14个字节,包括文件类型表示、文件大小、像素数据偏移等信息
2、位图信息头
固定占用40个字节,包含了图像的基本新,例如:像素的宽度、高度、色彩格式位数、是否有压缩(绝大部分情况下无压缩)等。
3、调色板信息
这一项是可选项,适用于索引方式伪彩色的图像数据。调色板其实是一张映射表,标识颜色索引号与其代表的颜色的对应关系。总共有256个索引,每个索引对应一个RGBA四字节的色彩值,而后续位图数据只需要保存每个像素点的索引,根据索引找到对应的RGBA数据值。根据索引列表大小,该部分信息总计占用 256*4=1024个字节。
4、位图数据
这部分存放的就是详细的每个像素的RGB或者RGBA数据。存放顺序从左到右、从下到上的顺序存放。 这里有两点需要注意一下:
(1)像素RGB值存放时,先存放图像最下面一行数据,然后再存放下面倒数第二行数据,所以存放顺序是倒的,这个跟正常浏览顺序不一样。
(2)每一行像素数据需要保证4字节对齐,不对齐的需要补充空字节,这个是BMP文件格式本身要求的。对于RGBA 32位色彩格式来说没有任何问题,但是对于Gray或者RGB格式的数据 ,有时一行像素需要补充一些空字节。例如:对于宽高为 322*240 像素的RGB数据,正常计算一行像素数据需要 322 * 3= 966字节,但是966不是四字节对齐,所以在存储时,每行的像素数据要保存为 968字节,最后2个字节保留。
bmp读写操作代码
//
// Bmp file structure
//
#define XBMP_FILEHDR_LEN 14
typedef struct _tag_XBmpFileHeader
{
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
}XBMPFILE_HEADER, *LPXBMPFILE_HEADER;
#define XBMP_INFOHDR_LEN 40
typedef struct _tag_XBMPINFO_HEADER
{
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
} XBMPINFO_HEADER, *LPXBMPINFO_HEADER;
#define XBMP_RGBQUAD_LEN 4
typedef struct _tag_XBmp_RGBQUAD
{
uint8_t rgbBlue;
uint8_t rgbGreen;
uint8_t rgbRed;
uint8_t rgbReserved;
} XBMP_RGBQUAD, *LPXBMP_XBMP_RGBQUAD;
int32_t XImage::LoadFromBmp(const char* bmp_file_path)
{
if (bmp_file_path == nullptr)
{
return XERR_INVALID_PARAM;
}
FILE* file_handler = fopen(bmp_file_path, "rb");
if (file_handler == nullptr)
{
XOS_ERROR(TAG_IMGENG, "<XImage.LoadFromBmp> fail to fopen(), file=%s", bmp_file_path);
return XERR_FILE_OPEN;
}
//
// 判断位图文件头结构BITMAPFILEHEADER
//
XBMPFILE_HEADER sFileHdr = { 0 };
uint8_t szBuffer[255] = { 0 };
fread(szBuffer, 1, XBMP_FILEHDR_LEN, file_handler);
sFileHdr.bfType = ((uint32_t)szBuffer[0] | (uint32_t)szBuffer[1] << 8);
if (0x4D42 != sFileHdr.bfType)
{
XOS_ERROR(TAG_IMGENG, "<XImage.LoadFromBmp> not BM flag, file=%s", bmp_file_path);
fclose(file_handler);
return XERR_UNSUPPORTED;
}
//
// 读取位图信息头结构变量,读取位图信息头进内存, 存放在变量head中
//
XBMPINFO_HEADER sInfoHdr = { 0 };
XBMP_RGBQUAD* pColorTab = XNULL;
memset(szBuffer, 0, 255);
fread(szBuffer, 1, XBMP_INFOHDR_LEN, file_handler);
sInfoHdr.biSize = ((uint32_t)szBuffer[0]) | ((uint32_t)szBuffer[1] << 8) | ((uint32_t)szBuffer[2] << 16) | ((uint32_t)szBuffer[3] << 24);
sInfoHdr.biWidth = ((uint32_t)szBuffer[4]) | ((uint32_t)szBuffer[5] << 8) | ((uint32_t)szBuffer[6] << 16) | ((uint32_t)szBuffer[7] << 24);
sInfoHdr.biHeight = ((uint32_t)szBuffer[8]) | ((uint32_t)szBuffer[9] << 8) | ((uint32_t)szBuffer[10] << 16) | ((uint32_t)szBuffer[11] << 24);
sInfoHdr.biPlanes = ((uint32_t)szBuffer[12]) | ((uint32_t)szBuffer[13] << 8);
sInfoHdr.biBitCount = ((uint32_t)szBuffer[14]) | ((uint32_t)szBuffer[15] << 8);
sInfoHdr.biCompression = ((uint32_t)szBuffer[16]) | ((uint32_t)szBuffer[17] << 8) | ((uint32_t)szBuffer[18] << 16) | ((uint32_t)szBuffer[19] << 24);
sInfoHdr.biSizeImage = ((uint32_t)szBuffer[20]) | ((uint32_t)szBuffer[21] << 8) | ((uint32_t)szBuffer[22] << 16) | ((uint32_t)szBuffer[23] << 24);
sInfoHdr.biXPelsPerMeter= ((uint32_t)szBuffer[24]) | ((uint32_t)szBuffer[25] << 8) | ((uint32_t)szBuffer[26] << 16) | ((uint32_t)szBuffer[27] << 24);
sInfoHdr.biYPelsPerMeter= ((uint32_t)szBuffer[28]) | ((uint32_t)szBuffer[29] << 8) | ((uint32_t)szBuffer[30] << 16) | ((uint32_t)szBuffer[31] << 24);
sInfoHdr.biClrUsed = ((uint32_t)szBuffer[32]) | ((uint32_t)szBuffer[33] << 8) | ((uint32_t)szBuffer[34] << 16) | ((uint32_t)szBuffer[35] << 24);
sInfoHdr.biClrImportant = ((uint32_t)szBuffer[36]) | ((uint32_t)szBuffer[37] << 8) | ((uint32_t)szBuffer[38] << 16) | ((uint32_t)szBuffer[39] << 24);
//
//获取图像宽、高、每像素所占位数等信息
//
int32_t bmp_width = sInfoHdr.biWidth;
int32_t bmp_height = sInfoHdr.biHeight;
PXL_FORMAT bmp_pxl_format = PXL_FORMAT_NONE;
if (8 == sInfoHdr.biBitCount)
{
bmp_pxl_format = PXL_FORMAT_8BIT_GRAY;
}
else if (24 == sInfoHdr.biBitCount)
{
bmp_pxl_format = PXL_FORMAT_24BIT_BGR;
}
else if (32 == sInfoHdr.biBitCount)
{
bmp_pxl_format = PXL_FORMAT_32BIT_BGRA;
}
else
{
XOS_ERROR(TAG_IMGENG, "<XImage.LoadFromBmp> unknown pixel format, biBitCount=%d, file=%s",
sInfoHdr.biBitCount, bmp_file_path);
fclose(file_handler);
return XERR_UNSUPPORTED;
}
if (8 == sInfoHdr.biBitCount) // 灰度图像有颜色表,且颜色表表项为256色
{
XOS_ERROR(TAG_IMGENG, "<XImage.LoadFromBmp> not support color 8bit, file=%s", bmp_file_path);
fclose(file_handler);
return XERR_UNSUPPORTED; // 目前不支持
}
// 分配内存空间
Allocate(bmp_pxl_format, bmp_width, bmp_height);
// 逐行读取像素数据, 注意: .bmp文件中像素数据时先存放最后一行,然后逐步到第一行, 是反向的
uint8_t* pixel_line = (uint8_t*)(image_data_ + (height_ - 1) * line_bytes_);
for (int32_t i = 0; i < height_; i++)
{
fread(pixel_line, 1, line_bytes_, file_handler);
pixel_line -= line_bytes_;
}
fclose(file_handler);
return XOK;
}
int32_t XImage::SaveToBmp(const char* save_bmp_file)
{
FILE* file_handler = fopen(save_bmp_file, "wb");
if (file_handler == nullptr)
{
XOS_ERROR(TAG_IMGENG, "<XImage.SaveToBmp> fail to fopen(), file=%s", save_bmp_file);
return XERR_FILE_OPEN;
}
// YUV图像数据直接保存原始数据
if (pxl_format_ & PXL_FORMAT_TYPE_YUV)
{
fwrite(image_data_, 1, image_bytes_, file_handler);
fclose(file_handler);
return XOK;
}
// 写入文件头信息
int32_t bit_count = pixel_bytes_ * 8;
uint16_t temp_16bit = 0x4D42;
uint32_t temp_32bit = static_cast<uint32_t>(54 + line_bytes_ * height_);
if (bit_count == 8)
temp_32bit += XBMP_RGBQUAD_LEN * 256;
else if (bit_count == 16)
temp_32bit += 16;
fwrite(&temp_16bit, 1, 2, file_handler);
fwrite(&temp_32bit, 1, 4, file_handler);
// 写入bitmap信息
temp_16bit = 0;
temp_32bit = 54;
if (bit_count == 8)
temp_32bit += XBMP_RGBQUAD_LEN * 256;
else if (bit_count == 16)
temp_32bit += 16;
fwrite(&temp_16bit, 1, 2, file_handler);
fwrite(&temp_16bit, 1, 2, file_handler);
fwrite(&temp_32bit, 1, 4, file_handler);
if (8 == bit_count)
{
XBMPINFO_HEADER bi = { 0 };
bi.biSize = 40;
bi.biWidth = width_;
bi.biHeight = height_;
bi.biBitCount = (uint16_t)bit_count;
bi.biPlanes = 1;
fwrite(&bi, 1, XBMP_INFOHDR_LEN, file_handler);
XBMP_RGBQUAD rgb[256];
for (int i = 0; i < 256; i++)
{
rgb[i].rgbBlue = (XBYTE)i;
rgb[i].rgbGreen = (XBYTE)i;
rgb[i].rgbRed = (XBYTE)i;
rgb[i].rgbReserved = 0;
}
fwrite(rgb, 1, XBMP_RGBQUAD_LEN * 256, file_handler);
}
else
{
uint32_t biSize = 40;
int32_t biWidth = width_;
int32_t biHeight = height_;
uint16_t biPlanes = 1;
uint16_t biBitCount = bit_count;
uint32_t biCompression = (bit_count == 16) ? 3 : 0;
uint32_t biSizeImage = 0;
int32_t biXPelsPerMeter = 0;
int32_t biYPelsPerMeter = 0;
uint32_t biClrUsed = 0;
uint32_t biClrImportant = 0;
fwrite(&biSize, 1, 4, file_handler);
fwrite(&biWidth, 1, 4, file_handler);
fwrite(&biHeight, 1, 4, file_handler);
fwrite(&biPlanes, 1, 2, file_handler);
fwrite(&biBitCount, 1, 2, file_handler);
fwrite(&biCompression, 1, 4, file_handler);
fwrite(&biSizeImage, 1, 4, file_handler);
fwrite(&biXPelsPerMeter, 1, 4, file_handler);
fwrite(&biYPelsPerMeter, 1, 4, file_handler);
fwrite(&biClrUsed, 1, 4, file_handler);
fwrite(&biClrImportant, 1, 4, file_handler);
if (bit_count == 16)
{
uint32_t mask[16] = { 0 };
mask[0] = 0xF800;
mask[1] = 0x7E0;
mask[2] = 0x1F;
mask[3] = 0;
fwrite(mask, 1, 16, file_handler);
}
}
uint8_t* pixel_line = image_data_ + (height_ - 1) * line_bytes_;
for (int32_t i = 0; i < height_; i++)
{
fwrite(pixel_line, 1, line_bytes_, file_handler);
pixel_line -= line_bytes_;
}
fclose(file_handler);
return XOK;
}
bmp转jpg参考代码
https://www.jb51.net/article/69779.htm
jpg转bmp代码
jpeg转bmp实现c代码__寒潭雁影的博客-CSDN博客_c语言 jpeg转bmp