圖像解碼之二——使用libpng解碼png圖片


上文《圖像解碼之一——使用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庫非常值得我們學習它的對外接口的定義。

原文


免責聲明!

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



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