当前位置: 首页 > 工具软件 > Netpbm2Gimp > 使用案例 >

图形学笔记: 因为懒所以用了一种叫Netpbm的图片格式

董子平
2023-12-01

如果说到图片格式, 大多数人都可以信手拈来很多, BMP, PNG, JPEG, GIF, TIFF等等. 然而这种叫做Netpbm的格式就显得有点陌生, 连Windows默认的系统图片浏览器都不支持这种格式. 这种一开始在Unix世界流行起来的简单图片(位图)格式, 既不支持透明像素, 也不能压缩, 更没有Exif. 它存在的目的, 大概就是为了让我们这些懒得外挂库的程序员能在半个小时内轻松造个解析图片格式的轮子.

先简单介绍一下这种图片格式. 这种图片有6个经典版本. (新的版本PAM很多地方支持不好):

TypeMagic numberFormatMagic numberFormatExtensionColors
Portable BitMapP1ASCIIP4binary.pbm0–1 (black & white)
Portable GrayMapP2ASCIIP5binary.pgm0–255 (gray scale)
Portable PixMapP3ASCIIP6binary.ppm0–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的格式:

  1. 第一字节是P, 第二个字节是一个数字N.
  2. 十进制数字分别表示宽, 高, 和最大的色域, 如255表示RGB每个分量可以取 0~255.
  3. 在这过程中如果读取到 # 则为注释, 则整行丢弃.
  4. 若N是3, 那么接下来每三个十进制整数表示一个像素(分别按顺序表示红绿蓝分量);
    若N是6, 那么接下来每三个字节表示一个像素(分别按顺序表示红绿蓝分量).

Reference

struct color 是一种颜色, 也即是一个像素.

cppstruct 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<color> buf_type;
        typedef std::vector<color>::iterator iter_type;

        // constructors
        image();
        image(size_t w, size_t h);
        image(const std::string& fn);

        // properties getters
        std::vector<color>& buffer();
        const std::vector<color>& 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

这个类的基础用法, 我写了三个简短的示例:

  1. 输出一张从黑到白渐变的图片;
  2. 输入一张图片, 将其缩小一半之后输出;
  3. 输出著名的 Mandelbrot Set.
#include <iostream>
#include <fstream>
#include <cstdint>
#include <complex>
#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<float> c((a - 550) / 250.0, (b - 300) / 250.0);
                        complex<float> 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;
}

 类似资料: