数字图像处理-bmp与jpeg格式互相转换

原理

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

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值