libpng使用


自己的實現

  1 unsigned int component(png_const_bytep row, png_uint_32 x, unsigned int c, unsigned int bit_depth, unsigned int channels) {
  2     png_uint_32 bit_offset_hi = bit_depth * ((x >> 6) * channels);
  3     png_uint_32 bit_offset_lo = bit_depth * ((x & 0x3f) * channels + c);
  4 
  5     row = (png_const_bytep)(((PNG_CONST png_byte(*)[8])row) + bit_offset_hi);
  6     row += bit_offset_lo >> 3;
  7     bit_offset_lo &= 0x07;
  8 
  9     switch (bit_depth) {
 10     case 1:
 11         return (row[0] >> (7 - bit_offset_lo)) & 0x01;
 12     case 2:
 13         return (row[0] >> (6 - bit_offset_lo)) & 0x03;
 14     case 4:
 15         return (row[0] >> (4 - bit_offset_lo)) & 0x0f;
 16     case 8:
 17         return row[0];
 18     case 16:
 19         return (row[0] << 8) + row[1];
 20     default:
 21         fprintf(stderr, "pixel_component: invalid bit depth %u\n", bit_depth);
 22         return row[0];
 23     }
 24 }
 25 
 26 void readPixel(png_const_bytep row, png_uint_32 x, png_uint_32 bit_depth, png_uint_32 color_type, BitmapPixel *pPixel) {
 27     unsigned int channels = PNG_COLOR_TYPE_RGB_ALPHA ? 4 : 3;
 28     pPixel->r = component(row, x, 0, bit_depth, channels);
 29     pPixel->g = component(row, x, 1, bit_depth, channels);
 30     pPixel->b = component(row, x, 2, bit_depth, channels);
 31     pPixel->a = color_type == PNG_COLOR_TYPE_RGB_ALPHA ? component(row, x, 3, bit_depth, channels) : 0xFF;
 32 }
 33 
 34 BitmapData* PNG_ARGB8888(FILE *pngFile) {
 35     BitmapData *pData;
 36     png_structp png_ptr;
 37     png_infop info_ptr;
 38     png_bytepp pRow = NULL;
 39     png_uint_32 width, height, ystart, xstart, ystep, xstep, px, ppx, py, count;
 40     png_int_32 bit_depth, color_type, interlace_method, passes, pass;
 41 
 42     if (!pngFile) {
 43         return NULL;
 44     }
 45     if (!(png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) {
 46         LETV_LOGE("ERROR PNG_ARGB8888: out of memory allocating png_struct!\n");
 47         return NULL;
 48     }
 49     if (!(info_ptr = png_create_info_struct(png_ptr))) {
 50         LETV_LOGE("ERROR PNG_ARGB8888: out of memory allocating png_info!\n");
 51         png_destroy_read_struct(&png_ptr, NULL, NULL);
 52         return NULL;
 53     }
 54     if (setjmp(png_jmpbuf(png_ptr))) {
 55         LETV_LOGE("ERROR PNG_ARGB8888: setjmp png_ptr failed!\n");
 56         png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 57         return NULL;
 58     }
 59     png_init_io(png_ptr, pngFile);
 60     png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);
 61     color_type = png_get_color_type(png_ptr, info_ptr);
 62     if (!(color_type & PNG_COLOR_TYPE_RGB)) {
 63         LETV_LOGE("ERROR PNG_ARGB8888: PNG_COLOR_TYPE unsupported!\n");
 64         png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 65         return NULL;
 66     }
 67     interlace_method = png_get_interlace_type(png_ptr, info_ptr);
 68     switch (interlace_method) {
 69     case PNG_INTERLACE_NONE:
 70         passes = 1;
 71         break;
 72     case PNG_INTERLACE_ADAM7:
 73         passes = PNG_INTERLACE_ADAM7_PASSES;
 74         break;
 75     default:
 76         LETV_LOGE("ERROR PNG_ARGB8888: png unknown interlace!\n");
 77         png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 78         return NULL;
 79     }
 80 
 81     pData = (BitmapData*)LETV_MEM_MALLOC(sizeof(BitmapData));
 82     if (!pData) {
 83         LETV_LOGE("ERROR PNG_ARGB8888: allocate memory for bitmap data failed!\n");
 84         png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
 85         return NULL;
 86     }
 87 
 88     width = png_get_image_width(png_ptr, info_ptr);
 89     height = png_get_image_height(png_ptr, info_ptr);
 90     bit_depth = png_get_bit_depth(png_ptr, info_ptr);
 91     pRow = png_get_rows(png_ptr, info_ptr);
 92     for (pass = 0; pass < passes; ++pass) {
 93         if (interlace_method == PNG_INTERLACE_ADAM7) {
 94             if (PNG_PASS_COLS(width, pass) == 0)
 95                 continue;
 96 
 97             xstart = PNG_PASS_START_COL(pass);
 98             ystart = PNG_PASS_START_ROW(pass);
 99             xstep = PNG_PASS_COL_OFFSET(pass);
100             ystep = PNG_PASS_ROW_OFFSET(pass);
101         } else {
102             ystart = xstart = 0;
103             ystep = xstep = 1;
104         }
105 
106         pData->infoHeader.width = width / xstep;
107         pData->infoHeader.height = height / ystep;
108         pData->fileHeader.size = sizeof(BitmapPixel) * pData->infoHeader.width * pData->infoHeader.height;
109         pData->pPixels = (BitmapPixel*)LETV_MEM_MALLOC(pData->fileHeader.size);
110         if (!pData->pPixels) {
111             LETV_LOGE("ERROR PNG_ARGB8888: allocate memory for bitmap pixels failed!\n");
112             break;
113         }
114 
115         count = 0;
116         for (py = ystart; py < ystart + height; py += ystep) {
117             for (px = xstart, ppx = 0; px < xstart + width; px += xstep, ppx++) {
118                 readPixel(*(pRow + py), ppx, bit_depth, color_type, pData->pPixels + count);
119                 count++;
120             }
121         }
122         break;
123     }
124     png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
125     return pData;
126 }

 

轉自:https://www.0xaa55.com/forum.php?mod=viewthread&tid=425&extra=page%3D1&page=1

我要講的三個大部分分別是:
1、libpng是什么,能做什么?
2、怎樣讓自己的程序可以使用libpng庫?
3、怎樣借助libpng讀寫PNG文件

1、libpng是什么?
libpng是一款C語言編寫的比較底層的讀寫PNG文件的跨平台的庫。借助它,你可以輕松讀寫PNG文件的每一行像素。
因為PNG文件是經過壓縮而且格式復雜的圖形文件(有的PNG文件甚至像GIF文件一樣帶動畫效果)
而且PNG可以是帶透明通道的真彩色圖像、不帶透明通道的真彩色圖像、索引顏色、灰度顏色等各種格式,如果大家都自己寫程序分析PNG文件就會顯得很麻煩、很累。因此,通過使用libpng你就能直接使用現成的函數、程序來讀寫PNG文件了。
注:libpng的官網說,“PNG”這個詞的發音是“拼”(ping),而不是“批恩雞”(pee en gee,也就是直接讀字母),也不是其它的單詞發音(什么pinj、pig之類的發音)。
但是如果你不是以英語為母語的話(咱都是中國人呢~~)你就不必蛋疼地在意這個發音的問題,直接見人就說“批恩雞”就行啦。(當我沒說)

2、怎樣讓自己的程序可以使用libpng庫?
有很多種方法。
方法1:上網下載libpng的DLL、LIB文件以及頭文件,然后在自己的程序里,包含png.h,鏈接libpng.lib,就可以了。但是這樣的話你的程序需要libpng.dll才能運行,而libpng使用了zlib所以可能你還需要zlib.dll才能運行。因此你還需要下載zlib的頭文件、lib、DLL。
方法2:直接下載libpng的源碼和zlib的源碼,然后把.c文件和.h文件都加入到自己的工程里面。這招最好使因為這樣便於調試。只是你的程序會很大因為你的程序直接集成了libpng和zlib。
方法3:下載libpng的源碼和zlib的源碼,自己將其編譯為DLL或LIB,然后包含png.h,鏈接LIB文件,就能使用。不過你如果沒編譯好也可能會出問題。

3、怎樣借助libpng讀寫PNG文件
首先來講如何寫入PNG文件。
第一步:初始化libpng庫。
當你需要讀一個PNG文件或者寫一個PNG文件的時候,你需要先定義兩個結構體指針:

1 png_structp png_ptr=NULL;//libpng的結構體
2 png_infop   info_ptr=NULL;//libpng的信息
 

你可以把上面的結構體指針定義為全局變量使用。
每這兩個結構體對應一個PNG文件。因此當你要同時操作多個PNG文件的時候,你就需要定義多個png_structppng_infop來處理這些PNG文件了。
因為是要寫文件,所以要這樣初始化:

 1 int iRetVal;
 2 png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
 3 if(!png_ptr)
 4     goto 錯誤處理;
 5 info_ptr=png_create_info_struct(png_ptr);
 6 if(!info_ptr)
 7 {
 8     png_destroy_write_struct(&png_ptr,NULL);
 9     goto 錯誤處理;
10 }
11 iRetVal=setjmp(png_jmpbuf(png_ptr));//安裝錯誤處理跳轉點
12 //當libpng內部出現錯誤的時候,libpng會調用longjmp直接跳轉到這里運行。
13 if(iRetVal)//setjmp的返回值就是libpng跳轉后提供的錯誤代碼(貌似總是1,但是還是請大家看libpng的官方文檔)
14 {
15     fprintf(stderr,"錯誤碼:%d\n",iRetVal);
16     goto 錯誤處理;
17 }
 

只要最后png_ptr和info_ptr都不是NULL就行了。否則就算是出錯了。
這里可以看到libpng使用了setjmp來做錯誤處理。有關setjmp的信息請點這里進去看。
這兩個結構體有對應的釋放函數:png_destroy_write_struct
結束對一個PNG的訪問之后,你只需像這樣調用這個函數:

1 png_destroy_write_struct(&png_ptr,&info_ptr);
 

就可以了。
接下來打開文件准備寫文件。還是大家熟悉的C語言文件流。

1 FILE*fp=fopen("C:\\TEST.PNG","wb");
2 if(!fp)
3     goto 錯誤處理;
 

打開了文件以后,你要讓libpng和這個文件流綁定起來,因此你需要調用png_init_io來完成綁定。

1 png_init_io(png_ptr,fp);
 

接下來就是關鍵的部分了:設置PNG文件的屬性、寫入PNG文件頭、寫入PNG文件。

 1 //設置PNG文件頭
 2 png_set_IHDR(png_ptr,info_ptr,
 3     圖像寬度,圖像高度,//尺寸
 4     8,//顏色深度,也就是每個顏色成分占用位數(8表示8位紅8位綠8位藍,如果有透明通道則還會有8位不透明度)
 5     PNG_COLOR_TYPE_RGB,//顏色類型,PNG_COLOR_TYPE_RGB表示24位真彩深色,PNG_COLOR_TYPE_RGBA表示32位帶透明通道真彩色
 6     PNG_INTERLACE_NONE,//不交錯。PNG_INTERLACE_ADAM7表示這個PNG文件是交錯格式。交錯格式的PNG文件在網絡傳輸的時候能以最快速度顯示出圖像的大致樣子。
 7     PNG_COMPRESSION_TYPE_BASE,//壓縮方式
 8     PNG_FILTER_TYPE_BASE);//這個不知道,總之填寫PNG_FILTER_TYPE_BASE即可。
 9 png_set_packing(png_ptr);//設置打包信息
10 png_write_info(png_ptr,info_ptr);//寫入文件頭
 

執行完這些語句以后,你會發現libpng已經通過文件流指針fp寫入了PNG的文件頭。
接下來要做的就是寫入PNG的圖像信息。其實就是把顏色保存到PNG。
不像惡心的BMP居然有“底到上型”和“頂到下型”之分,PNG只有“頂到下型”,因此你不需要考慮行序。
寫圖的方法之一是調用png_write_image(png_ptr,行指針數組的指針);這個你不需要考慮交錯文件的寫入的遍數。
而如果你需要手動寫入每一行的數據,你需要調用png_write_row(png_ptr,一行像素的指針);來進行逐行的像素值寫入。
如果你設置了交錯格式的PNG,你需要多寫入幾遍圖形數據,你需要調用png_set_interlace_handling(png_ptr);來得知你需要寫入的遍數。如果你沒有設置交錯格式的PNG,你只需要寫入一遍。

以下文本寫給小白。高手請略過。
這里需要詳細說明圖像是怎么寫入的。首先說明一下什么是圖像。一個圖像,是由一個一個的正方形的小像素點組成的。
每個像素點都有自己的顏色值,用三個字節來表示紅色、綠色、藍色的分量。所有的顏色都是用紅綠藍三基色混合搭配調出來的顏色。
然后對於libpng,圖像是一行一行寫入到文件的。這里所說的“行”和“列”指的是排列起來的像素點。比如一個圖像的寬度是1024像素,高度是768像素,那么這個圖像就有768行,每行有1024個像素點。
假設你設置的顏色深度是8,顏色類型是PNG_COLOR_TYPE_RGB,那么你的每個像素點都是由三個字節組成的,這三個字節分別是紅色、綠色和藍色的分量。
而如果顏色類型是PNG_COLOR_TYPE_RGBA,那么你的每個像素點都是由四個字節組成的,這四個字節分別是紅色、綠色和藍色的分量和不透明度。這樣的圖像才支持透明顏色的顯示。
調用png_write_row的方法很簡單,就是把一行的像素點的顏色設置好,然后調用它:png_write_row(png_ptr,這行像素的第一個像素在內存中的位置);就可以寫入一行。
而調用png_write_image你需要把每一行的像素顏色都設置好,然后建立一個指針數組,這個指針數組的每一個指針都指向每一行的像素。明白吧?

寫入好像素以后,調用png_write_end(png_ptr,info_ptr);把文件的結尾寫入。
調用png_destroy_write_struct(&png_ptr,&info_ptr);結束對這個PNG文件的訪問。
最后fclose(fp);關閉文件。這個時候你會發現,你已經成功地產生了一個PNG文件!而且可以用PS打開了。

讀取PNG文件也是類似的步驟,首先你需要初始化libpng庫。
你需要先定義兩個結構體指針:

1 png_structp png_ptr=NULL;//libpng的結構體
2 png_infop   info_ptr=NULL;//libpng的信息

你可以把上面的結構體指針定義為全局變量使用。

每這兩個結構體對應一個PNG文件。因此當你要同時操作多個PNG文件的時候,你就需要定義多個png_structppng_infop來處理這些PNG文件了。
因為是要讀文件,所以要這樣初始化:

 1 int iRetVal;
 2 png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
 3 if(!png_ptr)
 4     goto 錯誤處理;
 5 info_ptr=png_create_info_struct(png_ptr);
 6 if(!info_ptr)
 7 {
 8     png_destroy_read_struct(&png_ptr,NULL,NULL);
 9     goto 錯誤處理;
10 }
11 iRetVal=setjmp(png_jmpbuf(png_ptr));//安裝錯誤處理跳轉點
12 //當libpng內部出現錯誤的時候,libpng會調用longjmp直接跳轉到這里運行。
13 if(iRetVal)//setjmp的返回值就是libpng跳轉后提供的錯誤代碼(貌似總是1,但是還是請大家看libpng的官方文檔)
14 {
15     fprintf(stderr,"錯誤碼:%d\n",iRetVal);
16     goto 錯誤處理;
17 }
 

只要最后png_ptr和info_ptr都不是NULL就行了。否則就算是出錯了。
這兩個結構體有對應的釋放函數:png_destroy_read_struct
結束對一個PNG的訪問之后,你只需像這樣調用這個函數:

1 png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
 

就可以了。
接下來打開文件准備讀文件。還是大家熟悉的C語言文件流。

1 FILE*fp=fopen("C:\\TEST.PNG","rb");
2 if(!fp)
3     goto 錯誤處理;

打開了文件以后,你要讓libpng和這個文件流綁定起來,因此你需要調用png_init_io來完成綁定。綁定之后,你還需要獲取PNG的文件頭信息。因此你需要調用png_read_info(png_ptr, info_ptr);

 

1 png_init_io(png_ptr,fp);
2 png_read_info(png_ptr, info_ptr);
 

讀取了文件頭,你就能獲取文件頭的信息。比如文件尺寸、位深度等。代碼如下:

1 png_get_IHDR(png_ptr,info_ptr,&width,&height,&bit_depth,&color_type,NULL,NULL,NULL);
 

有些PNG文件是有背景色的,因此你需要處理這些背景色信息。我們可以用png_get_valid來判斷這個PNG是否有背景色信息。png_get_valid(png_ptr,info_ptr,PNG_INFO_bKGD)返回0表示沒有背景色信息,返回非零表示有背景色信息。然后我們調用png_get_bKGD來讀取背景色。

1 png_color_16p pBackground;
2 png_get_bKGD(png_ptr,info_ptr,&pBackground);

大家可以看看png_color_16p的原型:

1 typedef struct png_color_16_struct
2 {
3     png_byte index;
4     png_uint_16 red;
5     png_uint_16 green;
6     png_uint_16 blue;
7     png_uint_16 gray;
8 } png_color_16;
 

如果這個PNG是調色板顏色的位圖,那么index表示背景色的調色板顏色序號。
red、green、blue表示背景色的顏色值。如果png_get_IHDR返回的位深度(bit_depth)是16,那么red、green、blue就是16位的顏色值,范圍0~65535。(瞬間覺得PNG高大上啊!16+16+16=48,這個比真彩色還要真彩色!屌!)
而如果png_get_IHDR返回的位深度(bit_depth)是8,那么red、green、blue其實都是8位的顏色值,范圍0~255,也就是24位真彩色。
接下來就是關鍵的步驟了,讀取顏色數據。
因為有些PNG是灰度色,有些PNG是索引顏色,有些PNG是48位色,總之各種奇葩。為了便於讀取,我們應該先規范一下格式。

 1 if(colortype==PNG_COLOR_TYPE_PALETTE)
 2     png_set_palette_to_rgb(png_ptr);//要求轉換索引顏色到RGB
 3 if(colortype==PNG_COLOR_TYPE_GRAY && bit_depth<8)
 4     png_set_expand_gray_1_2_4_to_8(png_ptr);//要求位深度強制8bit
 5 if(bit_depth==16)
 6     png_set_strip_16(png_ptr);//要求位深度強制8bit
 7 if(png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS))
 8     png_set_tRNS_to_alpha(png_ptr);
 9 if(colortype == PNG_COLOR_TYPE_GRAY || colortype == PNG_COLOR_TYPE_GRAY_ALPHA)
10     png_set_gray_to_rgb(png_ptr);//灰度必須轉換成RGB
 

經過這些設定以后,我們讀取的PNG就一律是R8:G8:B8:A8的4字節格式了(紅綠藍透明均為8位,每像素4字節)
然后准備讀取PNG。首先分配一個足夠大的內存來存儲顏色數據,然后分配一個內存來存儲顏色數據每行的指針。
因為顏色已經被規范為32位了所以我們可以直接把每個像素當做一個COLORREF變量。

1 ppLinePtrs=(COLORREF**)malloc(g_dwHeight*sizeof(COLORREF*));//列指針
2 if(!ppLinePtrs)
3     goto Error;
4 i=g_dwHeight;
5 y=0;
6 while(i--)//逆行序讀取,因為位圖是底到上型
7     ppLinePtrs[y++]=(COLORREF*)&g_pBits[i*g_dwWidth];
 

這個時候就是萬事俱備的時候,只需要調用png_read_image(png_ptr,(png_bytepp)ppLinePtrs);就能完成讀取。
讀取完以后,調用png_read_end(png_ptr,info_ptr);結束讀取,調用png_destroy_read_struct(&png_ptr,&info_ptr,NULL);銷毀結構體,然后fclose(fp);就算一切都搞定了。

接下來我會放上兩份源碼,一份把BMP轉換成PNG(支持把兩個BMP合體轉換成帶透明通道的PNG)
而另一份是結合了分層窗體的技術,把PNG當做分層窗體的界面來顯示的源碼。
效果不錯。發張圖曬曬。
<ignore_js_op> 


免責聲明!

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



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