简介
很多应用程序需要在数据库中存储和管理数字图像。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 aclearcase/" target="_blank" >ccepted. * * 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; } |