本文最后更新于 298 天前,其中的信息可能已经有所发展或是发生改变。
前言
本博客 论操作系统是如何识别文件的 文章以及百度百科 RGB色彩模式 的介绍,可能对理解以下源码有所帮助。
源码收集于网络并修改。
生成 PNG 格式图片
新建 svpng.h
头文件[1]
/*
Copyright (C) 2017 Milo Yip. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of pngout nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*! \file
\brief svpng() is a minimalistic C function for saving RGB/RGBA image into uncompressed PNG.
\author Milo Yip
\version 0.1.1
\copyright MIT license
\sa http://github.com/miloyip/svpng
*/
#ifndef SVPNG_INC_
#define SVPNG_INC_
/*! \def SVPNG_LINKAGE
\brief User customizable linkage for svpng() function.
By default this macro is empty.
User may define this macro as static for static linkage,
and/or inline in C99/C++, etc.
*/
#ifndef SVPNG_LINKAGE
#define SVPNG_LINKAGE
#endif
/*! \def SVPNG_OUTPUT
\brief User customizable output stream.
By default, it uses C file descriptor and fputc() to output bytes.
In C++, for example, user may use std::ostream or std::vector instead.
*/
#ifndef SVPNG_OUTPUT
#include <stdio.h>
#define SVPNG_OUTPUT FILE* fp
#endif
/*! \def SVPNG_PUT
\brief Write a byte
*/
#ifndef SVPNG_PUT
#define SVPNG_PUT(u) fputc(u, fp)
#endif
/*!
\brief Save a RGB/RGBA image in PNG format.
\param SVPNG_OUTPUT Output stream (by default using file descriptor).
\param w Width of the image. (<16383)
\param h Height of the image.
\param img Image pixel data in 24-bit RGB or 32-bit RGBA format.
\param alpha Whether the image contains alpha channel.
*/
SVPNG_LINKAGE void svpng(SVPNG_OUTPUT, unsigned w, unsigned h, const unsigned char* img, int alpha) {
static const unsigned t[] = {0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
/* CRC32 Table */ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c};
unsigned a = 1, b = 0, c, p = w * (alpha ? 4 : 3) + 1, x, y, i; /* ADLER-a, ADLER-b, CRC, pitch */
#define SVPNG_U8A(ua, l) \
for (i = 0; i < l; i++) SVPNG_PUT((ua)[i]);
#define SVPNG_U32(u) \
do { \
SVPNG_PUT((u) >> 24); \
SVPNG_PUT(((u) >> 16) & 255); \
SVPNG_PUT(((u) >> 8) & 255); \
SVPNG_PUT((u)&255); \
} while (0)
#define SVPNG_U8C(u) \
do { \
SVPNG_PUT(u); \
c ^= (u); \
c = (c >> 4) ^ t[c & 15]; \
c = (c >> 4) ^ t[c & 15]; \
} while (0)
#define SVPNG_U8AC(ua, l) \
for (i = 0; i < l; i++) SVPNG_U8C((ua)[i])
#define SVPNG_U16LC(u) \
do { \
SVPNG_U8C((u)&255); \
SVPNG_U8C(((u) >> 8) & 255); \
} while (0)
#define SVPNG_U32C(u) \
do { \
SVPNG_U8C((u) >> 24); \
SVPNG_U8C(((u) >> 16) & 255); \
SVPNG_U8C(((u) >> 8) & 255); \
SVPNG_U8C((u)&255); \
} while (0)
#define SVPNG_U8ADLER(u) \
do { \
SVPNG_U8C(u); \
a = (a + (u)) % 65521; \
b = (b + a) % 65521; \
} while (0)
#define SVPNG_BEGIN(s, l) \
do { \
SVPNG_U32(l); \
c = ~0U; \
SVPNG_U8AC(s, 4); \
} while (0)
#define SVPNG_END() SVPNG_U32(~c)
SVPNG_U8A("\x89PNG\r\n\32\n", 8); /* Magic */
SVPNG_BEGIN("IHDR", 13); /* IHDR chunk { */
SVPNG_U32C(w);
SVPNG_U32C(h); /* Width & Height (8 bytes) */
SVPNG_U8C(8);
SVPNG_U8C(alpha ? 6 : 2); /* Depth=8, Color=True color with/without alpha (2 bytes) */
SVPNG_U8AC("\0\0\0", 3); /* Compression=Deflate, Filter=No, Interlace=No (3 bytes) */
SVPNG_END(); /* } */
SVPNG_BEGIN("IDAT", 2 + h * (5 + p) + 4); /* IDAT chunk { */
SVPNG_U8AC("\x78\1", 2); /* Deflate block begin (2 bytes) */
for (y = 0; y < h; y++) { /* Each horizontal line makes a block for simplicity */
SVPNG_U8C(y == h - 1); /* 1 for the last block, 0 for others (1 byte) */
SVPNG_U16LC(p);
SVPNG_U16LC(~p); /* Size of block in little endian and its 1's complement (4 bytes) */
SVPNG_U8ADLER(0); /* No filter prefix (1 byte) */
for (x = 0; x < p - 1; x++, img++)
SVPNG_U8ADLER(*img); /* Image pixel data */
}
SVPNG_U32C((b << 16) | a); /* Deflate block end with adler (4 bytes) */
SVPNG_END(); /* } */
SVPNG_BEGIN("IEND", 0);
SVPNG_END(); /* IEND chunk {} */
}
#endif /* SVPNG_INC_ */
新建 生成PNG图片.cpp
源代码文件
#include "svpng.h"
void test_rgb() {
unsigned char rgb[256 * 256 * 3], *p = rgb;
unsigned x, y;
FILE* fp = fopen("rgb.png", "wb");
for (y = 0; y < 256; y++)
for (x = 0; x < 256; x++) {
*p++ = (unsigned char)x; /* R */
*p++ = (unsigned char)y; /* G */
*p++ = 128; /* B */
}
svpng(fp, 256, 256, rgb, 0);
fclose(fp);
}
void test_rgba() {
unsigned char rgba[256 * 256 * 4], *p = rgba;
unsigned x, y;
FILE* fp = fopen("rgba.png", "wb");
for (y = 0; y < 256; y++)
for (x = 0; x < 256; x++) {
*p++ = (unsigned char)x; /* R */
*p++ = (unsigned char)y; /* G */
*p++ = 128; /* B */
*p++ = (unsigned char)((x + y) / 2); /* A */
}
svpng(fp, 256, 256, rgba, 1);
fclose(fp);
}
int main() {
test_rgb();
test_rgba();
return 0;
}
生成 JPG 格式图片
新建 svjpeg.hpp
头文件[2]
/*
*
* MIT License
*
* Copyright (c) 2017 SuperSodaSea
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef SVJPEG_HPP
#define SVJPEG_HPP
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <initializer_list>
#include <tuple>
namespace Impl {
inline std::tuple<double, double, double> rgb2ycbcr(const std::uint8_t* rgb) {
double r = rgb[0], g = rgb[1], b = rgb[2];
return std::tuple<double, double, double>(0.299 * r + 0.587 * g + 0.114 * b - 128,
-0.1687 * r - 0.3313 * g + 0.5 * b, 0.5 * r - 0.4187 * g - 0.0813 * b);
}
inline void dct(const double* input, std::int16_t* output) {
constexpr double PI = 3.1415926;
auto f = [](int x) { return x ? 1 : 1 / std::sqrt(2); };
for (int v = 0; v < 8; ++v)
for (int u = 0; u < 8; ++u) {
double sum = 0;
for (int y = 0; y < 8; ++y)
for (int x = 0; x < 8; ++x)
sum += input[y * 8 + x] * std::cos((2 * x + 1) * u * PI * 0.0625) * std::cos((2 * y + 1) * v * PI * 0.0625);
output[v * 8 + u] = std::round(sum * 0.25 * f(u) * f(v) / 4);
}
}
}
inline void svjpeg(std::FILE* file, std::uint32_t width, std::uint32_t height, std::uint8_t* data) {
static const std::uint8_t ZZ[64] = {0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63};
auto encode = [](std::int16_t x) -> std::pair<int, int> {
if (x) {
int s = floor(log2(abs(x))) + 1;
return {s, x > 0 ? x : x + (1 << s) - 1};
} else
return {0, 0};
};
auto write = [file](std::initializer_list<std::uint8_t> d) { std::fwrite(d.begin(), 1, d.size(), file); };
auto write16 = [write](std::uint16_t d) { write({std::uint8_t(d >> 8), std::uint8_t(d)}); };
auto writeMarker = [write](std::uint8_t code) { write({0xFF, code}); };
std::uint8_t buf = 0, off = 0;
auto writeBit = [write, &buf, &off](bool d) {
buf |= d << (7 - off);
if (++off == 8) {
if (buf == 0xFF)
write({0xFF, 0x00});
else
write({buf});
buf = off = 0;
}
};
auto writeBits = [writeBit](std::uint16_t d, int s) { for(int i = 0; i < s; ++i) writeBit(d & (1 << (s - i - 1))); };
auto writeHuffman = [writeBits](std::uint8_t d) { if(d == 0xFF) writeBits(0x1FE, 9); else writeBits(d, 8); };
writeMarker(0xD8); // SOI
for (std::uint8_t i = 0; i < 2; ++i) {
writeMarker(0xDB), write({0x00, 0x43, i});
for (int j = 0; j < 64; ++j) write({0x04});
} // DQT
writeMarker(0xC0); // SOF0
write({0x00, 0x11, 0x08}), write16(height), write16(width),
write({0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01});
writeMarker(0xC4);
write({0x00, 0x1F, 0x00}); // DHT
for (int i = 0; i < 16; ++i) write({std::uint8_t(i == 7 ? 12 : 0)});
for (int i = 0; i < 12; ++i) write({std::uint8_t(i)});
writeMarker(0xC4);
write({0x01, 0x13, 0x10}); // DHT
for (int i = 0; i < 16; ++i) write({std::uint8_t(i == 7 ? 255 : i == 8 ? 1
: 0)});
for (int i = 0; i < 256; ++i) write({std::uint8_t(i)});
writeMarker(0xDA);
write({0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x3F, 0x00}); // SOS
double ycbcr[3][64];
std::int16_t lastDC[3] = {}, dct1[64], dct2[64];
for (std::uint32_t ch = (height + 7) / 8, cy = 0; cy < ch; ++cy)
for (std::uint32_t cw = (width + 7) / 8, cx = 0; cx < cw; ++cx) {
for (int i = 0; i < 64; ++i) { // RGB -> YCbCr
std::uint32_t x = std::min(cx * 8 + i % 8, width - 1), y = std::min(cy * 8 + i / 8, height - 1);
std::tie(ycbcr[0][i], ycbcr[1][i], ycbcr[2][i]) = Impl::rgb2ycbcr(data + (y * width + x) * 3);
}
for (int i = 0; i < 3; ++i) {
Impl::dct(ycbcr[i], dct1); // DCT & Quantization
for (int i = 0; i < 64; ++i) dct2[i] = dct1[ZZ[i]];
std::int16_t delta = dct2[0] - lastDC[i];
lastDC[i] = dct2[0]; // DC
auto dc = encode(delta);
writeHuffman(dc.first);
writeBits(dc.second, dc.first);
int index = 1; // AC
do {
int zero = 0;
while (!dct2[index] && ++index < 64) ++zero;
if (index == 64) {
writeHuffman(0);
break;
}
while (zero >= 16) writeHuffman(0xF0), zero -= 16;
auto ac = encode(dct2[index]);
writeHuffman(ac.first | (zero << 4));
writeBits(ac.second, ac.first);
} while (++index < 64);
}
}
if (off != 0)
write({buf});
writeMarker(0xD9); // EOI
}
#endif
新建 生成JPG图片.cpp
源代码文件
#include "svjpeg.hpp"
using namespace std;
int main() {
FILE* file = fopen("test.jpg", "wb");
uint8_t data[256 * 256 * 3];
uint8_t* p = data;
for(int y = 0; y < 256; ++y) {
for(int x = 0; x < 256; ++x) {
*p++ = x;
*p++ = y;
*p++ = 128;
}
}
svjpeg(file, 256, 256, data);
fclose(file);
return 0;
}
生成 BMP 格式图片
自己实现的一个 BMP 类,全平台通用,仅支持 24 位 (RGB) 和 32 位 (RBGA) 真彩色位图。Windows 平台可以使用 Windows 系统提供的 API,这个自行百度一下。
新建 svbmp.hpp
头文件
#ifndef _SVBMP_HPP_
#define _SVBMP_HPP_
#pragma pack(1)
#include <iostream>
#define BI_RGB_ (0)
#define BI_RLE8_ (1)
#define BI_RLE4_ (2)
#define BI_BITFIELDS_ (3)
typedef unsigned short WORD_;
typedef unsigned int DWORD_;
typedef unsigned char BYTE_;
// 文件头 14 字节:short 2 个,int 4 个
typedef struct tagBmpFileHeader {
WORD_ bfType; // 标识该文件为 bmp 文件,判断文件是否为 bmp 文件,即用该值与 0x4d42 比较是否相等即可,0x4d42 = 19778 = "BM"
DWORD_ bfSize; // 位图文件大小,包括这 14 个字节
WORD_ bfReserved1; // 预保留位,暂不用
WORD_ bfReserved2; // 预保留位,暂不用
DWORD_ bfOffBits; // 像素数据偏移量,即图像数据区的起始位置
} BmpFileHeader;
// 信息头 40 字节
typedef struct tagBmpInfoHeader {
DWORD_ biSize; // 本结构的长度,为 40 个字节
DWORD_ biWidth; // 宽度
DWORD_ biHeight; // 高度
WORD_ biPlanes; // 目标设备的级别,必须是 1
WORD_ biBitCount; // 每个像素所占的位数(bit),其值必须为1(黑白图像)、4(16色图)、8(256色)、24(真彩色图),新的 BMP 格式支持 32 位色。
DWORD_ biCompression; // 压缩方式,有效的值为 BI_RGB(未经压缩)、BI_RLE8、BI_RLE4、BI_BITFILEDS
DWORD_ biSizeImage; // 图像区数据大小,即实际的位图数据占用的字节数
DWORD_ biXPelsPerMeter; // 水平分辨率,单位是像素/米,一般为0
DWORD_ biYPelsPerMeter; // 垂直分辨率,单位是像素/米,一般为0
DWORD_ biClrUsed; // 位图实际用到的颜色数,如果该值为零,则用到的颜色数为 2 的 biBitCount 次幂
DWORD_ biClrImportant; // 位图显示过程,重要的颜色数;0--所有都重要
} BmpInfoHeader;
// 调色板,实际上是一个 RGBPallete 结构的数组,数组的长度由 biClrUsed 指定
// 只有单色、16 色、256 色位图有调色板,24 位位图(真色彩)每个像素是用三个字节表示,分别对应 B(蓝)、G(绿)、R(红)三个值,文件头 + 信息头 + 调色板总长度固定为 54 字节
// 32 位位图与 24 位的区别就是每个像素 RGB 值后面多了一个 ALPHA 通道,用来控制图片的透明度
// 单色、16 色、256 色的图像数据区中的数据不是像素值,是调色板中的颜色的映射
// 数据区的第一个行像素值相当于图像的最后一行像素,因此图像坐标零点在图像左下角
typedef struct tagRGBPallete {
BYTE_ b; // 蓝色的亮度(值范围为 0-255)
BYTE_ g; // 绿色的亮度(值范围为 0-255)
BYTE_ r; // 红色的亮度(值范围为 0-255)
} RGBPallete;
typedef struct tagRGBAPallete : RGBPallete {
BYTE_ alphaReserved;
} RGBAPallete;
typedef RGBPallete RGB;
typedef RGBAPallete RGBA;
class BMP {
private:
FILE *bmpFile;
BmpFileHeader bmpFileHeader;
BmpInfoHeader bmpInfoHeader;
RGBPallete rgbPallete;
void writeData(RGB *rgb, int len);
public:
FILE *getBmpFile();
void setBmpFile(FILE *bmpFile);
int getBiBitCount();
void setBiBitCount(int biBitCount);
void setBmpInfoHeader(BmpInfoHeader &bmpInfoHeader);
void initHead(int width, int height);
void writeAll(int width, int height, RGB *rgb);
void append(RGB *rgb, int len);
};
void BMP::initHead(int width, int height) {
if (bmpFile == NULL) {
printf("The target file does not exist!");
return;
}
int horizontalPixel = (width * (bmpInfoHeader.biBitCount / 8) + bmpInfoHeader.biBitCount / 8) / 4 * 4;
bmpFileHeader.bfType = 0x4d42;
bmpFileHeader.bfSize = horizontalPixel * height + 54;
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfOffBits = 54;
bmpInfoHeader.biSize = 40;
bmpInfoHeader.biWidth = width;
bmpInfoHeader.biHeight = height;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biCompression = BI_RGB;
bmpInfoHeader.biSizeImage = horizontalPixel * height;
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 0;
bmpInfoHeader.biClrImportant = 0;
fseek(bmpFile, 0L, SEEK_SET);
std::fwrite(&bmpFileHeader, sizeof(bmpFileHeader), 1, bmpFile);
std::fwrite(&bmpInfoHeader, sizeof(bmpInfoHeader), 1, bmpFile);
}
FILE *BMP::getBmpFile() {
return bmpFile;
}
void BMP::setBmpFile(FILE *bmpFile) {
if (bmpFile == NULL) {
printf("The target file does not exist!");
return;
}
this->bmpFile = bmpFile;
}
int BMP::getBiBitCount() {
return bmpInfoHeader.biBitCount;
}
void BMP::setBiBitCount(int biBitCount) {
bmpInfoHeader.biBitCount = biBitCount;
}
void BMP::writeData(RGB *rgb, int len) {
if (getBiBitCount() == 24) {
for (int i = 0; i < len; i++) {
RGB temp {};
temp.r = rgb[i].r;
temp.g = rgb[i].g;
temp.b = rgb[i].b;
std::fwrite(&temp, sizeof(temp), 1, bmpFile);
}
} else if (getBiBitCount() == 32) {
for (int i = 0; i < len; i++) {
RGBA rgba = ((RGBA *) rgb)[i];
RGBA temp {};
temp.alphaReserved = 255;
temp.r = rgba.r * rgba.alphaReserved / 255 + temp.alphaReserved * (255 - rgba.alphaReserved) / 255;
temp.g = rgba.g * rgba.alphaReserved / 255 + temp.alphaReserved * (255 - rgba.alphaReserved) / 255;
temp.b = rgba.b * rgba.alphaReserved / 255 + temp.alphaReserved * (255 - rgba.alphaReserved) / 255;
std::fwrite(&temp, sizeof(temp), 1, bmpFile);
}
}
}
void BMP::writeAll(int width, int height, RGB *rgb) {
initHead(width, height);
writeData(rgb, width * height);
}
void BMP::append(RGB *rgb, int len) {
if (bmpFile == NULL) {
printf("The target file does not exist!");
return;
}
fseek(bmpFile, 0L, SEEK_END);
writeData(rgb, len);
}
#endif /* _SVBMP_HPP_ */
新建 生成BMP图片.cpp
源代码文件
#include <iostream>
#include "svbmp.hpp"
using namespace std;
void test_rgb() {
FILE *fp;
errno_t et = fopen_s(&fp, "rgb.bmp", "wb");
if (et != 0) {
cout << "无法打开此文件" << endl;
exit(0);
}
int width = 256, height = 256;
int size = width * height;
static RGB rgb[size] {}, *p = rgb;
for (int y = 0; y < width; y++) {
for (int x = 0; x < height; x++) {
p->r = (unsigned char) (x % 255); /* R */
p->g = (unsigned char) (y % 255); /* G */
p->b = 128; /* B */
p++;
}
}
BMP bmp = BMP();
bmp.setBiBitCount(24);
bmp.setBmpFile(fp);
bmp.writeAll(width, height, rgb);
fclose(fp);
}
void test_rgba() {
FILE *fp;
errno_t et = fopen_s(&fp, "rgba.bmp", "wb");
if (et != 0) {
cout << "无法打开此文件" << endl;
exit(0);
}
int width = 256, height = 256;
int size = width * height;
static RGBA rgba[size] {}, *p = rgba;
for (int y = 0; y < width; y++) {
for (int x = 0; x < height; x++) {
p->alphaReserved = (unsigned char) (255 * 0.5);
p->r = (unsigned char) (x % 255); /* R */
p->g = (unsigned char) (y % 255); /* G */
p->b = 128; /* B */
p++;
}
}
BMP bmp = BMP();
bmp.setBiBitCount(32);
bmp.setBmpFile(fp);
bmp.writeAll(width, height, rgba);
fclose(fp);
}
int main(int argc, char **argv) {
test_rgb();
test_rgba();
return 0;
}