圖形學筆記: 因為懶所以用了一種叫Netpbm的圖片格式

Shihira發表於2019-05-13

如果說到圖片格式, 大多數人都可以信手拈來很多, 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的格式:

  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;
}

相關文章