前一段時間使用GDAL庫進行瓦片切割,由於需要將生成的圖片數據直接寫入數據庫,不需要在本地磁盤上進行IO操作,因此跟蹤GDAL的源代碼(過程就不說了),發現GDAL庫調用了LibPNG庫進行相應的PNG格式編碼工作,因此我研究了一下LibPNG庫,下面是對自己學習的一點總結:
libpng程序結構
LibPNG庫的處理流程如下:
判斷文件是否是png文件
通過文件名來判斷文件是否是png文件。這里的fp是指向文件指針的指針。主要是作為出口參數。如果文件成功打開后不關閉文件,等待以后對文件操作結束后再關閉文件。
FILE *fp = fopen(file_name, "rb"); if (!fp) { return (ERROR); } fread(header, 1, number, fp); is_png = !png_sig_cmp(header, 0, number); if (!is_png) { return (NOT_PNG); }
初始化程序
所謂的初始化程序即為png_struct和png_info分配空間。
png_struct是關於png文件及其相關屬性的結構,並不直接訪問,只是用與程序內部的信息傳遞。
png_info則是對於png圖片操作的主題。在新版本中主要通過png_set_*和png_get-*系列函數來改變或者得到png_info的信息。
png_struct和png_info是使用libpng的基礎,必須在對png文件進行操作前為其分配內存。png_info依賴於png_struct創建。
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr, user_error_fn, user_warning_fn); if (!png_ptr) return (ERROR); png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr,(png_infopp)NULL, (png_infopp)NULL); return (ERROR); }
png_create_read_struct()函數和setjmp/longjmp method相關參數見官方文檔。
將創建好的png_struct和png_info的指針和已經打開的png文件相關聯。
png_init_io(png_ptr, fp);
相關讀寫操作
當將分配的png_struct和png_info和已經打開的png文件關聯后,就可以進行相關操作了。
釋放空間
當對png文件相關操作完成后則需要釋放分配的空間和關閉打開的文件。
png_free_data(png_ptr, info_ptr, mask, seq); png_destroy_write_struct(&png_ptr, &info_ptr); fclose(fp);
以上是在網上搜集整理的一些基本用法,由於我不需要讓LibPNG庫生成本地圖片,因此又繼續看LibPNG的文檔,終於功夫不負有心人,在官方文檔中看到了一下一段話:
IV. Writing
Much of this is very similar to reading. However, everything of importance is repeated here, so you won't have to constantly look back up in the reading section to understand writing.
Setup
You will want to do the I/O initialization before you get into libpng,so if it doesn't work, you don't have anything to undo. If you are not using the standard I/O functions, you will need to replace them with custom writing functions. See the discussion under Customizing libpng.
以上這段話的意思就是在使用LibPNG庫的之前,必須進行IO初始化操作,如果還不能正常工作,你也不需要進行撤銷操作。如果你不打算使用標准的IO函數,必須使用自定義的函數來替換他們,具體的在Custormizing這一章。好下面我們跳到這一章。
我們在這一章中找到下面這一段話:
Input/Output in libpng is done through png_read() and png_write(),which currently just call fread() and fwrite(). The FILE * is stored in png_struct and is initialized via png_init_io(). If you wish to change the method of I/O, the library supplies callbacks that you can set through the function png_set_read_fn() and png_set_write_fn() at run time, instead of calling the png_init_io() function. These functions also provide a void pointer that can be retrieved via the function png_get_io_ptr().
這段話的意思如下:輸入輸出操作在LibPNG庫中是通過實際調用fread()和fwrite()函數的png_read()和png_write()函數來實現的。文件指針存儲在png_struct結構中,並通過png_init_io()函數來初始化。如果你想改變IO方法,該庫支持使用回調函數,你可以在程序運行時通過png_set_read_fn()和png_set_write_fn()函數來設置,而不需要在開始是使用png_init_io()函數。這些函數同時還提供了一個可以獲得IO指針的函數png_get_io_ptr()。
png_set_read_fn(png_structp read_ptr,voidp read_io_ptr, png_rw_ptr read_data_fn) png_set_write_fn(png_structp write_ptr,voidp write_io_ptr, png_rw_ptr write_data_fn,png_flush_ptr output_flush_fn); voidp read_io_ptr = png_get_io_ptr(read_ptr); voidp write_io_ptr = png_get_io_ptr(write_ptr); //這些替換的函數必須有以下的結構 void user_read_data(png_structp png_ptr,png_bytep data, png_size_t length); void user_write_data(png_structp png_ptr,png_bytep data, png_size_t length); void user_flush_data(png_structp png_ptr);
說明:fwrite()函數是帶有緩沖的函數,該函數只有在緩沖區滿或者調用fclose()函數的時候才會調用flush()函數進行寫入。上面的user_flush_fn()函數就是自定義的flush()函數。
下面是自己實現的一個簡易測試:
下面兩個函數是自己實現的一個write_data()函數和flush()函數。
//寫入數據函數,將所有的數據寫入緩沖區 void PNGAPI png_own_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { if (png_ptr == NULL) return; memcpy(tData->data + tData->count,data,length); tData->count += length; } //處理所有的數據,截取有效的緩沖區間,重新復制 void PNGAPI png_own_flush(png_structp png_ptr) { FILE * fp= fopen("D:\\success.png","wb+"); fwrite(tData->data,1,tData->count,fp); fclose(fp); }
其中tData是TrueData類型的全局結構體指針:
struct TrueData { //截獲的數據緩沖區 unsigned char * data; //截獲數據的有效字節數,即緩沖區的第一個可用字節 int count; //緩沖區的大小 int size; };
下面是在主函數中進行的設置。其中png_set_IHDR()函數必須放在所有png_set_*()函數的前面。
png_set_IHDR(png_ptr,info_ptr,outWidth,outHeight,8,PNG_COLOR_TYPE_RGB,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT); png_set_rows(png_ptr,info_ptr,_row_pointers); png_set_write_fn(png_ptr,io_ptr,png_own_write_data,png_own_flush); png_write_png(png_ptr,info_ptr,PNG_TRANSFORM_IDENTITY,NULL);
這是我們跟蹤我們的程序,發現flush()函數並沒有執行。最后跟進源代碼發現需要定義一個宏才行。
在源代碼中pngwrite.c中大概420行的位置有這樣一段代碼:
#ifdef PNG_WRITE_FLUSH_SUPPORTED # ifdef PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED png_flush(png_ptr); # endif #endif //將其改為如下格式,或者其他格式,但是必須要能執行png_flush()才行 #ifdef PNG_WRITE_FLUSH_SUPPORTED #define PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED # ifdef PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED png_flush(png_ptr); # endif #undef PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED #endif
之后從新編譯LIbPNG庫,運行程序,一切正常……搞定,收工。
其實LIBPNG庫的默認配置是沒有定義
#define PNG_WRITE_FLUSH_AFTER_IEND_SUPPORTED
宏的,我這里將它定義出來是為了讓他調用我的png_own_flush()函數。當讓在我們不調用png_own_flush()函數的時候,也能正常的將所得到的的數據寫入的文件,因為tData是全局變量,任意的函數都可以將它寫入到文件。