上文《圖像解碼之一——使用libjpeg解碼jpeg圖片》介紹了使用libjpeg解碼jpeg圖片。png圖片應用也非常廣泛,本文將會簡單介紹怎樣使用開源libpng庫解碼png圖片。
libpng的數據結構
png_structp變量是在libpng初始化的時候創建,由libpng庫內部使用,代表libpng的是調用上下文,庫的使用者不應該對這個變量進行訪問。調用libpng的API的時候,需要把這個參數作為第一個參數傳入。
png_infop變量,初始化完成libpng之后,可以從libpng中獲得該類型變量指針。這個變量保存了png圖片數據的信息,庫的使用者可以修 改和查閱該變量,比如:查閱圖片信息,修改圖片解碼參數。在早期的版本中直接訪問該變量的成員,最新的版本建議是通過API來訪問這些成員。
libpng的使用
0、判斷是否為libpng數據
這步是可選的,在利用libpng繼續數據處理之前,可以調用png_sig_cmp函數來檢查是否為png數據,請參閱libpng手冊了解詳細內容。
1、初始化libpng
1: /* Create and initialize the png_struct with the desired error handler
2: * functions. If you want to use the default stderr and longjump method,
3: * you can supply NULL for the last three parameters. We also supply the
4: * the compiler header file version, so that we know if the application
5: * was compiled with a compatible version of the library. REQUIRED
6: */
7: png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
8: png_voidp user_error_ptr, user_error_fn, user_warning_fn);
初始化libpng的時候,用戶可以指定自定義錯誤處理函數,如果不需要指定自定義錯誤處理函數,則傳NULL即可。 png_create_read_struct函數返回一個png_structp變量,前面已經提到該變量不應該被用戶訪問,應該在以后調用 libpng的函數時傳遞給libpng庫。
如果你需要提供自定義內存管理模塊則需要調用png_create_read_struct_2來完成對libpng的初始化:
1: png_structp png_ptr = png_create_read_struct_2
2: (PNG_LIBPNG_VER_STRING, (png_voidp)user_error_ptr,
3: user_error_fn, user_warning_fn, (png_voidp)
4: user_mem_ptr, user_malloc_fn, user_free_fn)
2、創建圖像信息——png_infop變量
1: /* Allocate/initialize the memory for image information. REQUIRED. */
2: info_ptr = png_create_info_struct(png_ptr);
3: if (info_ptr == NULL)
4: {
5: png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
6: return (ERROR);
7: }
如前面所說,用戶將會通過png_infop變量來獲得圖片的信息,設置圖片解碼參數等。
3、設置錯誤返回點
上文libjpeg解碼jpeg圖片中提到用setjmp/longjmp函數來處理異常。libpng庫默認集成這種機制來完成異常處理,如下代碼初始化錯誤返回點:
1: /* Set error handling if you are using the setjmp/longjmp method (this is
2: * the normal method of doing things with libpng). REQUIRED unless you
3: * set up your own error handlers in the png_create_read_struct() earlier.
4: */
5: if (setjmp(png_jmpbuf(png_ptr)))
6: {
7: /* Free all of the memory associated with the png_ptr and info_ptr */
8: png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
9: /* If we get here, we had a problem reading the file */
10: return (ERROR);
11: }
正如上面注釋中提到情況,只有在初始化libpng的時候未指定用戶自定義的錯誤處理函數情況下,才需要設置錯誤返回點。如果設置了用戶自定義的錯誤處理函數,libpng將會調用用戶自定義錯誤處理函數,而不會返回到這個調用點。
當libpng庫出現錯誤的時候,libpng將會自動調用longjmp函數返回到這個點。在這個點我們可以進行必要的清理工作。
4、設置libpng的數據源
我在上文《圖像解碼之一——使用libjpeg解碼jpeg圖片》中提到,一個好的代碼庫應該能夠運行用戶輸入各式各樣的數據,而不能把輸入數據定死。libpng在這方面做得非常的好,它提供了默認的文件輸入流的支持,並且提供了用戶自定義回調函數來完成png數據的輸入。
對於文件流數據數據設置代碼如下:
1: /* One of the following I/O initialization methods is REQUIRED */
2: def streams /* PNG file I/O method 1 */
3: /* Set up the input control if you are using standard C streams */
4: png_init_io(png_ptr, fp);
用戶自定義回調函數設置libpng數據源的代碼如下:
1: /* If you are using replacement read functions, instead of calling
2: * png_init_io() here you would call:
3: */
4: png_set_read_fn(png_ptr, (void *)user_io_ptr, user_read_fn);
5: /* where user_io_ptr is a structure you want available to the callbacks */
如果你已經使用png_sig_cmp函數來檢查了png數據,需要調用png_set_sig_bytes函數來告訴libpng庫,這樣庫處理數據的時候將會跳過相應的數據,具體請參考libpng手冊。
5、png圖像處理
這步有兩種設置方案一種稱為高層處理,一種稱為底層處理。
高層處理
當用戶的內存足夠大,可以一次性讀入所有的png數據,並且輸出數據格式為如下libpng預定義數據類型時,可以用高層函數,下libpng預定義數據類型為:
PNG_TRANSFORM_IDENTITY No transformation
PNG_TRANSFORM_STRIP_16 Strip 16-bit samples to
8 bits
PNG_TRANSFORM_STRIP_ALPHA Discard the alpha channel
PNG_TRANSFORM_PACKING Expand 1, 2 and 4-bit
samples to bytes
PNG_TRANSFORM_PACKSWAP Change order of packed
pixels to LSB first
PNG_TRANSFORM_EXPAND Perform set_expand()
PNG_TRANSFORM_INVERT_MONO Invert monochrome images
PNG_TRANSFORM_SHIFT Normalize pixels to the
sBIT depth
PNG_TRANSFORM_BGR Flip RGB to BGR, RGBA
to BGRA
PNG_TRANSFORM_SWAP_ALPHA Flip RGBA to ARGB or GA
to AG
PNG_TRANSFORM_INVERT_ALPHA Change alpha from opacity
to transparency
PNG_TRANSFORM_SWAP_ENDIAN Byte-swap 16-bit samples
PNG_TRANSFORM_GRAY_TO_RGB Expand grayscale samples
to RGB (or GA to RGBA)
高層讀取函數如下:
1: /*
2: * If you have enough memory to read in the entire image at once,
3: * and you need to specify only transforms that can be controlled
4: * with one of the PNG_TRANSFORM_* bits (this presently excludes
5: * dithering, filling, setting background, and doing gamma
6: * adjustment), then you can read the entire image (including
7: * pixels) into the info structure with this call:
8: */
9: png_read_png(png_ptr, info_ptr, png_transforms, png_voidp_NULL);
該函數將會把所有的圖片數據解碼到info_ptr數據結構中。png_transforms為整型參數,為上面libpng預定義的數據類型進行or操作得到。調用了該函數,就不可以再調用png_set_transform函數來設置輸出數據。
該函數相當於調用底層函數(下文將會介紹)如下調用順序:
a)調用png_read_info函數獲得圖片信息。
b)根據png_transforms所指示的,調用png_set_transform設置輸出格式轉換的函數。
c)調用png_read_image來解碼整個圖片的數據到內存。
d)調用png_read_end結束圖片解碼。
當你調用png_read_png之后,則可以調用如下函數得到png數據:
1: row_pointers = png_get_rows(png_ptr, info_ptr);
底層處理
a)讀取輸入png數據的圖片信息:
1: /* The call to png_read_info() gives us all of the information from the
2: * PNG file before the first IDAT (image data chunk). REQUIRED
3: */
4: png_read_info(png_ptr, info_ptr);
該函數將會把輸入png數據的信息讀入到info_ptr數據結構中。
b)查詢圖像信息
前面提到png_read_info將會把輸入png數據的信息讀入到info_ptr數據結構中,接下來需要調用API查詢該信息。
1: png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
2: &interlace_type, int_p_NULL, int_p_NULL);
c)設置png輸出參數(轉換參數)
這步非常重要,用戶可以指定輸出數據的格式,比如RGB888,ARGB8888等等輸出數據格式。通過png_set_xxxxx函數來實現,例如如下代碼:
1: // expand images of all color-type and bit-depth to 3x8 bit RGB images
2: // let the library process things like alpha, transparency, background
3: if (bit_depth == 16)
4: png_set_strip_16(png_ptr);
5: if (color_type == PNG_COLOR_TYPE_PALETTE)
6: png_set_expand(png_ptr);
7: if (bit_depth<8)
8: png_set_expand(png_ptr);
9: if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
10: png_set_expand(png_ptr);
11: if (color_type == PNG_COLOR_TYPE_GRAY ||
12: color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
13: png_set_gray_to_rgb(png_ptr);
如上代碼將會把圖像轉換成RGB888的數據格式。這種轉換函數還很多,請參閱libpng手冊了解他們的作用。
雖然有很多設置輸出參數的函數可以調用,但是用戶的需求是無限的,很多輸出格式libpng並不是原生支持的,比如YUV565,RGB565,YUYV 等等。幸好libpng提供了自定義轉換函數的功能,可以讓用戶注冊轉換回調函數給libpng庫,在libpng對輸出數據進行轉換的時候,先對 png_set_xxxxx函數設置的參數進行轉換,最后將會調用用戶自定義的轉換函數進行轉換。
1: png_set_read_user_transform_fn(png_ptr,
2: read_transform_fn);
read_transform_fn為用戶自定義的數據轉換函數。具體實現可以參考pngtest.c中的實現。
另外你可以通過png_set_user_transform_info告訴libpng你的轉換函數的用戶自定義數據結構和輸出數據的詳細信息,比如顏 色深度,顏色通道(channel)等等。你可能會問為什么要告訴libpng呢?libpng將會根據這些信息來更新png圖片詳細信息,后面會介紹。 定義如下:
1: png_set_user_transform_info(png_ptr, user_ptr,
2: user_depth, user_channels);
usr_ptr是用戶自定義的數據結構,在用戶自定義轉換函數read_transform_fn中可以通過png_get_user_transform_ptr函數得到該數據結構,例如:
1: voidp read_user_transform_ptr =
2: png_get_user_transform_ptr(png_ptr);
d)更新png數據的詳細信息
經過前面的設置png數據的圖片信息肯定會有一些變化,則需要調用png_read_update_info函數更新圖片的詳細信息:
1: png_read_update_info(png_ptr, info_ptr);
該函數將會更新保存於info_ptr變量中的圖片數據信息,然后可以再調用png_get_IHDR重新查詢圖片信息。
e)讀取png數據
可以到用png_read_image函數,一次性把所有的數據讀入內存,例如:
1: png_read_image(png_ptr, row_pointers);
也可以調用png_read_rows一次讀入1行或多行到內存中,比如:
1: for (y = 0; y < height; y++)
2: {
3: png_read_rows(png_ptr, &row_pointers[y], png_bytepp_NULL, 1);
4: }
f)結束讀取數據
通過png_read_end結束讀取png數據,代碼如下:
1: /* Read rest of file, and get additional chunks in info_ptr - REQUIRED */
2: png_read_end(png_ptr, info_ptr);
6、釋放libpng的內存
調用png_destroy_read_struct來釋放libpng的內存,代碼如下:
1: /* Clean up after the read, and free any memory allocated - REQUIRED */
2: png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
至此png數據解碼,全部完成了。
總結:
通過上面的介紹,我們可以發現相對於libjpeg,libpng的擴展性非常好。我們基本上沒有任何修改libpng庫的需求,它的對外接口提供了足夠的靈活性,允許我們擴展。從這個角度來講,libpng庫非常值得我們學習它的對外接口的定義。