グラフィックノート:怠け者なのでNetpbmという画像フォーマットを使いました

6568 ワード

画像フォーマットといえば、BMP、PNG、JPEG、GIF、TIFFなど、多くの人が手当たり次第に手に入れることができます.しかし、このNetpbmというフォーマットは少し見慣れていないように見え、Windowsのデフォルトのシステム画像ブラウザでもサポートされていない.このようなUnix世界で最初に流行する単純な画像(ビットマップ)フォーマットは、透明画素も圧縮もExifもサポートされていない.ライブラリを外に出すのがおっくうなプログラマーが30分以内に画像フォーマットを解析する車輪を簡単に作るためだろう.
まず、この画像のフォーマットを簡単に紹介します.この画像には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)
しかし、P 1,P 2,P 4,P 5は単色と階調のみをサポートするため、特定の場合を除いてはあまり役に立たない.P 3とP 6について詳しくお話しします.P 3はRGBに対応するASCII形式である、P 6は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

同じ内容で、P 6はそんなに読めないように見えます.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、第2バイトはN.
  • の数字である.
  • の10進数はそれぞれ幅、高さ、最大の色域を表す、例えば255がRGBを表す成分ごとに0~255をとることができる.
  • は、この過程において、#が読み出すとコメントとなる、行全体が破棄される.
  • Nが3である場合、次に3つの10進数整数ごとに1つの画素(それぞれ赤緑青成分を順番に表す)を表す.Nが6である、次に3バイト毎に1画素(それぞれ赤緑青成分を順番に示す)を表す.

  • 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 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<

    Examples
    このクラスの基本的な使い方について、3つの簡単な例を書きました.
  • 黒から白へのグラデーションの画像を出力します.
  • 画像を入力し、半分に縮小して出力します.
  • 有名な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;
    }