如果说到图片格式, 大多数人都可以信手拈来很多, BMP, PNG, JPEG, GIF, TIFF等等. 然而这种叫做Netpbm的格式就显得有点陌生, 连Windows默认的系统图片浏览器都不支持这种格式. 这种一开始在Unix世界流行起来的简单图片(位图)格式, 既不支持透明像素, 也不能压缩, 更没有Exif. 它存在的目的, 大概就是为了让我们这些懒得外挂库的程序员能在半个小时内轻松造个解析图片格式的轮子.
先简单介绍一下这种图片格式. 这种图片有6个经典版本. (新的版本PAM很多地方支持不好):
Type | Magic number | Format | Magic number | Format | Extension | Colors |
---|---|---|---|---|---|---|
Portable BitMap | P1 | ASCII | P4 | binary | .pbm | 0–1 (black & white) |
Portable GrayMap | P2 | ASCII | P5 | binary | .pgm | 0–255 (gray scale) |
Portable PixMap | P3 | ASCII | P6 | binary | .ppm | 0–255 (RGB) |
然而 P1, P2, P4, P5 因为只支持单色和灰度, 所以除了某些特殊场合, 用处不大. 现在详细说说P3和P6. P3是支持RGB的ASCII格式的, P6是支持RGB的二进制格式的. 单说无用, 看看图片文件里面的内容就知道区别了:
P3
# RGB+Ascii. 这个3x2的图片的结果是:
# 红 绿 蓝
# 黄 紫 青
3 2
255
255 0 0 0 255 0 0 0 255
255 255 0 255 0 255 0 255 255
相同的内容, P6就显得没那么可读. 我们只能用xxd
來观察它的内容(图片是由GIMP生成的):
0000000: 5036 0a23 2043 5245 4154 4f52 3a20 4749 P6.# CREATOR: GI
0000010: 4d50 2050 4e4d 2046 696c 7465 7220 5665 MP PNM Filter Ve
0000020: 7273 696f 6e20 312e 310a 3320 320a 3235 rsion 1.1.3 2.25
0000030: 350a ff00 0000 ff00 0000 ffff ff00 ff00 5...............
0000040: ff00 ffff ....
由这些文件内容我们可以知道Netpbm的格式:
- 第一字节是P, 第二个字节是一个数字N.
- 十进制数字分别表示宽, 高, 和最大的色域, 如255表示RGB每个分量可以取 0~255.
- 在这过程中如果读取到 # 则为注释, 则整行丢弃.
- 若N是3, 那么接下来每三个十进制整数表示一个像素(分别按顺序表示红绿蓝分量);
若N是6, 那么接下来每三个字节表示一个像素(分别按顺序表示红绿蓝分量).
Reference
struct color
是一种颜色, 也即是一个像素.
cpp
struct color { uint8_t r; uint8_t g; uint8_t b; uint8_t a; // constructor color(uint8_t red = 255, uint8_t green = 255, uint8_t blue = 255, uint8_t alpha = 255); color(uint32_t pxl); // 32-bit unsigned integer operations color& operator=(uint32_t pxl); uint32_t value(); };
class image
提供了ppm文件的读取和写入, 还有像素操作.
class image {
public:
typedef std::vector buf_type;
typedef std::vector::iterator iter_type;
// constructors
image();
image(size_t w, size_t h);
image(const std::string& fn);
// properties getters
std::vector& buffer();
const std::vector& buffer() const;
size_t width() const;
size_t height() const;
size_t version() const;
void version(int v);
// pixel operation
color& operator()(size_t x, size_t y);
const color& operator()(size_t x, size_t y) const;
color& pixel(size_t x, size_t y);
const color& pixel(size_t x, size_t y) const;
// erase all content and create new image
void assign(const image& other);
void assign(size_t w, size_t h);
// load from and dump to file stream
void load(std::istream& fin);
void dump(std::ostream& fout) const;
}
std::istream& operator>>(std::istream& si, image& img);
std::ostream& operator<<(std::ostream& so, const image& img);
Examples
这个类的基础用法, 我写了三个简短的示例:
- 输出一张从黑到白渐变的图片;
- 输入一张图片, 将其缩小一半之后输出;
- 输出著名的 Mandelbrot Set.
#include
#include
#include
#include
#include "image.h"
using namespace std;
int main()
{
ofstream fout_1("image-test.out.1.ppm");
ofstream fout_2("image-test.out.2.ppm");
ofstream fout_3("image-test.out.3.ppm");
ifstream fin_2("image-test.in.2.ppm");
////////////////////////////////////////////////////////////////////////
// output a gradient from black to white
image out1(256, 256);
image in2;
image::buf_type& buf = out1.buffer();
for(size_t i = 0; i < buf.size(); i++)
buf[i] = color(i/256, i/256, i/256);
fout_1 << out1;
////////////////////////////////////////////////////////////////////////
// shrink the original image to its half
fin_2 >> in2;
image out2(in2.width() / 2, in2.height() / 2);
for(size_t i = 1; i < in2.width(); i += 2) {
for(size_t j = 1; j < in2.height(); j += 2) {
uint8_t r =(
in2.pixel(i-1, j-1).r +
in2.pixel(i , j-1).r +
in2.pixel(i-1, j ).r +
in2.pixel(i , j ).r) / 4;
uint8_t g =(
in2.pixel(i-1, j-1).g +
in2.pixel(i , j-1).g +
in2.pixel(i-1, j ).g +
in2.pixel(i , j ).g) / 4;
uint8_t b =(
in2.pixel(i-1, j-1).b +
in2.pixel(i , j-1).b +
in2.pixel(i-1, j ).b +
in2.pixel(i , j ).b) / 4;
out2(i/2, j/2) = {r, g, b, 0xff};
}
}
fout_2 << out2;
////////////////////////////////////////////////////////////////////////
// mandelbrot set
image out3(800, 600);
for(float a = 0; a < out3.width(); a ++) {
for(float b = 0; b < out3.height(); b ++) {
complex c((a - 550) / 250.0, (b - 300) / 250.0);
complex z(0, 0);
int r, max_r = 30;
for(r = 0; r < max_r && abs(z) < 2; r++) {
z = z * z + c;
}
if(r == max_r) out3(a, b) = 0xff000000;
else {
r = 256.0 - 256.0 / max_r * r;
out3(a, b) = color(r, r, r);
}
}
}
fout_3 << out3;
return 0;
}