自己的實現
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_structp和png_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_structp和png_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>