png圖片解碼


PNG。可移植網絡圖形格式(Portable Network Graphic Format,PNG)名稱來源於非官方的“PNG’s Not GIF”,是一種位圖文件(bitmap file)存儲格式。PNG用來存儲灰度圖像時,灰度圖像的深度可多到16位,存儲彩色圖像時,彩色圖像的深度可多到48位,並且還可存儲多到16位的α通道數據。

PNG格式有8位、24位、32位三種形式。當中8位PNG支持兩種不同的透明形式(索引透明和alpha透明),24位PNG不支持透明,32位PNG在24位基礎上添加了8位透明通道,因此可展現256級透明程度。
PNG8和PNG24后面的數字則是代表這樣的PNG格式最多能夠索引和存儲的顏色值。”8″代表2的8次方也就是256色。而24則代表2的24次方大概有1600多萬色。

格式 最高支持色彩通道 索引色編輯支持 透明支持
PNG8 256索引色 支持 支持設定特定索引色為透明色(布爾透明)
支持為索引色附加8位透明度(256階alpha透明)
PNG24 約1600萬色 不支持 不支持
PNG32 約1600萬色 不支持 支持8位透明度(256階alpha透明)

PNG文件格式保留GIF文件格式的下列特性:

  • 使用彩色查找表或者叫做調色板可支持256種顏色的彩色圖像;
  • 流式讀/寫性能(streamability):圖像文件格式同意連續讀出和寫入圖像數據。這個特性非常適合於在通信過程中生成和顯示圖像;
  • 逐次逼近顯示(progressive display):這樣的特性可使在通信鏈路上傳輸圖像文件的同一時候就在終端上顯示圖像,把整個輪廓顯示出來之后逐步顯示圖像的細節,也就是先用低分辨率顯示圖像,然后逐步提高它的分辨率;
  • 透明性(transparency):這個性能可使圖像中某些部分不顯示出來,用來創建一些有特色的圖像。
  • 輔助信息(ancillary information):這個特性可用來在圖像文件里存儲一些文本凝視信息。
  • 獨立於計算機軟硬件環境;
  • 使用無損壓縮。

PNG文件格式中要添加下列GIF文件格式所沒有的特性:

  • 每一個像素為48位的真彩色圖像;
  • 每一個像素為16位的灰度圖像;
  • 可為灰度圖和真彩色圖加入α通道;
  • 加入圖像的γ信息;
  • 使用循環冗余碼(cyclic redundancy code,CRC)檢測損害的文件;
  • 加快圖像顯示的逐次逼近顯示方式;
  • 標准的讀/寫工具包;
  • 可在一個文件里存儲多幅圖像。

文件結構

PNG圖像格式文件(或者稱為數據流)由一個8字節的PNG文件署名(PNG file signature)域和依照特定結構組織的3個以上的數據塊(chunk)組成。

PNG定義了兩種類型的數據塊。一種是稱為重要數據塊(critical chunk),這是標准的數據塊,還有一種叫做輔助數據塊(ancillary chunks),這是可選的數據塊。重要數據塊定義了4個標准數據塊,每一個PNG文件都必須包括它們。PNG讀寫軟件也都必需要支持這些數據塊。盡管PNG文件規范沒有要求PNG編譯碼器對可選數據塊進行編碼和譯碼,但規范提倡支持可選數據塊。

十進制數 137 80 78 71 13 10 26 10
十六進制數 89 50 4e 47 0d 0a 1a 0a

當中第一個字節0x89超出了ASCII字符的范圍,這是為了避免某些軟件將PNG文件當做文本文件來處理。文件里剩余的部分由3個以上的PNG的數據塊(Chunk)依照特定的順序組成,因此,一個標准的PNG文件結構應該例如以下:

PNG文件標志 PNG數據塊 PNG數據塊

所以我們能夠看到-x里面png格式的推斷函數:

bool Image::isPng(const unsigned char * data, ssize_t dataLen)
{
    if (dataLen <= 8)
    {
        return false;
    }

    static const unsigned char PNG_SIGNATURE[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};

    return memcmp(PNG_SIGNATURE, data, sizeof(PNG_SIGNATURE)) == 0;
}
PNG文件格式中的數據塊
數據塊符號 數據塊名稱 多數據塊 可選否 位置限制
IHDR 文件頭數據塊 第一塊
cHRM 基色和白色點數據塊 在PLTE和IDAT之前
gAMA 圖像γ數據塊 在PLTE和IDAT之前
sBIT 樣本有效位數據塊 在PLTE和IDAT之前
PLTE 調色板數據塊 在IDAT之前
bKGD 背景顏色數據塊 在PLTE之后IDAT之前
hIST 圖像直方圖數據塊 在PLTE之后IDAT之前
tRNS 圖像透明數據塊 在PLTE之后IDAT之前
oFFs (專用公共數據塊) 在IDAT之前
pHYs 物理像素尺寸數據塊 在IDAT之前
sCAL (專用公共數據塊) 在IDAT之前
IDAT 圖像數據塊 與其它IDAT連續
tIME 圖像最后改動時間數據塊 無限制
tEXt 文本信息數據塊 無限制
zTXt 壓縮文本數據塊 無限制
fRAc (專用公共數據塊) 無限制
gIFg (專用公共數據塊) 無限制
gIFt (專用公共數據塊) 無限制
gIFx (專用公共數據塊) 無限制
IEND 圖像結束數據 最后一個數據塊

數據塊結構

名稱 字節數 說明
Length (長度) 4字節 指定數據塊中數據域的長度,其長度不超過(231-1)字節
Chunk Type Code (數據塊類型碼) 4字節 數據塊類型碼由ASCII字母(A-Z和a-z)組成
Chunk Data (數據塊數據) 可變長度 存儲依照Chunk Type Code指定的數據
CRC (循環冗余檢測) 4字節 存儲用來檢測是否有錯誤的循環冗余碼

IHDR

文件頭數據塊IHDR(header chunk):它包括有PNG文件里存儲的圖像數據的基本信息,並要作為第一個數據塊出如今PNG數據流中,並且一個PNG數據流中僅僅能有一個文件頭數據塊。文件頭數據塊由13字節組成,它的格式例如以下表所看到的。

域的名稱 字節數 說明
Length (長度) 4字節 圖像寬度,以像素為單位
Height 4字節 圖像高度,以像素為單位
Bit depth 1字節 圖像深度:
索引彩色圖像:1,2。4或8
灰度圖像:1,2,4,8或16
真彩色圖像:8或16
ColorType 1字節 顏色類型:
0:灰度圖像, 1,2。4。8或16
2:真彩色圖像,8或16
3:索引彩色圖像,1,2,4或8
4:帶α通道數據的灰度圖像,8或16
6:帶α通道數據的真彩色圖像,8或16
Compression method 1字節 壓縮方法(LZ77派生算法)
Filter method 1字節 濾波器方法
Interlace method 1字節 隔行掃描方法:
0:非隔行掃描
1: Adam7(由Adam M. Costello開發的7遍隔行掃描方法)

PLTE

調色板數據塊PLTE(palette chunk)包括有與索引彩色圖像(indexed-color image)相關的彩色變換數據,它僅與索引彩色圖像有關。並且要放在圖像數據塊(image data chunk)之前。

PLTE數據塊是定義圖像的調色板信息。PLTE能夠包括1~256個調色板信息,每一個調色板信息由3個字節RGB組成。因此,調色板的長度應該是3的倍數,否則。這將是一個非法的調色板。同理調色板數據塊所包括的最大字節數為768。

對於索引圖像。調色板信息是必須的,調色板的顏色索引從0開始編號,然后是1、2……。調色板的顏色數不能超過色深中規定的顏色數(如圖像色深為4的時候,調色板中的顏色數不能夠超過2^4=16),否則,這將導致PNG圖像不合法。

真彩色圖像和帶α通道數據的真彩色圖像也能夠有調色板數據塊。目的是便於非真彩色顯示程序用它來量化圖像數據,從而顯示該圖像。

IDAT

圖像數據塊IDAT(image data chunk):它存儲實際的數據,在數據流中可包括多個連續順序的圖像數據塊。

IDAT存放着圖像真正的數據信息,因此,假設能夠了解IDAT的結構,我們就能夠非常方便的生成PNG圖像。

IEND

圖像結束數據IEND(image trailer chunk):它用來標記PNG文件或者數據流已經結束。並且必需要放在文件的尾部。假設我們細致觀察PNG文件,我們會發現。文件的結尾12個字符看起來總應該是這樣的:

00 00 00 00 49 45 4E 44 AE 42 60 82

不難明確。因為數據塊結構的定義。IEND數據塊的長度總是0(00 00 00 00,除非人為加入信息),數據標識總是IEND(49 45 4E 44)。因此,CRC碼也總是AE 42 60 82。

cocos2dx libpng的解碼

bool Image::initWithPngData(const unsigned char * data, ssize_t dataLen)
{
    // length of bytes to check if it is a valid png file
#define PNGSIGSIZE  8
    bool ret = false;
    png_byte        header[PNGSIGSIZE]   = {0}; 
    png_structp     png_ptr     =   0;
    png_infop       info_ptr    = 0;

    do 
    {
        // png header len is 8 bytes
        CC_BREAK_IF(dataLen < PNGSIGSIZE);

        //文件頭校驗
        // check the data is png or not
        memcpy(header, data, PNGSIGSIZE);
        CC_BREAK_IF(png_sig_cmp(header, 0, PNGSIGSIZE));

        //初始化png_structp類型結構體,libpng內部使用
        // init png_struct
        png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
        CC_BREAK_IF(! png_ptr);

        //創建圖像信息
        // init png_info
        info_ptr = png_create_info_struct(png_ptr);
        CC_BREAK_IF(!info_ptr);

#if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA && CC_TARGET_PLATFORM != CC_PLATFORM_NACL)
        //設置異常處理
        CC_BREAK_IF(setjmp(png_jmpbuf(png_ptr)));
#endif

        //使用自己定義的回調函數來設置libpng的數據源
        // set the read call back function
        tImageSource imageSource;
        imageSource.data    = (unsigned char*)data;
        imageSource.size    = dataLen;
        imageSource.offset  = 0;
        png_set_read_fn(png_ptr, &imageSource, pngReadCallback);

        // read png header info

        //使用底層處理來讀取png數據
        // read png file info
        png_read_info(png_ptr, info_ptr);

        //查詢圖像信息
        _width = png_get_image_width(png_ptr, info_ptr);
        _height = png_get_image_height(png_ptr, info_ptr);
        png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
        png_uint_32 color_type = png_get_color_type(png_ptr, info_ptr);

        //CCLOG("color type %u", color_type);

        //調色板格式的png圖片,轉化為RGB888的像素格式
        // force palette images to be expanded to 24-bit RGB
        // it may include alpha channel
        if (color_type == PNG_COLOR_TYPE_PALETTE)
        {
            png_set_palette_to_rgb(png_ptr);
        }
        //像素格式少於1字節長度的灰度圖,將其轉為每像素占1字節的像素格式
        // low-bit-depth grayscale images are to be expanded to 8 bits
        if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
        {
            bit_depth = 8;
            png_set_expand_gray_1_2_4_to_8(png_ptr);
        }
        //將tRNS塊數據信息擴展為完整的ALPHA通道信息 
        // expand any tRNS chunk data into a full alpha channel
        if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        {
            png_set_tRNS_to_alpha(png_ptr);
        }  
        //將16位輸入降為8位
        // reduce images with 16-bit samples to 8 bits
        if (bit_depth == 16)
        {
            png_set_strip_16(png_ptr);            
        } 

        // Expanded earlier for grayscale, now take care of palette and rgb
        if (bit_depth < 8)
        {
            png_set_packing(png_ptr);
        }
        //更新png數據的具體信息
        // update info
        png_read_update_info(png_ptr, info_ptr);
        bit_depth = png_get_bit_depth(png_ptr, info_ptr);
        color_type = png_get_color_type(png_ptr, info_ptr);

        switch (color_type)
        {
        case PNG_COLOR_TYPE_GRAY:
            _renderFormat = Texture2D::PixelFormat::I8;
            break;
        case PNG_COLOR_TYPE_GRAY_ALPHA:
            _renderFormat = Texture2D::PixelFormat::AI88;
            break;
        case PNG_COLOR_TYPE_RGB:
            _renderFormat = Texture2D::PixelFormat::RGB888;
            break;
        case PNG_COLOR_TYPE_RGB_ALPHA:
            _renderFormat = Texture2D::PixelFormat::RGBA8888;
            break;
        default:
            break;
        }

        //按行讀取png信息,
        // read png data
        png_size_t rowbytes;
        png_bytep* row_pointers = (png_bytep*)malloc( sizeof(png_bytep) * _height );

        //獲取每一行像素的字節數量
        rowbytes = png_get_rowbytes(png_ptr, info_ptr);

        //申請內存地址
        _dataLen = rowbytes * _height;
        _data = static_cast<unsigned char*>(malloc(_dataLen * sizeof(unsigned char)));
        if (!_data)
        {
            if (row_pointers != nullptr)
            {
                free(row_pointers);
            }
            break;
        }

        for (unsigned short i = 0; i < _height; ++i)
        {
            row_pointers[i] = _data + i*rowbytes;
        }
        //讀取png數據
        png_read_image(png_ptr, row_pointers);
        //結束讀取數據
        png_read_end(png_ptr, nullptr);

        // premultiplied alpha for RGBA8888
        if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
        {
            //預乘Alpha。使用漸變Alpha
            premultipliedAlpha();
        }
        else
        {
            _hasPremultipliedAlpha = false;
        }

        if (row_pointers != nullptr)
        {
            //釋放圖片數據的內存
            free(row_pointers);
        }

        ret = true;
    } while (0);

    if (png_ptr)
    {
        //釋放png_ptr
        png_destroy_read_struct(&png_ptr, (info_ptr) ?

&info_ptr : 0, 0); } return ret; }

tp


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM