TIFF文件解析

  TIF文件是一种标签标记文件,为什么这么说呢?它的文件的构成方式就是根据Tag来标记的。同时还可以记录地理信息,坐标、投影等信息,非常方便在GIS领域使用。网上关于TIFF文件的介绍也是不少,今天我主要是把我之前使用C++解析TIF文件的过程记录下来。
1.定义TIFF文件的结构体

//TIFF文件的数据结构
typedef struct 
{
    FILE* pfile;                     //tiff文件
    TIFF_UINT16_T tiff_byte_order;   //字节序   
    TIFF_UINT16_T tiff_version;      //文件版本
    TIFF_UINT32_T tiff_dir_off;      //DE位置
    long tif_width;                  //宽度
    long tif_height;                 //高度
    long compression;                //压缩 1:不压缩
    long photo_metric_inter;         //图像的色彩模式 2:RGB  3:索引图片
    long bit_per_samples ;           //像素的分量值
    long samples_per_pixel;          //每个象素的通道数
    long rows_per_strip;             //每个扫描行的图像数据行数
    long bcount_strip_offset;        //多少条扫描行
    long planar_config;              //图像数据存储方式 1 - RGBRGBRGB 2 - RRRGGGBBB
    TIFF_UINT32_T* strip_line;       //扫描行的起始位置
    long bcount_strip_byte_counts;   //
    TIFF_UINT32_T* strip_byte_counts;//每条扫描行的字节数目
    TIFF_UINT16_T* color_map;        //颜色表
    Tile tile;                       //瓦片数据
    GeoTiff geo_tiff;                //GeoTiff文件头
}TiffFile;

2.定义在这个过程中使用到的宏

#define TIFF void*


#define TIFF_VERSION_CLASSIC 42
#define TIFF_BIGENDIAN      0x4d4d
#define TIFF_LITTLEENDIAN   0x4949

#define PI 3.14159265358979323846

#define SIZEOF_INT 4

/* Signed 8-bit type */
#define TIFF_INT8_T signed char

/* Unsigned 8-bit type */
#define TIFF_UINT8_T unsigned char

/* Signed 16-bit type */
#define TIFF_INT16_T signed short

/* Unsigned 16-bit type */
#define TIFF_UINT16_T unsigned short

/* Signed 32-bit type formatter */
#define TIFF_INT32_FORMAT "%d"

/* Signed 32-bit type */
#define TIFF_INT32_T signed int

/* Unsigned 32-bit type */
#define TIFF_UINT32_T unsigned int


/* Signed 64-bit type */
#define TIFF_INT64_T signed __int64

/* Unsigned 64-bit type formatter */
#define TIFF_UINT64_FORMAT "%I64u"

/* Unsigned 64-bit type */
#define TIFF_UINT64_T unsigned __int64

/* Signed size type */
#if defined(_WIN64)
#define TIFF_SSIZE_T signed __int64
#else
#define TIFF_SSIZE_T signed int
#endif

/* Signed size type formatter */
#if defined(_WIN64)
#define TIFF_SSIZE_FORMAT "%I64d"
#else
#define TIFF_SSIZE_FORMAT "%ld"
#endif

/* Pointer difference type */
#define TIFF_PTRDIFF_T long

#define TIFF_ERROR_PATH_SUCCESS    0 
#define TIFF_ERROR_PATH_NULL       -1
#define TIFF_ERROR_OPEN_FAILED     -2
#define TIFF_ERROR_BYTE_ORDER      -3
#define TIFF_ERROR_TIFF_VER        -4

如果里面使用到地理信息还会使用到以下定义:

#define TIFF_GEO_GeoTagPixelScale   33550
#define TIFF_GEO_GeoTagTiePoint     33922
#define TIFF_GEO_GeoTagTransMatrix  34264
#define TIFF_GEO_GeoTagDirectory    34735
#define TIFF_GEO_GeoTagDoubleParams 34736
#define TIFF_GEO_GeoTagASCIIParams  34737

3.首先打开TIFF文件

int tif_open( const char* tiff_path , TiffFile* tf)
{
    if ( tiff_path == NULL)
    {
        return -1;
    }

    //打开文件
    FILE* ptiff = fopen( tiff_path , "rb" );
    if ( ptiff == NULL )
    {
        return -2;
    }
    tf->pfile = ptiff;

    fseek( ptiff ,0 ,SEEK_CUR );

    //得到字节序
    TIFF_UINT16_T byte_order;
    fread( &byte_order,1 , 2,ptiff );

    tf->tiff_byte_order = byte_order;

    if ( !((byte_order ==TIFF_BIGENDIAN) || (byte_order ==TIFF_LITTLEENDIAN)) )
    {
        fclose( ptiff );
        return -3;
    }

    TIFF_UINT8_T s[2]={0};


    //得到TIFF版本
    fread( s ,2 , 1, ptiff );
    TIFF_INT16_T byte_ver = sget2(s,byte_order);

    tf->tiff_version = byte_ver;

    if ( TIFF_VERSION_CLASSIC !=byte_ver )
    {
        fclose( ptiff );
        return -4;
    }

    //得到IFD的文件偏移量
    TIFF_UINT32_T IFDoffset = get4( ptiff , byte_order );

    tf->tiff_dir_off = IFDoffset;


    fseek( ptiff , IFDoffset-8 ,SEEK_CUR );

    //得到IFD的数量
    TIFF_UINT16_T IFDnum = get2( ptiff , byte_order );

    int fs = fseek( ptiff ,2+IFDoffset , SEEK_SET );

    TIFF_UINT16_T tdir_tag = 0 , tdir_type = 0 ;
    TIFF_UINT32_T tdir_count = 0 , toff_long = 0 ;
    for (TIFF_UINT16_T i=0x0000;i< IFDnum ;i++)
    {
        TIFF_UINT32_T file_offset = 2+IFDoffset+12*i;
        int fs = fseek( ptiff , 2+IFDoffset+12*i , SEEK_SET );

        tdir_tag = get2( ptiff , byte_order );

        if ( tdir_tag == 0x100 )//width
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );

            if ( ( tdir_type == 3 || tdir_type == 4 ) && byte_order == 0x4d4d/*Motor*/ && tdir_count == 1 )
            {
                toff_long = (TIFF_UINT32_T)get2( ptiff , byte_order );
            }
            else
            {
                toff_long = get4( ptiff , byte_order );
            }


            tf ->tif_width = toff_long ;
            continue;
        }
        else if ( tdir_tag == 0x101 )//height
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );


            if ( ( tdir_type == 3 || tdir_type == 4 ) && byte_order == 0x4d4d/*Motor*/ && tdir_count == 1 )
            {
                toff_long = (TIFF_UINT32_T)get2( ptiff , byte_order );
            }
            else
            {
                toff_long = get4( ptiff , byte_order );
            }

            tf ->tif_height = toff_long ;
            continue;
        }
        else if ( tdir_tag == 0x102 )//BitsPerSample
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            if ( tdir_count == 1 )
            {
                if ( ( tdir_type == 3 || tdir_type == 4 ) && byte_order == 0x4d4d/*Motor*/ && tdir_count == 1 )
                {
                    toff_long = (TIFF_UINT32_T)get2( ptiff , byte_order );
                }
                else
                {
                    toff_long = get4( ptiff , byte_order );
                }
                tf -> bit_per_samples = toff_long ;
            }
            else
            {
                toff_long = get4( ptiff , byte_order );
                TIFF_UINT16_T* sl = (TIFF_UINT16_T*)get_byte_mem( ptiff , toff_long , tdir_count*sizeof(TIFF_UINT16_T) );
                TIFF_UINT16_T* s2 = sl;
                for ( int i = 0 ; i < (int)tdir_count ; i++ )
                {
                    TIFF_UINT32_T s16 = sget2((TIFF_UINT8_T*)(s2 + i) , tf->tiff_byte_order );
                    tf -> bit_per_samples += s16 ;
                }
            }


            continue;
        }
        else if ( tdir_tag == 0x103 )//Compression
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            if ( ( tdir_type == 3 || tdir_type == 4 ) && byte_order == 0x4d4d/*Motor*/ && tdir_count == 1 )
            {
                toff_long = (TIFF_UINT32_T)get2( ptiff , byte_order );
            }
            else
            {
                toff_long = get4( ptiff , byte_order );
            }
            tf ->compression = toff_long ;
            continue;
        }
        else if ( tdir_tag == 0x106 )//PhotometricInterpretation
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            if ( ( tdir_type == 3 || tdir_type == 4 ) && byte_order == 0x4d4d/*Motor*/ && tdir_count == 1 )
            {
                toff_long = (TIFF_UINT32_T)get2( ptiff , byte_order );
            }
            else
            {
                toff_long = get4( ptiff , byte_order );
            }
            tf ->photo_metric_inter = toff_long ;
            continue;
        }
        else if ( tdir_tag == 0x111 )//StripOffsets
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            tf ->bcount_strip_offset = tdir_count ;
            if ( tdir_count > 1 )
            {
                TIFF_UINT32_T* sl = (TIFF_UINT32_T*)get_byte_mem( ptiff , toff_long , tdir_count * sizeof(TIFF_UINT32_T) );

                TIFF_UINT32_T* sl2 = sl;

                //字节序的转换
                for ( int i = 0 ; i < (int)tdir_count ; i++ )
                {
                    TIFF_UINT32_T s32 = sget4((TIFF_UINT8_T*)sl2 , tf->tiff_byte_order );
                    memcpy( sl2 , &s32 , 4 );
                    sl2+=1;
                }
                tf ->strip_line = sl ;
            }
            else
            {
                tf ->bcount_strip_offset = 1;
                tf ->strip_line = new TIFF_UINT32_T;
                *(tf ->strip_line) = toff_long;
            }
            continue;
        }
        else if ( tdir_tag == 0x115 )//SamplesPerPixel
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            if ( ( tdir_type == 3 || tdir_type == 4 ) && byte_order == 0x4d4d/*Motor*/ && tdir_count == 1 )
            {
                toff_long = (TIFF_UINT32_T)get2( ptiff , byte_order );
            }
            else
            {
                toff_long = get4( ptiff , byte_order );
            }
            tf ->samples_per_pixel = toff_long ;
            continue;
        }
        else if ( tdir_tag == 0x116 )//RowsPerStrip
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            if ( ( tdir_type == 3 || tdir_type == 4 ) && byte_order == 0x4d4d/*Motor*/ && tdir_count == 1 )
            {
                toff_long = (TIFF_UINT32_T)get2( ptiff , byte_order );
            }
            else
            {
                toff_long = get4( ptiff , byte_order );
            }
            tf ->rows_per_strip = toff_long;
            continue;
        }
        else if ( tdir_tag == 0x117 )//StripByteCounts
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            tf ->bcount_strip_byte_counts = tdir_count ;

            if ( tf ->bcount_strip_byte_counts > 1 )
            {
                TIFF_UINT32_T* sl = (TIFF_UINT32_T*)get_byte_mem( ptiff , toff_long , tdir_count*sizeof(TIFF_UINT32_T) );

                TIFF_UINT32_T* sl2 = sl;

                //字节序的转换
                for ( int i = 0 ; i < (int)tdir_count ; i++ )
                {
                    TIFF_UINT32_T s32 = sget4((TIFF_UINT8_T*)sl2 , tf->tiff_byte_order );
                    memcpy( sl2 , &s32 , 4 );
                    sl2+=1;
                }
                tf ->strip_byte_counts = sl ;
            }
            else
            {
                tf ->bcount_strip_byte_counts = 1;
                tf ->strip_byte_counts = new TIFF_UINT32_T;
                *(tf ->strip_byte_counts) = toff_long;
            }
            continue;
        }
        else if ( tdir_tag == 0x11c )//Planar Configuration
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );

            if ( ( tdir_type == 3 || tdir_type == 4 ) && byte_order == 0x4d4d/*Motor*/ && tdir_count == 1 )
            {
                toff_long = (TIFF_UINT32_T)get2( ptiff , byte_order );
            }
            else
            {
                toff_long = get4( ptiff , byte_order );
            }

            tf->planar_config = toff_long ;
            continue;
        }
        else if ( tdir_tag== 0x140 )//Color map
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            int pow1 = 0;
            if ( tf ->bit_per_samples == 1 )
            {
                pow1 = 1<<1;
            }
            else if ( tf ->bit_per_samples == 4 )
            {
                pow1 = 1<<4;
            }
            else if ( tf ->bit_per_samples == 8 )
            {
                pow1 = 1<<8;
            }

            tf ->color_map = (TIFF_UINT16_T*)get_byte_mem( ptiff , toff_long , 3*pow1*2 );

        }
        else if ( tdir_tag== 0x142 )//tile width
        {
            tf ->tile.is_tile = true ;
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            tf ->tile.tile_width = toff_long ;
            continue;
        }
        else if ( tdir_tag== 0x143 )//tile height
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            tf ->tile.tile_height = toff_long ;
            continue;
        }
        else if ( tdir_tag== 0x144 )//tile offset
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            tf ->tile.tile_offset_count = tdir_count ;

            if ( tdir_count > 1 )
            {
                tf ->tile.tile_offset_list = (TIFF_UINT32_T*)get_byte_mem( ptiff , toff_long , tdir_count*sizeof(TIFF_UINT32_T) );
            }

            continue;
        }
        else if ( tdir_tag== 0x145 )//tile byte count
        {
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            tf ->tile.tile_byte_num_count = tdir_count ;

            if ( tdir_count > 1 )
            {
                tf ->tile.tile_byte_num_list = (TIFF_UINT32_T*)get_byte_mem( ptiff , toff_long , tdir_count*sizeof(TIFF_UINT32_T) );
            }

            continue;
        }
        else if ( tdir_tag == 33550 ) //ModelPixelScaleTag
        {
            tf -> geo_tiff.is_geotiff = true ;
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );
            if ( tdir_count == 3 && tdir_type == 12 )
            {
                TIFF_UINT8_T* ps = (TIFF_UINT8_T*)get_byte_mem( ptiff , toff_long , tdir_count*8*sizeof(TIFF_UINT8_T) );
                tf -> geo_tiff.pixel_scale.scaleX = long_to_double( sget8( ps , byte_order)) ;
                tf -> geo_tiff.pixel_scale.scaleY = long_to_double( sget8( ps + 8 , byte_order)) ;
                tf -> geo_tiff.pixel_scale.scaleZ = long_to_double( sget8( ps + 16 , byte_order)) ;

                delete[] ps;
                ps = NULL;
            }
            continue;
        }
        else if ( tdir_tag == 33922 ) //ModelTiePointTag
        {
            tf -> geo_tiff.is_geotiff = true ;
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            if ( tdir_type == 12 && tdir_count >= 6 )
            {
                TIFF_UINT8_T* ps = (TIFF_UINT8_T*)get_byte_mem( ptiff , toff_long , tdir_count*8*sizeof(TIFF_UINT8_T) );

                tf -> geo_tiff.tie_point.SrcX = long_to_double( sget8( ps , byte_order)) ;
                tf -> geo_tiff.tie_point.SrcY = long_to_double( sget8( ps +8 , byte_order)) ;
                tf -> geo_tiff.tie_point.SrcZ = long_to_double( sget8( ps + 16 , byte_order)) ;

                tf -> geo_tiff.tie_point.DecX = long_to_double( sget8( ps + 24, byte_order)) ;
                tf -> geo_tiff.tie_point.DecY = long_to_double( sget8( ps + 32 , byte_order)) ;
                tf -> geo_tiff.tie_point.DecZ = long_to_double( sget8( ps + 40 , byte_order)) ;

                delete[] ps;
                ps = NULL;
            }

            continue;
        }
        else if ( tdir_tag == 34735 ) //GeoKeyDirectoryTag
        {
            tf -> geo_tiff.is_geotiff = true ;
            tf -> geo_tiff.is_geokeys = true ;
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            tf -> geo_tiff.geo_tag_directory = (TIFF_UINT16_T*)get_byte_mem( ptiff , toff_long , tdir_count*sizeof(TIFF_UINT16_T) );
            continue;
        }
        else if ( tdir_tag == 34736 ) //GeoDoubleParamsTag
        {
            tf -> geo_tiff.is_geotiff = true ;
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );

            tf -> geo_tiff.double_param_count = tdir_count ;

            tf -> geo_tiff.geo_double = (double*)get_byte_mem( ptiff , toff_long , tdir_count*sizeof(double) );
            continue;
        }
        else if ( tdir_tag == 34737 ) //GeoAsciiParamsTag
        {
            tf -> geo_tiff.is_geotiff = true ;
            tdir_type = get2( ptiff , byte_order );
            tdir_count = get4( ptiff , byte_order );
            toff_long = get4( ptiff , byte_order );
            tf -> geo_tiff.ascii_param_count = tdir_count ;

            tf -> geo_tiff.geo_ascii = (char*)get_byte_mem( ptiff , toff_long , tdir_count*sizeof(char) );

            break;
        }
    }
    return 0;
}

4.TIFF文件在存储的图形数据有两种存储方式:一种是线性存储的,一种是块状存储的。在读取不同的数据结构时也是有不同的。
读取线性数据:

int read_tiff_file( int view_w ,int view_h , double sca , TiffFile* tiff_file , TIFF_UINT8_T* pbuf )
{
    double sc = 1 / sca ;//目标文件相对于源文件放大的倍数

    FILE *pfile = tiff_file->pfile;
    int  samples_per_pixel = tiff_file->samples_per_pixel;
    TIFF_UINT32_T* line_start = tiff_file->strip_line ;
    int SrcWidth = tiff_file->tif_width;

    for ( int i = view_h-1; i >=0 ; i-- )//上下颠倒
    {
        //选取最邻近的点
        int tSrcH = (int)( sc * i + 0.5);
        for ( int j = 0; j < view_w; j++)
        {
            int tSrcW = (int)( sc * j + 0.5);    
            fseek ( pfile , line_start[tSrcH] + tSrcW * samples_per_pixel  , SEEK_SET );
            fread( pbuf ,samples_per_pixel  ,1 , pfile );
            TIFF_UINT8_T u;
            u = pbuf[0];       //************//
            pbuf[0] = pbuf[2]; // BGR -> RGB //
            pbuf[2] = u;       //************//
            pbuf += 4/*samples_per_pixel*/;  
        }
    } 

    return 0;
}

读取块状数据:

int read_tile_tiff_file( int view_w ,int view_h , double sca , TiffFile* tiff_file , TIFF_UINT8_T* pbuf )
{
    FILE *pfile = tiff_file->pfile;
    int  samples_per_pixel = tiff_file->samples_per_pixel;
    TIFF_UINT32_T* line_start = tiff_file->strip_line ;
    int SrcWidth = tiff_file->tif_width;

    double sc = 1 / sca ;//目标文件相对于源文件放大的倍数
    TIFF_UINT32_T*tile_offset = tiff_file->tile.tile_offset_list;   //偏移量列表
    int pos_h = 0 ,pos_w = 0;
    int table_w = (int)( tiff_file->tif_width/ tiff_file->tile.tile_width);
    if ( tiff_file->tif_width% tiff_file->tile.tile_width > 0 )
    {
        table_w ++ ;
    }
    int table_h = (int)( tiff_file->tif_height/ tiff_file->tile.tile_height)+1;

    if (  tiff_file->tif_height% tiff_file->tile.tile_height > 0 )
    {
        table_h ++ ;
    }

    int tile_H = tiff_file->tile.tile_height ;
    int tile_W = tiff_file->tile.tile_width ;
    //for ( int i = 0 ; i< desH ; i ++ )
    for ( int i = view_h-1 ; i >= 0 ; i -- )
    {
        int tSrcH = (int)( sc * i + 0.5);
        pos_h = tSrcH / tile_H;
        for ( int j = 0 ; j < view_w ; j ++ )
        {
            int tSrcW = (int)( sc * j + 0.5);    
            pos_w  = tSrcW / tile_W;
            TIFF_UINT32_T file_offset_start = tile_offset[ pos_h*table_w + pos_w ];

            to_client( tSrcW , tSrcH , tile_W , tile_H );

            fseek ( pfile , file_offset_start+(tSrcH*tile_W+ tSrcW)*samples_per_pixel, SEEK_SET );
            fread( pbuf ,samples_per_pixel  ,1 , pfile );
            TIFF_UINT8_T u;
            u = pbuf[0];       //************//
            pbuf[0] = pbuf[2]; // BGR -> RGB //
            pbuf[2] = u;       //************//
            pbuf += 4/*samples_per_pixel*/;  
        }
    }
    return 0;
}

5.如果TIFF中包含地理信息,还需要把地理信息取出来

//通过像素坐标的范围确定地理坐标的范围
void geo_coord( TiffFile* tf , geoRECT pixel_rect , geoRECT&geoRect )
{
    geoRect.left = tf->geo_tiff.tie_point.DecX + ( pixel_rect.left - tf->geo_tiff.tie_point.SrcX ) * tf->geo_tiff.pixel_scale.scaleX ;
    geoRect.top = tf->geo_tiff.tie_point.DecY + ( pixel_rect.top - tf->geo_tiff.tie_point.SrcY ) * tf->geo_tiff.pixel_scale.scaleY ;
    geoRect.right = tf->geo_tiff.tie_point.DecX + ( pixel_rect.right - tf->geo_tiff.tie_point.SrcX ) * tf->geo_tiff.pixel_scale.scaleX ;
    geoRect.botton = tf->geo_tiff.tie_point.DecY - ( pixel_rect.botton - tf->geo_tiff.tie_point.SrcY ) * tf->geo_tiff.pixel_scale.scaleY ;
}

6.最后,如果是Google投影,则不需要proj4来转换,直接使用以下函数就可以实现地理坐标新(WGS84)和Google投影之间的坐标转换

#define PI 3.14159265358979323846

void web_mercator_2_lonlat( coordPairs&ml )
{
    ml.x = ml.x / 20037508.3427892 * 180;
    double y = ml.y / 20037508.3427892 * 180;
    ml.y = 180 / PI * ( 2 * atan ( exp ( y * PI / 180 ) ) - PI / 2 );
}

void lonlat_2_web_mercator( coordPairs&ml )
{
    ml.x = ml.x * 20037508.3427892 / 180;
    double y = log ( tan ( ( 90 + ml.y ) * PI / 360 ) ) / ( PI / 180 );
    ml.y = y * 20037508.3427892 / 180;
}

好了,以上是读取TIFF文件的主要内容
源码下载

  • 5
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wb175208

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值