Gregory Igehy

Dancing at hemisphere coordinate

Simple C codes for reading PFM (Portable FloatMap Image Format)

  • 下のようなLight Probe の画像のうち, PFM フォーマットの画像を読み込んで PPM フォーマットに変換する C のコード


  • コードは未整理かつ かなり決めうちだけど, メモとして以下に貼り付け
    // PFM を読み込む
    FILE* p_texture_file = nullptr;
    {
        const char* pfm_filename = "textures/memorial.pfm";
        fopen_s( ( & p_texture_file ), pfm_filename, "rb" );

        const bool is_success = ( p_texture_file != nullptr );
        MY_ASSERT_MSG( is_success );
    }

    f32* p_texture_buffer = nullptr;
    u32  texture_width    = 0;
    u32  texture_height   = 0;
    u32  total_texture_pixel_num = 0;
    u32  total_texture_f32_num   = 0;
    u32  total_texture_byte_size = 0;
    {
        char type_str[ 128 ];
        f32 byte_order;

        const u32 c_buffer_size = 256;
        char p_buffer[ c_buffer_size ];
        u32 line_num = 0;

        // ヘッダの読み込み
        while ( ( fgets( p_buffer, c_buffer_size, p_texture_file ) != nullptr ) )
        {
            if ( line_num < 3 )
            {
                printf( "line %d: %s", line_num, p_buffer );

                if ( line_num == 0 )
                {
                    sscanf_s( p_buffer, "%2c", ( &type_str ) );
                    type_str[ 2 ] = '\0';
                }
                else if ( line_num == 1 )
                {
                    sscanf_s( p_buffer, "%d %d",
                        ( &texture_width ), ( &texture_height ) );

                    total_texture_pixel_num = texture_width * texture_height;
                    total_texture_f32_num   = 3 * total_texture_pixel_num;
                    total_texture_byte_size = sizeof( f32 ) * total_texture_f32_num;
                }
                else if ( line_num == 2 )
                {
                    sscanf_s( p_buffer, "%f", ( &byte_order ) );
                    break;
                }
            }

            line_num++;
        }

        if ( p_texture_buffer == nullptr )
        {
            p_texture_buffer        = new f32[ 3 * total_texture_pixel_num ]();
            MY_ASSERT( p_texture_buffer != nullptr );
        }

        const size_t read_texture_byte_size =
            fread( p_texture_buffer, sizeof( f32 ), total_texture_f32_num, p_texture_file );
        const bool is_success =
            ( read_texture_byte_size == total_texture_byte_size );
        MY_ASSERT_MSG( ( ! is_success ),
            "read_texture_byte_size = %d, assumed_size = %d",
            read_texture_byte_size, total_texture_byte_size );

        // Write image to PPM file.
        {
            FILE *f;
            fopen_s( ( &f ), "test_pfm.ppm", "w" );
            fprintf( f, "P3\n%d %d\n%d\n", texture_width, texture_height, 255 );

            for ( u32 y = 0; y < texture_height; ++y )
            {
                for ( u32 x = 0; x < texture_width; ++x )
                {
                    // PFM : specified in left to right, bottom to top order
                    // PPM : A raster of Height rows, in order from top to bottom.
                    // PPM に書き出すために,  Y 反転
                    const u32 read_y      = ( texture_height - y - 1 );
                    const u32 pixel_index = read_y * texture_width + x;
                    const f32 r           = p_texture_buffer[ 3 * pixel_index ];
                    const f32 g           = p_texture_buffer[ 3 * pixel_index + 1 ];
                    const f32 b           = p_texture_buffer[ 3 * pixel_index + 2 ];

                    fprintf( f, "%d %d %d ", toInt( r ), toInt( g ), toInt( b ) );
                }
            }
        }

        printf( "header = %s\n", type_str );
        printf( "width  = %d, height = %d\n", texture_width, texture_height );
        printf( "byte_order = %f\n", byte_order );
        printf( "total_texture_pixel_num = %d, total_texture_byte_size = %d\n",
            total_texture_pixel_num, total_texture_byte_size );
        printf( "Total line_num = %d\n", line_num );

        MY_ASSERT( p_texture_buffer != nullptr );
    }
  • 追記
    • このままだと使いにくかったので, 以下は構造体にしたバージョン
    • 下は使い方と構造体 PfmTexture
    
   PfmTexture s_PfmTexture;

   // ライトプローブテクスチャの読み込み
   const char* pfm_filename = "textures/grace_probe.pfm";
   s_PfmTexture.create( pfm_filename );

   // PPM に変換
    s_PfmTexture.writePPM( "test_output.ppm" );

    // x = 32, y = 58 ピクセル目のカラーを取得
    Vec rgb;
    s_PfmTexture.readPixel( ( & rgb ), x, y );
struct PfmTexture
{
    PfmTexture() :
    mpTextureFilename( nullptr ),
    mpFile( nullptr ),
    mpTextureBuffer( nullptr ),
    mTextureWidth( 0 ),
    mTextureHeight( 0 ),
    mTotalTexturePixelNum( 0 ),
    mTotalTextureF32Num( 0 ),
    mTotalTextureByteSize( 0 ),
    mByteOrder( - 1.0f )
    {
    }

    ~PfmTexture()
    {
        destroy();
    }

    bool create( const char* filename )
    {
        {
            MY_NULL_ASSERT( filename );
            mpTextureFilename = filename;
            fopen_s( ( & mpFile ), mpTextureFilename, "rb" );
            const bool is_success = ( mpFile != nullptr );
            MY_ASSERT_MSG( is_success, "Failed to open %s.\n", mpTextureFilename );
        }

        char type_str[ 128 ];
        const u32 c_buffer_size = 256;
        char p_buffer[ c_buffer_size ];
        u32 line_num = 0;

        // ヘッダの読み込み
        while ( ( fgets( p_buffer, c_buffer_size, mpFile ) != nullptr ) )
        {
            if ( line_num < 3 )
            {
                printf( "line %d: %s", line_num, p_buffer );

                if ( line_num == 0 )
                {
                    sscanf_s( p_buffer, "%2c", ( &type_str ) );
                    type_str[ 2 ] = '\0';
                }
                else if ( line_num == 1 )
                {
                    sscanf_s( p_buffer, "%d %d",
                              ( & mTextureWidth ), ( & mTextureHeight ) );

                    mTotalTexturePixelNum = mTextureWidth * mTextureHeight;
                    mTotalTextureF32Num   = 3 * mTotalTexturePixelNum;
                    mTotalTextureByteSize = sizeof( f32 ) * mTotalTextureF32Num;
                }
                else if ( line_num == 2 )
                {
                    sscanf_s( p_buffer, "%f", ( & mByteOrder ) );
                    break;
                }
            }

            line_num++;
        }

        if ( mpTextureBuffer == nullptr )
        {
            mpTextureBuffer = new f32[ mTotalTextureF32Num ]();
            MY_ASSERT( mpTextureBuffer != nullptr );
        }

        // ピクセルデータを一気に読み込む
        const size_t read_texture_byte_size =
            fread( mpTextureBuffer, sizeof( f32 ),
                   mTotalTextureF32Num, mpFile );
        const bool is_success =
            ( read_texture_byte_size == mTotalTextureByteSize );
        MY_ASSERT_MSG( ( ! is_success ),
                       "read_texture_byte_size = %d, assumed_size = %d",
                       read_texture_byte_size, mTotalTextureByteSize );

        return true;
    }

    void destroy()
    {
        if ( mpFile != nullptr )
        {
            fclose( mpFile );
            mpFile = nullptr;
        }

        if ( mpTextureBuffer != nullptr )
        {
            delete[] mpTextureBuffer;
            mpTextureBuffer = nullptr;
        }
    }

    void readPixel( Vec* p_rgb, const u32 x, const u32 y, const bool enable_y_flip = false )
    {
        MY_NULL_ASSERT( p_rgb );

        MY_NULL_ASSERT( mpFile );
        MY_NULL_ASSERT( mpTextureBuffer );
       
        MY_ASSERT( x < mTextureWidth  );
        MY_ASSERT( y < mTextureHeight );

        const u32 read_y      = ( enable_y_flip ? ( mTextureHeight - y - 1 ) : y );
        const u32 pixel_index = read_y * mTextureWidth + x;
        const u32 array_index = 3 * pixel_index;
        ( *p_rgb ).x          = mpTextureBuffer[ array_index     ];
        ( *p_rgb ).y          = mpTextureBuffer[ array_index + 1 ];
        ( *p_rgb ).z          = mpTextureBuffer[ array_index + 2 ];
    }

    void writePPM( const char* ppm_filename )
    {
        FILE* f;
        fopen_s( ( & f ), ppm_filename, "w" );
        fprintf( f, "P3\n%d %d\n%d\n", mTextureWidth, mTextureHeight, 255 );

        for ( u32 y = 0; y < mTextureHeight; ++y )
        {
            for ( u32 x = 0; x < mTextureWidth; ++x )
            {
                // PFM : specified in left to right, bottom to top order
                // PPM : A raster of Height rows, in order from top to bottom.
                // PPM に書き出すために,  Y 反転
                Vec rgb;
                readPixel( ( & rgb ), x, y, true );

                fprintf( f, "%d %d %d ", toInt( rgb.x ), toInt( rgb.y ), toInt( rgb.z ) );
            }
        }

        fclose( f );
    }

    const char* mpTextureFilename;
    FILE*       mpFile;
    f32*        mpTextureBuffer;

    u32         mTextureWidth;
    u32         mTextureHeight;
    u32         mTotalTexturePixelNum;
    u32         mTotalTextureF32Num;
    u32         mTotalTextureByteSize;

    f32         mByteOrder;
};