jpg图片转换bmp图片的npm模块jpg2bmp

500 阅读8分钟

A library for jpeg to bmp, with a lovely and simple API. See womenzhai.cn/project for all the documentation.

前言

这是一个将 JPG 图像转换为 BMP 图像的 Node.js 模块。因此,该模块主要的功能就是读取 JPG 图像文件,解码文件中的二进制数据,并将其转换为 BMP 格式,最后将转换后的数据写入到新的 BMP 文件中

使用方法

    const jpg2bmp = require('jpg2bmp');
    
    var bmpBuffer = jpg2bm.convertToBuffer(jpgBuffer, {bitDepth: 32})

API接口

  1. 函数将接受一个 JPG 文件的缓冲区,并返回一个 BMP 文件的缓冲区, bitDepth为图片位深度
    static convertToBuffer: (jpgBuffer: Buffer,opts?: {
        bitDepth?: number ;
      }) => Buffer;
  1. 函数将接受一个 JPG 文件的路径,并返回一个 BMP 文件的缓冲区, bitDepth为图片位深度
    static convertToBufferFromFile: (jpgPath: string,opts?: {
        bitDepth?: number ;
      }) => Buffer;
  1. 函数将接受一个JPG 文件的路径和一个 BMP文件的路径, bitDepth为图片位深度
    static convertToFile: (jpgPath: string, bmpPath: string, opts?: {
        bitDepth?: number ;
    }) => void;

原理

PG 和 BMP 是两种不同的图像文件格式,它们的编码方式和存储方式有很大的不同之处。

JPG 是一种有损压缩的图像文件格式,它使用离散余弦变换(DCT)算法将图像数据进行压缩。JPG 格式可以在不同的压缩率下存储图像,从而实现图像文件的大小和质量的平衡。因为 JPG 使用的是有损压缩,所以在压缩过程中会有一定程度的图像信息丢失。这意味着当 JPG 图像被多次编辑和保存时,每次都会导致更多的图像信息丢失,从而导致图像质量的下降。

BMP 是一种无损压缩的图像文件格式,它使用位图存储方式来存储图像数据。BMP 格式将图像数据存储为像素点的颜色值,每个像素点都有自己的颜色信息。因为 BMP 使用的是无损压缩,所以它可以保存图像的所有信息,但这也导致了 BMP 文件的大小相对较大。

在编码方式上,JPG 使用的是一种基于离散余弦变换的压缩算法,而 BMP 使用的是一种无压缩的编码方式。这也是导致 JPG 文件大小比 BMP 文件更小的一个重要原因。

另外,JPG 和 BMP 的颜色深度也有所不同。JPG 图像通常使用 24 位颜色深度(每个像素点使用 24 位二进制数表示),而 BMP 图像可以使用多种不同的颜色深度,包括 1、4、8、16、24 和 32 位等。

1. JPG 解码成rgb格式

在将 JPG 图像转换为 BMP 图像之前,需要先将 JPG 图像解码成 RGB 格式。这是因为 JPG 格式使用的是基于离散余弦变换(DCT)的压缩算法,它将原始的 RGB 图像数据转换为一组 DCT 系数,并进行压缩存储。

在进行 JPG 图像解码时,需要将 DCT 系数反变换回 RGB 图像数据。这个反变换过程会还原出原始的 RGB 图像数据,并且会将压缩过程中丢失的图像信息进行恢复。因此,将 JPG 图像解码成 RGB 格式后,就可以得到完整的图像信息,包括图像的色彩、亮度等信息。

2. 颜色转换,每行像素反序

在 BMP 图片中,颜色是 BGR 排列的,这是因为 BMP 图片的格式是 Windows 设计的,在 Windows 中,字节的低位存放在内存的低地址处,高位存放在内存的高地址处。因此,BMP 图片中颜色的存储顺序是从低到高分别是蓝、绿、红三个分量,即 BGR 格式。另外,在 BMP 图片中,每行像素的存储顺序是反序排列的。具体来说,BMP 图片中每个像素点的颜色值是按行存储的,每行的存储顺序是从左到右,但是每行的颜色值又是从下到上存储的。这种存储方式被称为“逐行扫描”,每行的颜色值存储方式被称为“行扫描”,因为这样可以减少在读取图像数据时的寻址次数,提高图像数据的读取速度。在进行 BMP 图片编码或解码时,需要根据其特定的格式来进行处理。

// 将 RGB 数据中的像素排列顺序调整为 BMP 格式

export function swapRgbBgr(data: any) {

    const swappedData = Buffer.alloc(data.length);

    for (let i = 0; i < data.length; i += 4) {

        swappedData[i] = data[i + 2];

        swappedData[i + 1] = data[i + 1];

        swappedData[i + 2] = data[i];

        swappedData[i + 3] = data[i + 3]; // 添加透明通道

    }

    return swappedData;

}

3.BMP 文件格式生成

BMP 文件格式是由文件头、位图信息头和像素数据三部分组成的。其中,文件头(File Header)包含了文件类型、文件大小、位图数据的偏移量等信息;位图信息头(Bitmap Info Header)则包含了图像的宽度、高度、颜色位数等信息;像素数据(Pixel Data)则包含了图像的实际像素值数据。

具体来说,BMP 文件格式的文件头一般包括以下几个参数:

  • 文件类型(2 bytes):用于标识 BMP 文件类型,取值为 "BM"。
  • 文件大小(4 bytes):用于表示整个 BMP 文件的大小,以字节为单位。
  • 保留字1(2 bytes):暂未使用,通常设置为 0。
  • 保留字2(2 bytes):暂未使用,通常设置为 0。
  • 位图数据的偏移量(4 bytes):用于表示 BMP 文件中像素数据的起始位置,以字节为单位。

位图信息头则包括以下参数:

  • 结构体大小(4 bytes):用于表示位图信息头结构体的大小,一般为 40 字节。
  • 图像宽度(4 bytes):用于表示 BMP 图像的宽度,以像素为单位。
  • 图像高度(4 bytes):用于表示 BMP 图像的高度,以像素为单位。
  • 颜色平面数(2 bytes):用于表示位图的颜色平面数,一般为 1。
  • 颜色位数(2 bytes):用于表示每个像素所占的位数,取值一般为 1、4、8、16、24 或 32。
  • 压缩类型(4 bytes):用于表示 BMP 图像的压缩类型,一般为 0(不压缩)。
  • 图像数据大小(4 bytes):用于表示 BMP 图像的像素数据大小,以字节为单位。
  • 水平分辨率(4 bytes):用于表示 BMP 图像的水平分辨率,以像素每米为单位。
  • 垂直分辨率(4 bytes):用于表示 BMP 图像的垂直分辨率,以像素每米为单位。
  • 颜色数(4 bytes):用于表示 BMP 图像使用的颜色数,如果该值为 0,则表示使用所有颜色。

在定义 BMP 文件格式的文件头时,需要注意使用 little-endian 或 big-endian 字节序,以确保不同操作系统的兼容性。同时,在编码或解码 BMP 文件时,需要根据文件头和位图信息头的具体参数来进行处理。

// 创建BMP文件头

    const fileSize = 54 + bytesPerRow * height;

    const bmpHeader = Buffer.alloc(14);

    bmpHeader.write('BM', 0); // 文件类型

    bmpHeader.writeUInt32LE(fileSize, 2); // 文件大小

    bmpHeader.writeUInt32LE(54, 10); // 数据偏移量

  


    // 创建BMP信息头

    const bmpInfoHeader = Buffer.alloc(40);

    bmpInfoHeader.writeUInt32LE(40, 0); // 信息头大小

    bmpInfoHeader.writeUInt32LE(width, 4); // 图像宽度

    bmpInfoHeader.writeInt32LE(height, 8); // 图像高度(注意是有符号整数)

    bmpInfoHeader.writeUInt16LE(1, 12); // 颜色平面数(必须为1)

    bmpInfoHeader.writeUInt16LE(32, 14); // 每个像素的位数(32位)

    bmpInfoHeader.writeUInt32LE(0, 16); // 压缩类型(无压缩)

    bmpInfoHeader.writeUInt32LE(bytesPerRow * height, 20); // 图像数据大小

    bmpInfoHeader.writeInt32LE(2835, 24); // 水平分辨率(像素/米)

    bmpInfoHeader.writeInt32LE(2835, 28); // 垂直分辨率(像素/米)

    bmpInfoHeader.writeUInt32LE(0, 32); // 调色板颜色数(使用默认值)

    bmpInfoHeader.writeUInt32LE(0, 36); // 重要颜色数(使用所有颜色)

4.像素数据写入

像素数据写入时,需要对于写入数据位运算做特殊数据,通常我们在写入一个32位数据时可能会遇到最高为1的情况。在JavaScript中,所有数字都是以64位双精度浮点数的形式表示的。而左移运算符(<<)是一个位运算符,它将二进制表示的数字向左移动指定的位数,右边空出的位用0填充。在进行位运算时,JavaScript会将数字转换为32位有符号整数,然后再执行位运算。当一个数字左移24位时,它的二进制表示中最高的8位(即左侧的8位)会被移到整个32位数的最高位,而右侧的24位将变为0。如果最高位为0,则结果将为一个正数;但如果最高位为1,则结果将被解释为一个负数,因为在32位有符号整数中,最高位表示符号位。所以库中做了额外处理

let oidi = (pixels[index + 2] << 16 | pixels[index + 1] << 8 | pixels[index]) + (pixels[index + 3] << 23) * 2;

            bmpBuffer.writeUInt32LE(oidi , start + x * 4);

5.数据整合

// 将BMP文件头和信息头写入文件

    return Buffer.concat([bmpHeader, bmpInfoHeader, bmpBuffer]);

License

jpg2bmp is dual-licensed. You may use it under the MIT license or the GPLv3 license.