将 ImageMagick 与 DB2 UDB 集成
Knut Stolze
信息集成开发, IBM Germany
2005 年 5 月 16 日
将最新的图像处理功能与存储在 IBM® DB2® Universal Database™ (DB2 UDB) 数据库中的图像集成。本文将展示一种方法,该方法将诸如 ImageMagick™ 之类的第三方图像处理库与 DB2 UDB 集成,以增强直接在 DB2 UDB 数据库系统中管理和处理静态图像的功能。文中将描述实现把 DB2 连接到库的用户定义函数的一些步骤,展示如何编译这些 UDF 并将它们链接在一起。最后,您将看到一个完整的、基于库的、用于 DB2 的静态图像扩展。文中还将给出一些代码示例。
简介
很多应用程序需要在数据库中存储和管理数字图像。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;
}
|
函数 SI_getHeight
除了实际的图像操纵操作外,我们的静态图像扩展还支持一组用于获取图像某些属性的函数。例如,每个图像都有其高度和宽度,以像素为单位。高度等于图像中的行数。清单 2 中显示了 SI_getHeight UDF 的 C++ 源代码。
ImageMagick 带有函数 PingBlob。这个函数只读图像的头部,提取高度、宽度、颜色空间等属性。ping 图像的一大好处是,我们只需要访问图像数据的一部分,即头部。
函数 InitializeScratchWithPing (在下面的清单中以斜体显示)比较常用,它只从 BLOB 定位符读图像的开始部分,并调用 ImageMagick 函数 PingBlob ping 图像。当然,您不知道这个头部到底有多大,有时候从定位符选取的开始部分可能太小。所以,如果 ping 操作失败,就选择大一点的部分,再重新 ping 一次。如果再次失败,那么就放弃尝试,获取整个图像数据来构造 ImageMagick 对象。幸运的是,在实际中这一招杀手锏很少用得上。
当 scratchpad 上有了 ImageMagick 对象时,便只需访问 ImageMagick 对象,并收集关于图像高度的信息。相应的代码在清单中以粗体显示。
清单 2. 关于 UDF SI_getHeight 的示例代码
/** Get the image height.
*
* Get the image height from the BLOB (locator) provided as input parameter.
*/
IEX_EXTERNC
void SQL_API_FN IexGetImageHeight(
// input: locator to image
SQLUDF_LOCATOR *imageLocator,
// output: height of the image
SQLUDF_INTEGER *imageHeight,
// null indicators
SQLUDF_NULLIND *imageLocator_ind,
SQLUDF_NULLIND *imageHeight_ind,
SQLUDF_TRAIL_ARGS_ALL)
{
int rc = IEX_SUCCESS;
IexError error;
// we assume NULL result
*imageHeight_ind = -1;
// map the scratchpad
struct scratchMap *scratch = (struct scratchMap *)SQLUDF_SCRAT->data;
rc = InitializeScratchWithPing(imageLocator, *imageLocator_ind, scratch,
SQLUDF_CALLT, error);
if (rc || !scratch->image) {
goto cleanup;
}
// we got a result; set output parameter and null indicator
*imageHeight = scratch->image->magick_rows;
*imageHeight_ind = 0;
cleanup:
IEX_COMMON_CLEANUP;
}
|
构建(Building)函数
本节解释配置、编译和链接代码与 ImageMagick 库的步骤。在系统上安装了 ImageMagick 库之后,便可以编译 UDF 并将它们链接到一个共享库,以便 DB2 在数据库中处理 SQL 语句时能调用这些 UDF。当然,如果已经安装了 ImageMagick,那么可以忽略第一步。这种情况在 Linux 系统上经常出现,因为在 Linux 上只需安装 ImageMagick 包。请确信那些必要的头文件——特别是文件“magick/api.h”——是可用的,否则将无法成功地编译 UDF 源代码。
构建 ImageMagick
下载和解压 ImageMagick 的源代码之后,在 ImageMagick 目录中可以发现一个名为 INSTALL 的文件。这个文件详细描述了在系统上编译和安装 ImageMagick 的过程和需求。您应该以那里给出的步骤为准,因为信息会随着 ImageMagick 的每一个新版本而更新。
ImageMagick 依赖各种不同的库来处理特定的图像格式。例如,libjpeg 库用于根据 JPEG 2000 标准解码和编码 JPEG 图像,libpng 库处理 Portable Network Graphics (PNG) 格式的图像。取决于将要使用的格式,您还需要安装相应的那些库。请参考 ImageMagick 文档了解更多细节。
构建 UDF
在下载小节中可以找到这些 UDF 的源代码。代码被分成多个文件,以便将类似的功能组织在一起,并将整个项目模块化。这种模块化以及 ImageMagick 库的链接都使您不能不作更改就使用 bldrtn 脚本(该脚本在 sqllib/samples/c/ 目录中)。我们通过一个例子,使用该脚本的 Linux 版本来解释需要应用哪些必要的更改。清单 3 显示了修改后的脚本,修改过的部分以斜体显示。
清单 3. 对 bldrtn 脚本的修改
########################################################
# SCRIPT: bldrtn
# Builds Linux C routines (stored procedures or UDFs)
# Usage: bldrtn <prog_name> [ <db_name> ]
# Set DB2PATH to where DB2 will be accessed.
# The default is the standard instance path.
DB2PATH=$HOME/sqllib
# Default compiler/linker settings
LIB="lib"
EXTRA_C_FLAGS=""
# Determine our bitwidth (32 or 64) and hardware platform
BITWIDTH=`LANG=C db2level | awk '/bits/{print $5}'`
HARDWAREPLAT=`uname -m`
if [ $BITWIDTH = "\"32\"" ]
then
LIB="lib32"
fi
# Set up compiler switches according to the current environment
if [ "$HARDWAREPLAT" = "x86_64" ] || [ "$HARDWAREPLAT" = "ppc64" ]
then
if [ $BITWIDTH = "\"64\"" ]
then
EXTRA_C_FLAGS="-m64"
else
if [ "$HARDWAREPLAT" != "ppc64" ]
then
EXTRA_C_FLAGS="-m32"
fi
fi
fi
# Set the runtime path.
EXTRA_LFLAG="-Wl,-rpath,$DB2PATH/$LIB"
# If an embedded SQL program, precompile and bind it.
if [ -f $1".sqc" ]
then
./embprep $1 $2
fi
# Compile the program.
for i in IexAttributes.cpp IexCatalog.cpp IexError.cpp IexManipulation.cpp IexUdfUtils.cpp IexImageBlob.cpp IexDisplay.cpp; do
gcc $EXTRA_C_FLAGS -fpic -I$DB2PATH/include -c $i -D_REENTRANT
done
# Link the program and create a shared library
gcc $EXTRA_C_FLAGS -shared -o imageudfs *.o $EXTRA_LFLAG -L$DB2PATH/$LIB -ldb2 -lpthread -ldb2apie -lMagick
# Copy the shared library to 函数 subdirectory.
# The user must have write permission to this directory.
rm -f $DB2PATH/function/imageudfs
cp imageudfs $DB2PATH/function
|
修改脚本之后,只需输入 ./bldrtn 便可以开始构建过程。最终的共享库放在 DB2 实例的 sqllib/function/ 目录中。这就对了。现在只需注册所有函数,这个过程将在下一节描述。
SQL 接口
在简介中已经提到,我们定义了两种不同的 SQL 接口[5]。第一种是基于 BLOB 的,这是非常轻量级的一种。它只处理编码实际图像数据的 BLOB,而不会创建任何特殊的图像数据格式。第二种接口建立在第一种接口的基础之上,它提供了特殊的数据类型 SI_StillImage。这样的数据类型及其方法在“SQL/MM Part 5: Still Image”standard [3] 中也有定义。
在使用前面小节中解释的函数之前,必须将它们注册到数据库中。为此,需分别应用脚本 create_blob.sql 和 create_sqlmm.sql。请注意,这两个脚本使用‘@’作为 SQL 语句的终止符。
清单 4. 执行 SQL 脚本来注册 UDF
$ db2 "connect to <your-database>"
$ db2 -td@ -f create_blob.sql
$ db2 -td@ -f create_sqlmm.sql
$ db2 "connect reset"
|
如果您对 SQL/MM 接口不感兴趣,那么不需要运行第二个 SQL 脚本。然而,即使您不想使用 BLOB 接口,第一个脚本仍是必需的,因为 SQL/MM 接口是建立在 BLOB 函数的基础上的。
处理 BLOB 形式的图像
下面的函数用于获取图像的属性。每个 UDF 的输入参数是一个 BLOB,这个 BLOB 包含了图像。在 IexAttributes.cpp 文件中可以找到所有这些函数的 C++ 代码,该文件在下载小节中可以找到。
表 1. 用于收集图像属性的函数列表
| UDF |
返回类型 |
描述 |
| SI_getHeight(image BLOB) |
INTEGER |
获得图像的高度,以像素为单位 |
| SI_getWidth(image BLOB) |
INTEGER |
获得图像的宽度,以像素为单位 |
| SI_getXResolution(image BLOB) |
DOUBLE |
获得图像水平方向的分辨率,以 DPI 为单位 |
| SI_getYResolution(image BLOB) |
DOUBLE |
获得图像垂直方向的分辨率,,以 DPI 为单位 |
| SI_getFormat(image BLOB) |
VARCHAR(128) |
获得图像格式的名称 |
| SI_getCompression(image BLOB) |
VARCHAR(128) |
获得图像压缩模式的名称 |
| SI_getColorSpace(image BLOB) |
VARCHAR(128) |
获得图像颜色空间的名称 |
| SI_getNumColors(image BLOB) |
INTEGER |
获得图像中用于所有像素点的颜色的数量 |
| SI_getImageAttr(image BLOB) |
Table ( width INTEGER, height INTEGER, xResolution DOUBLE, yResolution DOUBLE, format VARCHAR(128), compression VARCHAR(128), colorSpace VARCHAR(128) ) |
以只包含一行的表的形式返回所有图像属性,颜色的数量除外 |
注: 函数 SI_getNumColors 要求读取整个图像数据,因为需要访问所有的像素点。因此,仅仅对图像执行 ping 操作不足以获取这样的信息。所有其他函数则只需通过 ping 操作,即可获得所需的结果,如前面描述的那样。
考虑到您可能有兴趣一次获取多个属性,我们实现了如上面的表所示的表函数 SI_getImageAttr,用于返回一个给定图像的所有属性(颜色数量除外)。单独的函数拥有更好的性能,因为(a)减少了一些函数调用,因此在 DB2 中所执行的代码路径就更短,并且,(b)在 ImageMagick 中的处理只发生一次。
大部分函数都是以各种不同方式操纵一副图像的。所有那些函数都以一副图像(BLOB)作为输入参数,并且,必要时还有一组对各自操作有影响的参数。最后,每个 UDF 返回一个 BLOB,表示新的图像。下面的表列出了所有那些函数,并对参数进行了描述。
表 2. 图像操纵函数列表
| UDF |
参数 |
描述 |
|
SI_blur(image BLOB, stdDeviation DOUBLE) &
SI_blur(image BLOB, radius DOUBLE, stdDeviation DOUBLE)
|
| stdDeviation |
Gaussian 操作符的标准偏差 |
| radius |
Gaussian 操作符的半径。如果省略了这个半径,则由函数决定合适的半径 |
|
模糊化图像。该操作用一个具有给定半径和标准偏差的 Gaussian 操作符盘绕图像。半径的值应该大于标准偏差。如果半径和标准偏差都是 NULL 或 0(零),则图像会原封不动地返回 |
| SI_convertFormat(image BLOB, newFormat VARCHAR) |
| newFormat |
转换后图像将被编码成这个目标格式 |
|
将图像从当前格式转换成指定的格式。
新格式必须是受支持的格式,在编目表 SI_SUPPORTED_FORMATS 中其相应的 ENCODE 标志必须是‘Y’。如果在 Image Extender 编目中没有这种格式,或者其 ENCODE 标志没有被设为‘Y’,那么就会产生异常状况(SQLSTATE 38IUx)
|
| SI_crop(image BLOB, width INTEGER, height INTEGER, xOffset INTEGER, yOffset INTEGER) |
| width & height |
要从图像中获取的区域的大小 |
| xOffset & yOffset |
相对于图像左上角的偏移量,要从图像中获取的区域的左上角从这个地方开始 |
|
从给定的图像中裁剪出一片区域
如果裁剪的区域超出了图像的范围,就会产生异常状况(SQLSTATE 38IU5)
|
|
SI_detectEdges(image BLOB) &
SI_detectEdges(image BLOB, radius DOUBLE)
|
| radius |
旋转过滤器(convolution filter)的半径,以像素为单位。如果这个半径没有给出,而是被设为 NULL 或 0 (零),那么函数就会自己决定一个合适的半径。这个半径必须小于图像的高度和宽度 |
|
使用具有指定半径的旋转过滤器探测出图像中的边界
如果这个半径小于 0,那么就会产生一个异常状况(SQLSTATE 38IUx)
|
| SI_flip(image BLOB) |
|
顺着图像中间的水平轴翻转图像。此操作完成后,图像会颠倒过来 |
| SI_flop(image BLOB) |
|
顺着图像中间的垂直轴翻转图像。此操作完成后,图像会像在镜子中一样左右颠倒。 |
| SI_invert(image BLOB) |
|
将图像中个像素点的颜色变成其补色,从而使图像倒转 |
| SI_reduceNumColors(image BLOB, numColors INTEGER) |
| numColors |
产生的图像中最多可以使用的颜色数量 |
|
将图像中的颜色数量减少到指定的数量。新颜色的计算使用 YUV 颜色空间来进行。
如果颜色数量小于 1,那么将产生一个异常状况(SQLSTATE 38IUx)
|
|
SI_monochrome(image BLOB) &
SI_monochrome(image BLOB, threshold DOUBLE)
|
| threshold |
用于与每个像素点的亮度进行比较的阈值。最大可能的阈值是 131,070 |
|
将给定的图像转换成单色图像。计算每个像素点的亮度,并将其与给定的阈值相比较。如果像素点的亮度低于阈值,则该象素点将变成黑色,反之,就变成白色。阈值越高,图像中就有越多的部分变成黑色。
如果阈值小于 0,或者大于 1,那么将产生一个异常状况(SQLSTATE 38IUx)。
每个像素点的亮度是根据它的 RGB 值计算的。红色部分和蓝色部分的权重占 0.114,绿色部分的权重占 0.587。将 R、G 和 B 三部分的值分别乘于相应的权重,三个积加起来就得到亮度。如果没有给出阈值,或者给出的阈值为 NULL,那么就会隐式地使用缺省的 25,000 作为阈值
|
|
SI_resize(image BLOB, width INTEGER, height INTEGER)
SI_resize(image BLOB, width INTEGER, height INTEGER, method VARCHAR)
|
| width & height |
新图像的大小 |
| method |
method 参数定义用于大小调整操作的算法(也称过滤器)。算法将影响所得图像的质量,同时对执行操作所需的时间也有影响。这个参数必须是以下字符串之一:
- Bessel
- Blackman
- Box
- Catrom
- Cubic
- Gaussian
- Hanning
- Hermite
- Lanczos
- Mitchell
- Point
- Quadratic
- Sinc
- Triangle
如果没有指定 method,则自动使用“Point”。如果指定了该参数,但参数的值不是上述列表中的任何一个值,那么将产生一个异常状况(SQLSTATE 38IU6) |
|
将图像大小调整到给定的宽度和高度。在处理过程中,图像的高宽比可能发生变化(这与 SI_scale 不同)。这样产生的图像可能变得更大,也可能变得更小,这取决于参数 |
| SI_roll(image BLOB, xOffset INTEGER, yOffset INTEGER) |
| xOffset |
图像沿水平方向滚动的像素数。如果该参数为正数,则图像向右滚动,如果该参数为负数,则图像向左滚动 |
| yOffset |
图像沿垂直方向滚动的像素数。如果该参数为正数,则图像向下滚动,如果该参数为负数,则图像向上滚动 |
|
沿 X 和/或 Y 轴使图像滚动指定的像素数。滚动图像意味着一定数量的行或列表从图像的一端移动到另一端。如果 X 和 Y 偏移量都是 NULL 或 0,或者分别是图像宽度和/或高度的倍数,则图像保持不变 |
| SI_rotate(image BLOB, angle DOUBLE) |
| angle |
旋转图像时所依据的角度。angle 可以为任意值,但必须以度的形式指定。如果 angle 为正数,那么图像按逆时针方向旋转,如果该参数为负数,那么图像按顺时针方向旋转 |
|
按给定角度旋转图像 |
|
SI_scale(image BLOB, factor DOUBLE)
SI_scale(image BLOB, factor DOULE, method VARCHAR)
|
| factor |
缩放图像时所依据的系数 |
| method |
method 参数定义用于缩放操作的算法(也称过滤器)。算法将影响所得图像的质量,同时对执行该操作所需的时间也有影响。此参数必须是以下字符串中的一个:
- Bessel
- Blackman
- Box
- Catrom
- Cubic
- Gaussian
- Hanning
- Hermite
- Lanczos
- Mitchell
- Point
- Quadratic
- Sinc
- Triangle
如果没有指定 method,则自动使用“Point”。如果指定了这个参数,但参数的值不是上述列表中的任何一个,那么将产生异常状况(SQLSTATE 38IU6) |
|
按照给定系数缩放图像。图像的高宽比保持不变。如果该系数小于 1,那么图像会变小。如果该系数大于 1,那么图像会变大。如果该系数等于 1,则图像保持不变 |
|
SI_sharpen(image BLOB, stdDeviation DOUBLE) &
SI_sharpen(image BLOB, radius DOUBLE, stdDeviation DOUBLE)
|
| stdDeviation |
Gaussian 操作符的标准偏差 |
| radius |
Gaussian 操作符的半径。如果省略了这个半径,则由函数自己决定一个合适的半径 |
|
锐化图像。该操作用一个具有给定半径和标准偏差的 Gaussian 操作符盘绕图像。半径的值应该大于标准偏差。如果半径和标准偏差的值都是 NULL 或 0,则图像保持不变 |
| SI_shear(image BLOB, xShear DOUBLE, yShear DOUBLE) |
| xShear |
沿水平方向剪切图像时所依据的角度。这个角度可以是任意的值,但必须以度的形式指定。如果角度为正数,则图像的左上角会向右移动,如果角度为负数,则图像的左上角会向左移动(或左下角向右移动) |
| yShear |
沿垂直方向剪切图像时所依据的角度。这个角度可以是任意的值,但必须以度的形式指定。如果角度为正数,则图像的左上角向上移动,如果角度为负数,则图像的左上角向下移动 |
|
沿 X 和 Y 方向按照给定的角度剪切图像。X 方向的剪切角是相对于 Y 轴计算的,Y 方向的剪切角是相对于 X 轴计算的。如果剪切角度为 0,或者等于 180 度的倍数,则那个方向上就不会发生剪切。请注意,X 或 Y 轴会将给定的剪切角分成两部分 |
实现 SQL/MM 接口
除了一组用于在数据库系统中处理静态图像的方法外,SQL/MM standard ISO/IEC 13249 的第 5 部分还定义了一种名为 SI_StillImage 的数据类型。前面提到,BLOB 接口不遵从这种标准。然而,基于 BLOB 的函数允许我们很容易地得到一种遵从标准的扩展器。在这一节中,我们将给出我们自己的方法。我们经常引用下载小节,在那个小节中可以找到所有用于设置数据库的 SQL 脚本(以及其他的代码)。
我们定义了一个结构类型,用于封装一个图像、图像的属性以及适当的方法,如清单 5 所示。这种类型必须能够存储图像数据,所以它需要一个 BLOB 类型的属性。此外,我们添加了关于图像的高度、宽度、格式、压缩模式和颜色空间的属性。从功能的角度来看,这些属性不是必需的。但是如果要经常用到图像的宽度或格式的话,当然是直接访问属性,比调用 UDF 从原始图像数据中提取所需的信息要快些。
清单 5. 数据类型 SI_StillImage
CREATE TYPE image.SI_StillImage
AS (
image_data BLOB(10M),
height INTEGER,
width INTEGER,
format VARCHAR(128),
compression VARCHAR(128),
color_space VARCHAR(128)
)
INSTANTIABLE
NOT FINAL
MODE DB2SQL
REF USING BIGINT@
|
定义好数据类型之后,便可以添加方法。为了演示,我们定义了查询图像高度的方法,另外还添加了一个按给定角度旋转图像的方法,如清单 6 所示。旋转方法将在内部使用函数 SI_rotate(BLOB, DOUBLE);此外,它必须确保结构类型中所有的属性都适当更新。
清单 6. 用于 SI_StillImage 的方法的定义
ALTER TYPE image.SI_StillImage
ADD METHOD SI_getHeight()
RETURNS INTEGER
SPECIFIC image.SQLMM_getHeight
LANGUAGE SQL
DETERMINISTIC
NO EXTERNAL ACTION
CONTAINS SQL
ADD METHOD SI_rotate(angle DOUBLE)
RETURNS INTEGER
SPECIFIC image.SQLMM_rotate
SELF AS RESULT
LANGUAGE SQL
DETERMINISTIC
NO EXTERNAL ACTION
CONTAINS SQL@
CREATE METHOD SI_getHeight()
FOR image.SI_StillImage
RETURN SELF..height@
CREATE METHOD SI_rotate(angle DOUBLE)
FOR image.SI_StillImage
RETURN SELECT SELF..image_data(result.image)..
height(attr.height)..width(attr.width)..
format(attr.format)..compression(attr.compression)..
color_space(attr.colorSpace)
FROM TABLE ( VALUES ( SI_rotate(SELF..image_data, angle) ) )
AS result(image),
TABLE ( SI_getImageAttr(result.image) ) AS attr
WHERE SELF IS NOT NULL@
|
在方法 SI_rotate 的实现中,VALUES 构造函数以来自源参数(SELF)的图像数据为参数,并将它传递给 SI_rotate 函数,另外一个参数是角度。最终得到的 BLOB 是旋转后的图像,它被传递给表函数 SI_getImageAttr,以便提取图像属性。新 SI_StillImage 对象的构造发生在 SELECT 子句中,在这里 SI_StillImage 数据类型的各个属性都被设置了新的值。
SQL/MM 标准的其他函数和方法也可以用类似的代码实现。但是请注意,如果只调用一次外部代码来执行图像操纵和提取属性,那么可以取得更好的性能。有兴趣的读者可以自己编写这样的代码,作为一次练习。
用于 SQL/MM 支持的最后一部分是构造函数的实现。DB2 还不支持本地的构造方法,所以我们借助函数来完成同样的任务。构造函数带有必要的输入参数,并返回 SI_StillImage 类型的一个值。构造好这个值之后,可以将它存储在 SI_StillImage 类型的一个列中,并且/或者调用为这种类型定义的方法。清单 7 展示了这个构造函数,在这里我们再次使用了为 BLOB 接口实现的功能。
清单 7. 定义构造函数
CREATE FUNCTION image.SI_StillImage(image BLOB)
RETURNS image.SI_StillImage
SPECIFIC image.SI_StillImage
LANGUAGE SQL
DETERMINISTIC
NO ExTERNAL ACTION
CONTAINS SQL
RETURN SELECT SI_StillImage()..image_data(image)..
height(attr.height)..width(attr.width)..
format(attr.format)..compression(attr.compression)..
color_space(attr.colorSpace)
FROM TABLE ( SI_getImageAttr(image) ) AS attr
WHERE image IS NOT NULL@
|
为了完整起见,同时也作为一种参考,我在表 3 中提供了受支持的方法和构造函数的完整列表。您将注意到,这些方法和前面列出的用于操作 BLOB 的函数在名称上有很多相似之处。那些对应的方法和函数在功能上是一致的,前面的表 1 和表 2 给出了对各个参数的描述。默认情况下,SI_StillImage 值在每个方法中都是可用的,它就是所谓的源参数(subject parameter),即 SELF,因而图像数据也是可用的。
表 3. SI_StillImage 类型的方法列表
| 方法标签 |
返回类型 |
| SI_getHeight() |
INTEGER |
| SI_getWidth() |
INTEGER |
| SI_getXResolution() |
DOUBLE |
| SI_getYResolution() |
DOUBLE |
| SI_getFormat() |
VARCHAR(128) |
| SI_getCompression() |
VARCHAR(128) |
| SI_getColorSpace() |
VARCHAR(128) |
| SI_getNumColors() |
INTEGER |
| SI_blur(stdDeviation DOUBLE) |
SI_StillImage |
| SI_blur(radius DOUBLE, stdDeviation DOUBLE) |
SI_StillImage |
| SI_convertFormat(newFormat VARCHAR(128)) |
SI_StillImage |
| SI_crop(width INTEGER, height INTEGER, xOffset INTEGER, yOffset INTEGER) |
SI_StillImage |
| SI_detectEdges() |
SI_StillImage |
| SI_detectEdges(radius DOUBLE) |
SI_StillImage |
| SI_flip() |
SI_StillImage |
| SI_flop() |
SI_StillImage |
| SI_invert() |
SI_StillImage |
| SI_reduceNumColors(numColors INTEGER) |
SI_StillImage |
| SI_monochrome() |
SI_StillImage |
| SI_monochrome(threshold DOUBLE) |
SI_StillImage |
| SI_resize(width INTEGER, height INTEGER) |
SI_StillImage |
| SI_resize(width INTEGER, height INTEGER, method VARCHAR(128)) |
SI_StillImage |
| SI_roll(xOffset INTEGER, yOffset INTEGER) |
SI_StillImage |
| SI_rotate(angle DOUBLE) |
SI_StillImage |
| SI_scale(factor DOUBLE) |
SI_StillImage |
| SI_scale(factor DOUBLE, method VARCHAR(128)) |
SI_StillImage |
| SI_sharpen(stdDeviation DOUBLE) |
SI_StillImage |
| SI_sharpen(radius DOUBLE, stdDeviation DOUBLE) |
SI_StillImage |
| SI_shear(xShear DOUBLE, yShear DOUBLE) |
SI_StillImage |
catalog
对于这种图像扩展器,有一种信息很重要,那就是受支持的图像格式。于是这里提供了一个名为 SI_SUPPORTED_FORMATS 的 catalog 视图,以便将该信息反映给用户。这个视图显示每种格式的标识符,即格式的名称,以及 ImageMagick 是否能够解码(读)和编码(写)特定的格式。如果一种格式可以被解码和/或编码,那么相应的值就是‘Y’,否则这个值就是‘N’。接着后面给出了对每种格式的一个简短的描述。这个视图的结构如下。
清单 8. catalog 视图 SI_SUPPORTED_FORMATS
IMAGE.SI_SUPPORTED_FORMATS(
format VARCHAR(128),
decode CHARACTER(1),
encode CHARACTER(1),
description VARCHAR(254)
)
|
在内部,这个视图基于一个表函数。这个函数的代码可以在文件 IexCatalog.cpp 中找到。它通过查询 ImageMagick 获得所有可用的格式,并将返回的信息转换到前面描述的表结构中。
对该函数的 OPEN 调用可以获得指向一个 MagickInfo 结构数组的指针,并将那个指针放在 scratchpad 上,另外加上关于数组中元素个数的信息。随后通过 FETCH 调用逐个获得格式指示,同时根据相应的解码和编码函数指针获得相关的描述和解码/编码信息。然后,该 UDF 进入下一个 MagickInfo 结构,进行下一次 FETCH 调用。如果没有更多的格式可供处理,则函数 SQLSTATE '02000',通知 DB2 函数不会继续返回行,对这个表的处理已经完成。最后一步是 FINAL 调用,在这里需要释放 MagickInfo 结构数组,以免出现内存泄漏。
示例场景
完成所有这些解释之后,最后给出一些例子来演示前面描述的功能的确可用。为此,我们需要一些图像,以便将它们装载到数据库。您可以使用自己的应用程序或者 DB2 IMPORT 或 LOAD 命令将图像从文件装载到数据库中的 BLOB 中。或者也可以使用我们提供的两个函数,SI_loadImage(VARCHAR(256)) 和 SI_exportImage(BLOB, VARCHAR(256))。第一个函数以一个完整文件名作为输入参数,打开被引用的文件,并以 BLOB 的形式返回文件的内容。因此,您只需从数据库服务器上的文件中装载图像。第二个函数以一个 BLOB 和一个文件名作为输入,并将 BLOB 写到指定的文件。通过这种方式,您可以修改图像,导出图像,并使用任何外部图像浏览器观察结果。而且,函数 SI_display(BLOB, VARCHAR(255)) 也是可用的,该函数使用一个 X 连接在客户机的屏幕上打开一个窗口(在第二个参数中指定)。这个窗口将显示给定的图像。在该窗口获得焦点并在其中输入‘q’时,它将被关闭,UDF 也随之返回。为了在数据库中注册这三个函数,可以运行脚本 create_load.sql 和 create_display.sql,如清单 9 所示。注意,第二个脚本为 SI_StillImage 类型提供了一个方法。因此,应该首先执行 create_blob.sql 和 create_sqlmm.sql。或者,也可以修改这个脚本,去掉与 SQL/MM 相关的功能。
清单 9. 注册测试函数
$ db2 "connect to <your-database>"
$ db2 -td@ -f create_load.sql
$ db2 -td@ -f create_display.sql
$ db2 "connect reset"
|
注意,我们将实现的所有函数、表和视图都放在模式 IMAGE 中。因此,要么将所有函数冠以那个模式名,要么就必须将 DB2 专用寄存器 CURRENT FUNCTION PATH 设置成包括前面提到的模式。这两种方法都可以让 DB2 找到我们想要调用的函数。
首先,我们获得所有受支持图像格式的列表。前面已经解释过,catalog 视图提供了那样的信息。在清单 10 的输出中可以看到,我们安装的 ImageMagick 库支持很多不同的格式,包括这些格式的一些不同版本,例如 BMP。
清单 10. 测试 Image Catalog
$ db2 -td@
db2 => SELECT COUNT(*) FROM image.si_supported_formats@
1
-----------
147
1 record(s) selected.
db2 => SELECT * FROM image.si_supported_formats FETCH FIRST 13 ROWS ONLY@
FORMAT ENCODE DECODE DESCRIPTION
---------- ------ ------ --------------------------------------
A Y Y Raw alpha samples
ART N Y PFS: 1st Publisher
AVI N Y Microsoft® Audio/Visual Interleaved
AVS Y Y AVS X image
B Y Y Raw blue samples
BMP Y Y Microsoft Windows® bitmap image
BMP2 Y N Microsoft Windows bitmap image v2
BMP3 Y N Microsoft Windows bitmap image v3
C Y Y Raw cyan samples
CAPTION N Y Image caption
CIN Y Y Cineon Image File
CIP Y N Cisco IP phone image format
CLIP Y N Image Clip Mask
13 record(s) selected.
|
下面的一组函数从图像本身提取属性。为此,在数据库中实际上需要有一个 BLOB 形式的可用图像。我们使用 SI_loadImage 函数装载一个图像,并应用这些函数获得属性信息。注意,清单 11 展示了直接对 LOB 操作的函数,此外,还有基于 SI_StillImage 数据类型的遵从 SQL/MM 的接口。用于测试的图像有:
图 1. 样本图像 test_1.jpg

图 2. 样本图像 test_2.gif

清单 11. 测试用于从图像获取属性的函数
$ db2 -td@
db2 => SET CURRENT FUNCTION PATH = image, CURRENT FUNCTION PATH@
db2 => VALUES SI_getHeight(SI_loadImage('/home/stolze/test_1.jpg'))@
1
-----------
128
1 record(s) selected.
db2 => VALUES SI_getFormat(SI_loadImage('/home/stolze/test_2.gif'))@
1
------------------------
GIF
1 record(s) selected.
db2 => VALUES SI_StillImage(
db2 (cont.) => SI_loadImage('/home/stolze/test_2.gif'))..SI_getWidth()@
1
-----------
481
1 record(s) selected.
db2 => VALUES SI_StillImage(
db2 (cont.) => SI_loadImage('/home/stolze/test_1.jpg'))..SI_getYResolution()@
1
------------------------
+1.11000000000000E+002
1 record(s) selected.
|
至此一切顺利,现在我们可以将目光投向一些更高级的函数,这些函数实际操纵图像的内容。在清单 11 和清单 12 中,我们采用两种不同的方法来显示结果。在第一种方法中,我们应用 SI_display UDF 在客户机上弹出一个窗口,在第二种方法中,产生的图像将被写到另一个文件,并使用一个外部图像浏览器来检查前面操作的结果。
清单 12. 测试使用 BLOB 的图像操纵函数
$ db2 -td@
db2 => SET CURRENT FUNCTION PATH = image, CURRENT FUNCTION PATH@
db2 => CREATE TABLE t1 ( id INTEGER, img BLOB(10M) )@
db2 => INSERT INTO t1 VALUES ( 1, SI_loadImage('/home/stolze/test_1.jpg') )@
db2 => UPDATE t1 SET img = SI_rotate(img, 45) WHERE id = 1@
db2 => SELECT SI_exportImage(img, '/home/stolze/test_1.jpg') FROM t1 WHERE id = 1@
1
-----------
10237
1 record(s) selected.
db2 => QUIT@
$ xv /home/stolze/res_1.jpg
|
如果执行顺利,得到的应该是一个 181 乘 181 像素的图像,如图 3 所示。
图 3. 旋转后的样本图像

清单 13. 测试 SI_StillImage 及其方法
$ db2 -td@
db2 => SET CURRENT FUNCTION PATH = image, CURRENT FUNCTION PATH@
db2 => CREATE TABLE t2 ( id INTEGER, img image.SI_StillImage )@
db2 => INSERT INTO t2 VALUES ( 1, SI_StillImage(
db2 (cont.) => SI_loadImage('/home/stolze/test_2.gif')) )@
db2 => SELECT img..SI_shear(10, 0)..SI_roll(50, 0)..SI_display()
db2 (cont.) => FROM t2 WHERE id = 1@
1
-----------
0
1 record(s) selected.
|
在前面的示例场景中,当处理从 SELECT 语句返回的每一行时,将弹出一个窗口。在这个窗口中,应该可以看到如图 4 所示的一个图像。这个图像首先沿 X 轴剪切,然后向右滚动,即,图像中有 50 列从右边移到了左边。如果通过单击 q 或者从菜单(在窗口中单击右键弹出)中选择 Quit 选项关闭该窗口,则 UDF 将终止,并返回 0。如果要处理另外一行,那么将打开一个新的窗口,下一个图像就显示在该窗口中。但是我们只使用了一个图像。注意,所有可用的菜单都源于 ImageMagick。
图 4. 剪切和滚动后的样本图像

结束语
在本文中,您看到了将一个外部的库集成到 DB2 数据库中的一种方法。通过使用一个例子,我们实现了一个静态图像扩展器。我们还解释了两个不同的接口。第一个接口非常简单,只处理 BLOB 形式的图像。第二个接口建立在第一个接口的基础之上,并遵从 SQL/MM Still Image 标准。本文包括完整的源代码,并给出了构建真正实现特定于图像的功能的共享库的说明。
参考资料
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- [1] ImageMagick 是用于本文给出的静态图像扩展的图像操纵库。
- [2] IBM 提供了 DB2 UDB Audio、Image 和 Video 包,DB2 Image Extender 是其中具有不同范围的一部分。
- [3] ISO/IEC 13249-5 SQL/MM Part 5: Still Image 标准定义了用于关系数据库中静态图像扩展的接口。
- [4] ISO/IEC 15444 JPEG 2000 标准规定 JPEG/JFIF 格式的图像的编码。
- [5] DB2 SQL Reference 规定 DB2 UDB 中可用的功能。
- [6] DB2 Application Development Guide 解释了关于如何实现本文给出的用户定义函数、类型和方法的细节。
下载
|
描述 |
|
 |
|
Name |
|
 |
|
Size |
|
 |
|
Download method |
|
 |
|
ImageMagick Extender for DB2 UDB souce code |
|
 |
|
image-ext.zip |
|
 |
|
43 KB |
|
 |
|
FTP | HTTP |
|
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
 |
关于作者
Knut Stolze 从作为访问学者加入 IBM 硅谷实验室时就开始从事 DB2 的研究,在那里,他从事 DB2 Image Extender 开发工作。随后的两年多时间里,他转向 DB2 Spatial Extender Version 8,并负责几个增强功能,以提高 Extender 的可用性、性能以及与标准的兼容性。目前他的工作是德国耶拿大学的教学助理,并继续在联邦数据库方面为 IBM 工作。您可通过新闻组 comp.databases.ibm-db2 和 ibm.software.db2.udb.spatial 或 stolze@de.ibm.com 与他取得联系。 |