簡介
很多應用程序需要在數據庫中存儲和管理數字圖像。DB2 UDB 提供了 DB2 UDB Audio、Image 和 Video Extenders 包,DB2 Image Extender 是其中一部分[2]。雖然這個擴展器提供了一定數量的圖像處理函數,但常常還需要用到更多的函數,或者,現有的函數需要更加通用,例如支持任意角度的旋轉。現有的擴展器起初是以 DB2 UDB Version 5 為基礎,在此之後,數據庫引擎中已經添加了很多新的特性。然而,擴展器包還沒有充分利用這些新特性。
本文演示如何將最新的圖像處理程序 ImageMagick 與 DB2 UDB 相結合,提供一個新的擴展器,從而以一種新型的方式管理靜態圖像。本文展示了如何實現必要的用戶定義函數(UDF),將它們注冊到數據庫中,並通過 ImageMagick 庫將這些函數集中到一起,最終得到您自己的用於 DB2 UDB [5, 6] 的圖像擴展器。
本文中描述的“擴展器(extender)”提供了操縱以二進制大型對象(BLOB)形式存儲在數據庫中的圖像的基本功能。這些圖像可以按任意角度旋轉,可以縮放、調整大小、反轉、修剪,還可以按多種不同方式對形狀、顏色或內容進行操縱。該擴展器還提供了一組函數,用於獲得特定於圖像的屬性,例如高度或寬度(以像素為單位),或者 X 或 Y 維上的分辨率。除了基於 BLOB 的接口以外,本文還描述了如何根據 SQL/MM Part 5: Still Image 標准 [3]實現一種特定的數據類型。
下一節將對 ImageMagick 作一個簡短的概述,然後附上一些關於圖像相關 UDF 的示例代碼。您將看到如何編譯該代碼,並將其與 ImageMagick 鏈接成一個可以被 DB2 處理的共享庫。然後文中描述了 SQL 接口,從中可以看出那些僅僅處理 BLOB 的簡單接口與利用 DB2 的對象 — 關系(object-relational)特性(特別是利用結構類型封裝圖像功能的接口)兩者之間有什麼不同。
ImageMagick 概述
ImageMagick [1] 是由一些庫和工具組成的集合,它允許讀、寫和處理很多不同格式的靜態圖像。目前,受支持的圖像格式超過 89 種,例如 TIFF、JPEG、PNG、PDF 和 GIF。通過它可以對圖像調整大小、旋轉或銳化,還可以減少顏色的數量,或者添加特殊效果。ImageMagick 提供了各種各樣的接口,包括用於 C/C++、Perl、Java™ 和 PHP 等編程語言交互的各種命令行工具。本文給出的 DB2 擴展利用了 ImageMagick 庫及其 C/C++ 接口來連接到 DB2。在下載小節中給出的示例代碼是基於 ImageMagick 的 6.1.9 版本的。如果您想使用不同的版本,那麼需要對代碼作一些修改,因為接口可能會有所變化。
關於部分 UDF 的示例代碼
首先讓我們對關於部分 UDF 的 C/C++ 代碼作一個簡短的概述,您將實現這些 UDF [6]。注意,其他所有函數的實現都非常類似。主要的不同點在於所調用的 ImageMagick 函數。
在開始描述實際 UDF 的細節之前,我們先來看一下在大多數 UDF 中都用到的支持函數,這些函數有的用於錯誤處理,有的使用 BLOB 定位符從 DB2 獲取圖像數據,還有的是把結果寫到另一個 BLOB 定位符。
支持函數
所有 UDF 都需要某種基礎設施來管理錯誤。為靜態圖像擴展器實現的錯誤處理將處理所有的 ImageMagick 錯誤。錯誤處理封裝在類 IexError中。通過這種方式,可以很容易地添加錯誤消息的定位。這個類還提供了對所有錯誤的單點控制,並提供了跟蹤錯誤信息的必要基礎,這樣有助於在生產環境中發現意料之外的錯誤。除了 IexError類之外,我們還定義了一組名為 IEX_SET_ERROR*的宏,這些宏用於設置新的錯誤信息。
第二組支持函數在 IexUdfUtils.cpp文件中。這些函數負責 scratchpad 的管理,並處理圖像數據在 DB2 不同 BLOB 定位符之間的傳輸。函數 IExReadImageToScratchPad從輸入 BLOB 定位符獲取圖像數據,在內存中構造特定的 ImageMagick 對象,並將指向那個對象的指針存儲在被映射到 scratchpad 上的數據結構中。與此類似,函數 IExWriteImageToLocator用一個 ImageMagick 對象作為輸入參數,將該對象轉換成一個二進制流,並將這個流寫到輸出 BLOB 定位符。我們不會一行一行地討論這些支持函數的代碼,而只是關注特定於圖像的那些函數。
函數 SI_rotate
每個 UDF 都被實現為一個獨立的 C++ 函數。它采用特定於函數的參數作為輸入(例如格式轉換操作的目標格式),其中一個是圖像的 BLOB 定位符,用於對其進行操作,另一個是 BLOB 定位符,用於最終得到的圖像。此外,用於 UDF 的常見的 null 指示符和其他強制參數必須出現在函數的標簽中。請注意,我們使用定位符是為了提供運行時的性能。例如,在檢測特定於圖像的性能時,通常只需要處理圖像的頭部,而不必將整個圖像數據從 DB2 傳遞到 UDF。
調用類型 & scratchpad 參數
所有 UDF 都將用選項 FINAL CALL和 SCRATCHPAD來聲明。因此,會有一個內存塊用於將信息或指向其他內存塊的指針從一次函數調用傳遞到下一次函數調用。當一個函數要處理多個圖像時,例如 SQL 語句便經常如此,由於 SQL 是面向集合的查詢語言,因此上述做法為改善性能提供了必要的支持。
在 清單 1 中,您可以看到關於 UDF SI_rotate的代碼。它可以作為所有其他操縱圖像內容的 UDF 的代表,這些 UDF 都是非常類似的。在函數的一開始,緊接著函數標簽的地方,是函數內部變量的初始化,例如 ImageMagick 相關變量、指向 scratchpad(用於強加一個結構到它上面)的指針以及用於結果圖像定位符的 null 指示符。然後是對參數 angle的檢驗,該參數將影響對圖像的操作,例如定義圖像如何旋轉,如果有必要的話,代碼還將標准化這個參數,以介於 [0, 360] 之間的度數的方式表示角度。
接下來是實際的處理。首先調用支持函數 IExReadImageToScratchPad(在清單中以斜體顯示)從 BLOB 定位符獲取圖像,構造 ImageMagick 所需的數據結構,並將指向該數據結構的指針放在 scratchpad 上。如果不是第一次該函數,那麼可以重用上一次分配的數據結構。旋轉操作本身是使用 ImageMagick 函數 RotateImage(在下面的清單中以粗體顯示)。處理結果被再次放在特定於 ImageMagick 的數據結構中,這個結構需要轉換成一個 BLOB,並通過一個定位符傳遞給 DB2。這是通過使用支持函數 IExWriteImageToLocator(同樣以斜體顯示)來完成的。
清單 1. 關於圖像旋轉 UDF 的示例代碼/** Rotate the image. * * The given image is rotated using the ImageMagick function RotateImage(). * The angle needs to be specifIEd in degrees. The angle is taken modulo 360 * degrees; in other Words, angles larger than +360 degrees and smaller than * -360 degrees are accepted. * * Positive angles cause the image to be rotated counter-clockwise, and * negative angles rotate the image clockwise. * * NULL is returned if the given image is NULL. If the specified angle is * NULL, then the image is returned unchanged. */ IEX_EXTERNC void SQL_API_FN IexRotateImage( // input: locator to source image SQLUDF_LOCATOR *sourceLocator, // input: angle of the rotation SQLUDF_DOUBLE *angle, // output: locator to target image SQLUDF_LOCATOR *targetLocator, // null indicators SQLUDF_NULLIND *sourceLocator_ind, SQLUDF_NULLIND *angle_ind, SQLUDF_NULLIND *targetLocator_ind, SQLUDF_TRAIL_ARGS_ALL) { int rc = IEX_SUCCESS; IExError error;Image *result = NULL;ExceptionInfo exception;GetExceptionInfo(&exception); // we assume NULL result *targetLocator_ind = -1; // map the scratchpad struct scratchMap *scratch = (struct scratchMap *)SQLUDF_SCRAT->data; // clean up when the SQL statement is finished if (SQLUDF_CALLT == SQLUDF_FINAL_CALL) { goto cleanup; } // normalize the angle and test if we actually have something to do if (SQLUDF_NULL(angle_ind)) { *angle = 0.0; } *angle = fmod(*angle, 360); if (*angle == 0.0) { *targetLocator = *sourceLocator; *targetLocator_ind = 0; goto cleanup; } // read the image data rc = IExReadImageToScratchPad(sourceLocator, *sourceLocator_ind, scratch, SQLUDF_CALLT, error); if (rc || !scratch->image) { goto cleanup; } // rotate the image result =RotateImage(scratch->image, *angle, &exception); if (!result || IEX_HAVE_MAGICK_EXCEPTION(exception)) { IEX_SET_MAGICK_ERROR(exception); goto cleanup; } if (IEX_HAVE_MAGICK_EXCEPTION(result->exception)) { IEX_SET_MAGICK_ERROR(result->exception); goto cleanup; } // write the result to the target locator rc = IExWriteImageToLocator(result, targetLocator, error); if (rc) { goto cleanup; } *targetLocator_ind = 0; cleanup:DestroyExceptionInfo(&exception); if (result) {DestroyImage(result); } IEX_COMMON_CLEANUP; }