VB.NET 中的 ImageMagick






4.78/5 (23投票s)
2007 年 3 月 3 日
22分钟阅读

430304

33006
ImageMagick 是一个功能强大的图像处理工具,支持多种格式。在本文中,我们将开发一个 C++ 版的 ImageMagick 包装器,以将 Magick++ 的功能暴露给 .NET 应用程序。
- 下载 ImageMagickNET-Source.zip - 820.0 KB
- 下载 ImageMagickNET-DLLs-Q8.zip - 3,514.4 KB
- 下载 ImageMagickNET-DLLs-Q16.zip - 3,518.3 KB
引言

ImageMagick 是一个功能强大的图像处理工具,可识别多种图像格式,包括网络上常用的 JPG、GIF、PNG 格式。它的一些处理功能包括应用图形效果,例如炭笔画、高斯模糊、曝光反转(以及在 Adobe Photoshop 等专业图像处理软件中看到的一些效果)。
ImageMagick 通过一组命令行可执行文件公开其功能,可以通过指定开关和选项来应用效果。但它也通过其 MagickCore 和 MagickWand C 库以及 Magick++ C++ 库公开其功能,以便开发人员可以在其软件中利用其功能。ImageMagick 社区还开发了 Perl、Python、Java 等其他语言的接口。
在此练习中,我们创建了一个 .NET ImageMagick 包装器,作为对 ImageMagick 功能的研究。我们还尝试创建了一个 VB.NET 应用程序,该应用程序调用 .NET 包装器来访问 ImageMagick 功能。尽管已经存在一个 ImageMagick .NET 包装器,但我们创建了一个更接近 Magick++ 可用类的模型。由于 Magick++ 在 C++ 类和标准模板库中公开了 ImageMagick,因此在 .NET 和 C++ 之间封送类型变得不那么困难。
在开始之前,我们想强调一下 ImageMagick 现在支持的格式。(请注意,某些格式需要安装额外的插件。)
扩展名 | 模式 | 描述 | 注释 |
ART | R | PFS: 1st Publisher | 最初在 Macintosh (MacPaint?) 上使用的格式,后来用于 PFS: 1st Publisher 剪贴画。 |
AVI | R | Microsoft 音频/视频交错 | |
AVS | RW | AVS X 图像 | |
BMP | RW | Microsoft Windows 位图 | |
CGM | R | 计算机图形元文件 | 需要 ralcgm 来渲染 CGM 文件。 |
CIN | RW | 柯达 Cineon 图像格式 | 使用 -set 指定图像伽马或黑白点(例如 -set gamma 1.7, -set reference-black 95, -set reference-white 685)。 |
CMYK | RW | 原始青色、品红色、黄色和黑色样本 | 使用 -size 和 -depth 指定图像的宽度、高度和深度。要指定单精度浮点格式,请使用 -depth 32 -define quantum:format=floating-point。将深度设置为 64 以获取双精度浮点格式。 |
CMYKA | RW | 原始青色、品红色、黄色、黑色和 Alpha 样本 | 使用 -size 和 -depth 指定图像的宽度、高度和深度。要指定单精度浮点格式,请使用 -depth 32 -define quantum:format=floating-point。将深度设置为 64 以获取双精度浮点格式。 |
CUR | R | Microsoft 光标图标 | |
CUT | R | DR Halo | |
DCM | R | 医学数字成像和通信 (DICOM) 图像 | 医疗界用于 X 射线等图像。 |
DCX | RW | ZSoft IBM PC 多页画笔图像 | |
DIB | RW | Microsoft Windows 设备无关位图 | DIB 是没有 BMP 头部的 BMP 文件。用于支持 WMF 等复合格式中的嵌入图像。 |
DJVU | R | ||
DNG | R | 数字负片 | |
DOT | R | 图形可视化 | 使用 -define 指定布局引擎(例如 -define dot:layout-engine=neato)。 |
DPX | RW | SMPTE 数字电影交换 | 使用 -set 指定图像伽马或黑白点(例如 -set gamma 1.7, -set reference-black 95, -set reference-white 685)。 |
EMF | R | Microsoft 增强型图元文件 (32 位) | 仅适用于 Microsoft Windows。 |
EPDF | RW | 封装便携式文档格式 | |
EPI | RW | Adobe 封装 PostScript 交换格式 | 需要 Ghostscript 才能读取。 |
EPS | RW | Adobe 封装 PostScript | 需要 Ghostscript 才能读取。 |
EPS2 | W | Adobe Level II 封装 PostScript | 需要 Ghostscript 才能读取。 |
EPS3 | W | Adobe Level III 封装 PostScript | 需要 Ghostscript 才能读取。 |
EPSF | RW | Adobe 封装 PostScript | 需要 Ghostscript 才能读取。 |
EPSI | RW | Adobe 封装 PostScript 交换格式 | 需要 Ghostscript 才能读取。 |
EPT | RW | ||
OTB | RW | 空中位图 | |
P7 | RW | Xv 的 Visual Schnauzer 缩略图格式 | |
PALM | RW | Palm 像素图 | |
PAM | W | 通用二维位图格式 | |
PBM | RW | 便携式位图格式(黑白) | |
PCD | RW | Photo CD | 写入的最大分辨率为 768x512 像素,因为更大的图像需要哈夫曼压缩(不支持)。 |
PCDS | RW | Photo CD | 使用 sRGB 颜色表解码。 |
PCL | W | HP 页面控制语言 | 用于输出到 HP 激光打印机。 |
PCX | RW | ZSoft IBM PC 画笔文件 | |
PDB | RW | Palm 数据库 ImageViewer 格式 | |
RW | 便携式文档格式 | 需要 Ghostscript 才能读取。默认情况下,ImageMagick 将页面大小设置为 MediaBox。但是,某些 PDF 文件具有小于 MediaBox 的 CropBox,并且可能在 CropBox 之外包含空白、注册或裁剪标记。要强制 ImageMagick 使用 CropBox 而不是 MediaBox,请使用 -define(例如 -define pdf:use-cropbox=true)。 | |
PFA | R | Postscript Type 1 字体 (ASCII) | 作为文件打开会返回预览图像。 |
PFB | R | Postscript Type 1 字体 (二进制) | 作为文件打开会返回预览图像。 |
PGM | RW | 便携式灰度图格式(灰度) | |
PICON | RW | 个人图标 | |
PICT | RW | Apple Macintosh QuickDraw/PICT 文件 | |
PIX | R | Alias/Wavefront RLE 图像格式 | |
PNG | RW | 便携式网络图形 | 需要 libpng-1.0.2 或更高版本,推荐 libpng-1.2.5 或更高版本。 |
PNM | RW | 便携式任何图 | PNM 是一系列支持便携式位图 (PBM)、灰度图 (PGM) 和像素图 (PPM) 的格式。没有与 pnm 本身关联的文件格式。如果 PNM 用作输出格式说明符,则 ImageMagick 会自动选择最合适的格式来表示图像。默认是写入格式的二进制版本。使用 -compress none 写入格式的 ASCII 版本。 |
PPM | RW | 便携式像素图格式(彩色) | |
PS | RW | Adobe PostScript 文件 | 需要 Ghostscript 才能读取。 |
PS2 | RW | Adobe Level II PostScript 文件 | 需要 Ghostscript 才能读取。 |
PS3 | RW | Adobe Level III PostScript 文件 | 需要 Ghostscript 才能读取。 |
PSD | RW | Adobe Photoshop 位图文件 | |
PTIF | RW | 金字塔编码 TIFF | 多分辨率 TIFF,包含图像的 successively smaller versions,直至图标大小。在读取时可以通过 -size 选项指定所需的子图像大小。 |
PWP | R | Seattle File Works 多图像文件 | |
RAD | R | 辐射图像文件 | 需要安装 Radiance 软件包中的 ra_ppm。 |
RGB | RW | 原始红色、绿色和蓝色样本 | 使用 -size 和 -depth 指定图像的宽度、高度和深度。要指定单精度浮点格式,请使用 -depth 32 -define quantum:format=floating-point。将深度设置为 64 以获取双精度浮点格式。 |
RGBA | RW | 原始红色、绿色、蓝色和 Alpha 样本 | 使用 -size 和 -depth 指定图像的宽度、高度和深度。要指定单精度浮点格式,请使用 -depth 32 -define quantum:format=floating-point。将深度设置为 64 以获取双精度浮点格式。 |
RLA | R | Alias/Wavefront 图像文件 | |
RLE | R | 犹他行程编码图像文件 | |
SCT | R | Scitex 连续色调图片 | |
SFW | R | Seattle File Works 图像 | |
SGI | RW | Irix RGB 图像 | |
SHTML | W | 超文本标记语言客户端图像映射 | 用于根据蒙太奇输出或支持平铺图像(如 MIFF)的格式写入 HTML 可点击图像映射。 |
SUN | RW | SUN 光栅文件 | |
SVG | RW | 可伸缩矢量图形 | 需要 libxml2 和 freetype-2。请注意,SVG 是一个非常复杂的规范,因此支持尚不完整。 |
TGA | RW | Truevision Targa 图像 | 也称为 ICB、VDA 和 VST 格式。 |
TIFF | RW | 标记图像文件格式 | 也称为 TIF。需要 tiff-v3.6.1.tar.gz 或更高版本。使用 -define 指定每条纹的行数(例如 -define tiff:rows-per-strip=8)。要指定有符号格式,请使用 -define quantum:format signed。要指定单精度浮点格式,请使用 -depth 32 -define quantum:format=floating-point。将深度设置为 64 以获取双精度浮点格式。使用 -define tiff:photometric=min-is-black 或 -define tiff:photometric=min-is-white 切换双级图像的光度解释。例如,使用 -define tiff:alpha=unassociated 将额外样本指定为关联或非关联 Alpha。 |
TIM | R | PSX TIM 文件 | |
TTF | R | TrueType 字体文件 | 需要 freetype 2。作为文件打开会返回预览图像。如果您不希望在缩放到设备像素后提示字形轮廓,请使用 -set(例如 -set type:hinting off)。 |
TXT | RW | 原始文本文件 | |
UIL | W | X-Motif UIL 表 | |
UYVY | RW | 交错 YUV 原始图像 | 使用 -size 和 -depth 命令行选项指定宽度和高度。使用 -sampling-factor 设置所需的子采样(例如 -sampling-factor 4:2:2)。 |
VICAR | RW | VICAR 光栅文件格式 | |
VIFF | RW | Khoros 可视化图像文件格式 | |
WBMP | RW | 无线位图 | 仅支持未压缩的单色。 |
WMF | R | Windows 元文件 | 需要 libwmf。默认情况下,使用元文件标头指定的尺寸渲染 WMF 文件。使用 -density 选项调整输出分辨率,从而调整输出大小。默认输出分辨率为 72DPI,因此 -density 144 会生成比默认大小大两倍的图像。使用 -background color 指定 WMF 背景颜色(默认白色)或 -texture filename 指定背景纹理图像。 |
WPG | R | Word Perfect 图形文件 | |
X | RW | 显示或导入图像到或从 X11 服务器 | 使用 -define 从根窗口获取图像(例如 -define x:screen=true)。 |
XBM | RW | X Windows 系统位图,仅限黑白 | X Windows 系统用于存储单色图标。 |
XCF | R | GIMP 图像 | |
XPM | RW | X Windows 系统像素图 | 也称为 PM。X Windows 系统用于存储彩色图标。 |
XWD | RW | X Windows 系统窗口转储 | X Windows 系统用于保存/显示屏幕转储。 |
YCbCr | RW | 原始 Y、Cb 和 Cr 样本 | 使用 -size 和 -depth 指定图像宽度、高度和深度。 |
YCbCrA | RW | 原始 Y、Cb、Cr 和 Alpha 样本 | 使用 -size 和 -depth 指定图像宽度、高度和深度。 |
YUV | RW | CCIR 601 4:1:1 | 使用 -size 和 -depth 命令行选项指定宽度、高度和深度。使用 -sampling-factor 设置所需的子采样(例如 -sampling-factor 4:2:2)。 |
编译源代码
我们已尽力使源代码的编译尽可能简单。但您的机器上需要设置以下环境
- 您必须有一个可用的 C: 盘。
- 您必须有 Visual Studio .NET 2005。
如果您没有,您仍然可以使用 Visual Studio 2005 Express Editions (C++ 和 Visual Basic),但您可能需要拆分 .SLN 文件。
由于某种原因,ImageMagick 6.3.2 无法从与执行应用程序相同的目录加载 IM_MOD*.DLL。相反,它总是引用注册表中的 IM_MOD*.DLL 文件路径。这可能会给那些希望在托管服务器上使用 ImageMagick 但无法访问托管服务器注册表的用户带来问题。因此,我获取了 ImageMagick 6.3.2 源代码并对其进行了非常小的修改,使其能够从执行应用程序目录读取 IM_MOD*.DLL。因此,在此更新版本中,您不再需要设置注册表中的任何内容。
要安装,请下载 ImageMagick-Source.zip、ImageMagick-DLLs-Q8.zip、ImageMagick-DLLs-Q16.zip 并将它们解压缩到同一文件夹位置。(我们为单独的文件道歉,因为 CodeProject 无法将它们全部作为单个大 zip 文件接受)如果您已正确解压缩它们,您的目录结构应该如下所示
Folder +-DLLs | +-DebugQ8 | +-DebugQ16 +-ImageMagickNET | +-include8 | +-include16 | +-lib8 | +-lib16 +-VBForms +-My Project您会注意到其中有一个批处理文件:install.bat。双击 install.bat 执行批处理文件。批处理文件执行以下操作
- 创建 DLLs\DebugQ8 的副本到 DLLs\ReleaseQ8,以及 DLLs\DebugQ16 的副本到 DLLs\ReleaseQ16。
完成后,在 Visual Studio .NET 中打开 ImageMagick.NET.sln 文件
- 有四种可能的编译配置:DebugQ8、DebugQ16、ReleaseQ8、ReleaseQ16。选择 Q8 或 Q16 进行编译。您之前是否在机器上设置过 ImageMagick 无关紧要。
- 重新生成整个解决方案。
- 运行 VB 演示。
使用 ImageMagick 的后续版本进行编译
如果您需要使用 ImageMagick 的后续版本编译 .NET 包装器,有几点需要注意。第一种方法
- 从以下位置下载 ImageMagick 的最新版本:https://imagemagick.org.cn/script/binary-releases.php。请记住下载 DLL 版本,而**不是**静态版本
- 获取最新的 *.h 文件并将其复制到 ImageMagickNET\include8 或 ImageMagickNET\include16 文件夹(取决于像素量化)
- 获取最新的 *.lib 文件并将其复制到 ImageMagickNET\lib8 或 ImageMagickNET\lib16
- 获取最新的 CORE_RL*.DLL、mfc71、msvcp71、msvcr71.dll 以及 ImageMagick 安装根文件夹中存在的所有其他非 IM_MOD_RL*.DLL。将这些文件复制到 DLLs\DebugQX 和 DLLs\ReleaseQX 文件夹中。VB 项目有一个后期构建事件,它会根据您选择的配置将相应的 DLL 复制到您的应用程序输出文件夹中。(请参阅下面关于 CORE_RL*.DLL 的说明)
- 从 ImageMagick 获取 IM_MOD_RL*.DLL 文件和 *.xml 文件,并将其复制到 C:\ImageMagick
以下注意事项对于开发环境的设置很重要,可能对希望将应用程序部署到生产站点的用户有用。
关于 CORE_RL*.DLL 的注意事项
运行应用程序时,请确保 CORE_RL*.DLL、msvcp71.dll、msvcr71.dll 和所有其他非 IM_MOD_RL*.DLL 的正确像素量化版本 Q8/Q16 位于**与您的应用程序相同的文件夹中**。这是为了确保您的应用程序直接从其当前路径加载 DLL,而不是从 ImageMagick 文件夹加载。
您可能会问,我们为什么要这样做?这是因为如果您的机器上同时安装了 Q8 和 Q16 版本的 ImageMagick,Windows 不会自动知道要查找哪个 CORE_RL*.DLL。我们相信它只会在当前应用程序目录中查找 DLL,然后按照 PATH 环境变量中指定的文件夹顺序查找。因此,如果您的机器上同时设置了 Q8 和 Q16,则 Windows 将始终查找 Q8 DLL 或 Q16 DLL。当使用错误的像素量化库时,这会导致您的应用程序崩溃。因此,将 CORE_RL*.DLL 复制到您的应用程序文件夹更安全。
关于 IM_MOD_RLL*.DLL 的注意事项
当您从安装程序设置最新的 ImageMagick 时,安装程序将自动在您的机器上设置一些注册表项。这些 DLL 负责解码和编码各种受支持的图像文件格式。现在,当 CORE_RL*.DLL 需要加载其中一个时,它**不会**(至少在 6.3.2 版本中)在当前应用程序文件夹中搜索它们。相反,它会在注册表项中 CoderModulesPath 键指定的文件夹中搜索它们。(这就是为什么我们必须重新编译 ImageMagick 源代码以使其能够在当前执行文件夹下查找)ImageMagick 注册表项,Q8 和 Q16 各一组,定义如下
[HKEY_LOCAL_MACHINE\SOFTWARE\ImageMagick\6.3.2\Q:8]
"BinPath"="C:\\Program Files\\ImageMagick-6.3.2-Q8"
"ConfigurePath"="C:\\Program Files\\ImageMagick-6.3.2-Q8\\config"
"LibPath"="C:\\Program Files\\ImageMagick-6.3.2-Q8"
"CoderModulesPath"="C:\\Program Files\\ImageMagick-6.3.2-Q8\\modules\\coders"
"FilterModulesPath"="C:\\Program Files\\ImageMagick-6.3.2-Q8\\modules\\filters"
[HKEY_LOCAL_MACHINE\SOFTWARE\ImageMagick\6.3.2\Q:16]
"BinPath"="C:\\Program Files\\ImageMagick-6.3.2-Q16"
"ConfigurePath"="C:\\Program Files\\ImageMagick-6.3.2-Q16\\config"
"LibPath"="C:\\Program Files\\ImageMagick-6.3.2-Q16"
"CoderModulesPath"="C:\\Program Files\\ImageMagick-6.3.2-Q16\\modules\\coders"
"FilterModulesPath"="C:\\Program Files\\ImageMagick-6.3.2-Q16\\modules\\filters"

托管 C++ 扩展
我们将通过托管扩展将 Magick++ 接口暴露给 .NET。但是由于 Magick++ 是用 C++ 编写的,我们将使用 C++ 创建 .NET 接口。请注意,我们使用的 C++ 托管扩展仅适用于 .NET Framework 2.0,因此文件只能在 Visual Studio 2005 上编译。
网上已经有很多文章深入介绍了托管 C++ 扩展,所以我们不会过多地介绍细节。在本文中,我们将仅简要介绍冰山一角,足以让我们在 C++ 中创建一个包装器。
创建包装类
我们有兴趣为 Magick++ 库的 Image 类 (Magick::Image) 创建包装类。以下是我们如何在 Image.h 中声明头文件
#include "Magick++.h"
using namespace System;
using namespace System::Runtime::InteropServices;
namespace ImageMagickNET
{
public ref class Image
{
// Create a pointer reference to the Magick::Image
// class, the very object that we are going to wrap
//
Magick::Image* image;
protected:
// Here, we create the destructors
//
~Image() { this->!Image(); }
!Image()
{
if( image!=NULL && !isReferenceOnly )
{
delete image;
image = NULL;
}
}
public:
// Here, we create the constructors.
// We have created two whose method signature matches
// that of the Magick::Image class' constructors.
// Although there are many more constructors available,
// we created only these two enough for us to perform
// simple manipulation.
//
Image();
Image(System::String^ imageSpec);
};
}
请注意,在第二个构造函数中,.NET Image 类接受 System::String,这相当于 .NET String 类。但是,等效的 Magick::Image
构造函数具有以下签名
Image(std::string imageSpec);
std::string 类是 C++ STL 字符串格式。现在,这立即给我们带来了问题,因为 C++ STL 字符串类和 .NET String 类显然不兼容。这就是封送器必须介入的地方。
封送字符串
将 .NET 类型转换为等效 C++ 类型的大部分封送工作涉及字符串,至少在我们这里是这样。所以我们专门编写了一个单独的类,以一种相当抽象的方式处理封送。这是我们设计用于处理字符串封送的 Marshaller 类。Marshaller.h 声明如下
#pragma once
using namespace System;
using namespace System::Runtime::InteropServices;
namespace ImageMagickNET
{
public ref class Marshaller
{
private:
protected:
public:
static System::String^ StdStringToSystemString(const std::string& s);
static std::string& SystemStringToStdString(System::String^ s,
std::string& s2);
static std::string& SystemStringToStdString(System::String^ s);
};
}
它仅由 3 个静态方法组成。
第一个是 StdStringToSystemString
,其职责是将 C++ STL 字符串转换为 .NET 字符串。
另外两个将 .NET 字符串转换为 C++ STL 字符串。大多数情况下,我们将使用最后一个方法将字符串从 .NET 传递到 ImageMagick 库,因为它是最简单的方法,并且处理 StringToHGlobalAnsi 所涉及的所有工作都得到了抽象。但是,使用最后一个方法有一个小小的警告,因为此方法需要内存中静态的字符串池。我们选择创建一个包含 8 个字符串的池,这些字符串以循环方式用于封送。
Marshaller.cpp 实现如下
namespace ImageMagickNET
{
int stringPoolCounter = 0;
std::string strings[8];
///-------------------------------------------------------------------
/// Converts a STL string to a CLR string.
///
/// When the conversion is complete, returns the CLR string to the
/// caller.
///-------------------------------------------------------------------
System::String^ Marshaller::StdStringToSystemString(const std::string& s)
{
return gcnew System::String(s.c_str());
}
///-------------------------------------------------------------------
/// Converts a CLR string to a STL string.
///
/// When the conversion completes, returns the STL string through the
/// referenced parameter s2.
///-------------------------------------------------------------------
std::string& Marshaller::SystemStringToStdString(System::String^ s,
std::string &s2)
{
const char* chars = (const char*)(Marshal::StringToHGlobalAnsi(s))
.ToPointer();
s2 = chars;
Marshal::FreeHGlobal(IntPtr((void*)chars));
return s2;
}
///-------------------------------------------------------------------
/// Converts a CLR string to a STL string, using a pool of internally
/// declared strings (up to 8 of them)
///-------------------------------------------------------------------
std::string& Marshaller::SystemStringToStdString(System::String^ s)
{
int c = stringPoolCounter;
stringPoolCounter = (stringPoolCounter+1) % 8;
return Marshaller::SystemStringToStdString(s, strings[c]);
}
}
实现图像构造函数
现在我们已经实现了 Marshaller,我们可以继续实现 Image 类的构造函数。
///-------------------------------------------------------------------
/// Constructor
///-------------------------------------------------------------------
Image::Image()
{
// Here we construct a new Magick::Image class
// and assign the reference to it from our class
//
image = new Magick::Image();
}
///-------------------------------------------------------------------
/// Constructor
/// Loads the file from the specified file path.
///-------------------------------------------------------------------
Image::Image(System::String^ imageSpec)
{
// Here we construct a new Magick::Image class
// and we use the Marshaller's SystemStringToStdString
// method to assist us convert the .NET string to the
// C++ STL string.
//
image = new Magick::Image(
Marshaller::SystemStringToStdString(imageSpec));
}
实现加载和保存
现在基本的构造函数和析构函数已经准备就绪,我们可以继续实现 Magick::Image
类的许多其他功能。
我们首先包装加载和保存方法。Image.h 现在实现如下
#include "Magick++.h"
using namespace System;
using namespace System::Runtime::InteropServices;
namespace ImageMagickNET
{
public ref class Image
{
// Create a pointer reference to the Magick::Image
// class, the very object that we are going to wrap
//
Magick::Image* image;
protected:
// Here, we create the destructors
//
~Image() { this->!Image(); }
!Image()
{
if( image!=NULL && !isReferenceOnly )
{
delete image;
image = NULL;
}
}
public:
// Here, we create the constructors.
// We have created two whose method signature matches
// that of the Magick::Image class' constructors.
// Although there are many more constructors available,
// we created only these two enough for us to perform
// simple manipulation.
//
Image();
Image(System::String^ imageSpec);
//----------------------------------------------------------------
// IO
//----------------------------------------------------------------
// read
void Read(String^ imageSpec_);
// write
void Write(String^ imageSpec_);
}
}
再次注意,我们使用了 Marshaller:SystemStringToStdString
方法进行封送。Magick::Image
类有一些用于加载和保存文件的方法,但其中我们只关注两个基本方法
Magick::Image::read(std::string imageSpec_)
Magick::Image::write(std::string imageSpec_)
我们尽量避免对方法签名和调用方式进行重大更改,只是将方法名称的首字母大写。这是为了尽可能保留 Magick::Image
类的原始接口,这样我们就不必重新编写所有接口文档。
Read 和 Write 函数在 Image.cpp 中实现如下。
///-------------------------------------------------------------------
/// Load an image from the specified file path.
///-------------------------------------------------------------------
void Image::Read(System::String^ imageSpec)
{
// call the read method by simply calling
// image->read and passing in the C++ STL string
//
image->read(Marshaller::SystemStringToStdString(imageSpec));
}
///-------------------------------------------------------------------
/// Save an image to the specified file path.
///-------------------------------------------------------------------
void Image::Write(System::String^ imageSpec)
{
// call the read method by simply calling
// image->write and passing in the C++ STL string
//
image->write(Marshaller::SystemStringToStdString(imageSpec));
}
瞧!我们现在有一个 Image 类,可以将其导出到 .NET 以进行简单的文件加载和保存。
当然,我们还不满足,因为 Magick::Image
中封装了更多我们需要暴露给 .NET 程序的功能。但在我们暴露所有这些功能之前,Magick::Image
类中的一些方法需要引用 Magick::
命名空间中类的对象。类的示例有:Magick::Geometry
、Magick::Color
。一些方法还需要传递枚举类型作为参数。这意味着这些类和枚举类型必须在我们的 .NET 包装器中定义。
设置枚举器类
在实现 .NET Image 类的整个接口时,我们将设置大量枚举器类,但我们只突出显示一个,因为其余的可以完全以相同的方式实现。
ImageMagick 库中的 MagickLib::
命名空间具有一个名为 PaintMethod 的枚举类型。它允许用户指定图像上的洪水填充和绘画相关操作的方法。它在 C++ 中这样声明
typedef enum
{
UndefinedMethod,
PointMethod,
ReplaceMethod,
FloodfillMethod,
FillToBorderMethod,
ResetMethod
} PaintMethod;
当我们在 .NET 中实现它时,我们使用了 .NET 枚举类,如下所示。
public enum class PaintMethod
{
UndefinedMethod = MagickLib::UndefinedMethod,
PointMethod = MagickLib::PointMethod,
ReplaceMethod = MagickLib::ReplaceMethod,
FloodfillMethod = MagickLib::FloodfillMethod,
FillToBorderMethod = MagickLib::FillToBorderMethod,
ResetMethod = MagickLib::ResetMethod
};
通常,建议将枚举中的每个项映射到 C++ 库中声明的相应常量。这是为了避免在较新版本中枚举顺序不同时可能发生的重大更改。
设置其他类
正如前一节中强调的,Magick::Image 类需要引用 Magick::Color
、Magick::Geometry
等类作为参数。在这里,我们将以 Color 类为例进行实现,其余的类也可以用相同的方式实现。
Magick::Geometry
类的实现,本质上,与我们包装 Magick::Image
类的方式相同。在实现包装器时,我们尝试匹配 Magick::Geometry
类公开的尽可能多的函数。但为了缩短我们在此处用于说明的代码,我们只挑选了几个方法。我们是这样做的
public ref class Geometry
{
internal:
// Create a pointer reference to the Magick::Geometry
// class, the very object that we are going to wrap
//
// Notice that this is declared in the internal section
// which is very important, because this is where other
// classes within our .NET wrapper will access the
// object.
//
Magick::Geometry* geometry;
protected:
~Geometry() { this->!Geometry(); }
!Geometry() { if( geometry!=NULL ) { delete geometry; geometry = NULL; } }
public:
// constructors
Geometry ( );
Geometry (
unsigned int width_,
unsigned int height_,
unsigned int xOff_,
unsigned int yOff_,
bool xNegative_,
bool yNegative_ );
Geometry ( System::String ^geometry_ );
Geometry ( Geometry^ geometry_ );
// Width
void Width ( unsigned int width_ );
unsigned int Width ( void );
// Height
void Height ( unsigned int height_ );
unsigned int Height ( void );
};
然后,我们在 Geometry.cpp 中实现类本身。
#include "stdafx.h"
#include "Geometry.h"
using namespace System;
using namespace System::Runtime::InteropServices;
namespace ImageMagickNET
{
///-------------------------------------------------------------------
/// Constructor
///-------------------------------------------------------------------
Geometry::Geometry ( )
{
geometry = new Magick::Geometry();
}
///-------------------------------------------------------------------
/// Constructor
///-------------------------------------------------------------------
Geometry::Geometry (
unsigned int width,
unsigned int height,
unsigned int xOff,
unsigned int yOff,
bool xNegative,
bool yNegative )
{
geometry = new Magick::Geometry(width, height, xOff, yOff,
xNegative, yNegative);
}
///-------------------------------------------------------------------
/// Constructor
///-------------------------------------------------------------------
Geometry::Geometry ( System::String^ pGeometry )
{
std::string geometryStr;
geometry = new Magick::Geometry(
Marshaller::SystemStringToStdString(pGeometry, geometryStr));
}
///-------------------------------------------------------------------
/// Constructor
///-------------------------------------------------------------------
Geometry::Geometry ( Geometry^ pGeometry )
{
geometry = new Magick::Geometry(*(pGeometry->geometry));
}
///-------------------------------------------------------------------
// Width
///-------------------------------------------------------------------
void Geometry::Width ( unsigned int width_ )
{
geometry->width( width_ );
}
unsigned int Geometry::Width ( void )
{
return geometry->width();
}
///-------------------------------------------------------------------
// Height
///-------------------------------------------------------------------
void Geometry::Height ( unsigned int height_ )
{
geometry->height( height_ );
}
unsigned int Geometry::Height ( void )
{
return geometry->height();
}
}
回到图像包装器
现在,回到图像包装器,我们应该使用上面学到的所有包装技术,实现与 Magick::Image
类一样多的方法。但我们将专注于使用 Magick::Geometry
和 Magick::Color
类的引用对象的这个方法。
Magick::Image 中的方法签名如下
void resize( Magick::Geometry &geometry_ );
当我们在 Image.h 中实现它时,它将变成这样
namespace ImageMagickNET
{
public ref class Image
{
public:
void Resize( Geometry^ geometry_ );
};
}
在 Image.cpp 文件中实现此功能时,我们必须将 .NET 包装的 Geometry 类解析为等效的 C++ 类。在这种情况下,我们不再使用 Marshaller,而是使用 .NET Geometry 类中包装的引用对象。我们是这样做的
///-------------------------------------------------------------------
// Resize image to specified size.
///-------------------------------------------------------------------
void Image::Resize ( Geometry ^geometry_ )
{
image->resize( *(geometry_->geometry) );
}
.NET Image 类包装器公开了 Magick::Image
对象的许多功能,尽管不是所有功能。但对于任何希望从 .NET 使用 ImageMagick 库的人来说,它都是一个很好的起点。
在接下来的部分中,我们将讨论如何在 VB.NET Windows Forms 应用程序中使用此库。
从 VB.NET 使用 ImageMagick
从 VB 使用我们为 .NET 开发的 ImageMagick 包装器非常简单。
- 在您的 ImageMagick .NET 包装器所在的解决方案中,在 Visual Studio 2005 中创建一个 VB.NET Windows 应用程序项目。选择“其他语言”>“Visual Basic”>“Windows”>“Windows 应用程序”。给应用程序命名,然后单击“确定”。
- 从您的 VB.NET 项目中添加对 ImageMagick .NET 包装器项目的引用。
- 在 .vb 文件的顶部添加以下行
Imports ImageMagickNET
- 将 MenuStrip 对象拖放到窗体上,并构建以下菜单
File Load... Save...
- 将窗体重命名为
Convert
,我们创建Convert_Load
事件并在Convert_Load
事件中调用以下内容'------------------------------------------------------------------- ' Load event '------------------------------------------------------------------- Private Sub Convert_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' initialize the ImageMagick library here MagickNet.InitializeMagick(Application.ExecutablePath) End Sub
- 将
OpenFileDialog
和SaveFileDialog
拖放到窗体上。 - 为加载菜单创建菜单
Click
事件,并在事件中显示一个对话框,然后加载用户在打开文件对话框中指定的文件'------------------------------------------------------------------- ' Load file from the path specified. '------------------------------------------------------------------- Private Sub LoadFile(ByVal filename As String) ' we load all formats as a list of images ' (single frame images such as JPG will ' only contain one image in the list) imageList = New ImageMagickNET.ImageList() imageList.ReadImages(filename) Me.Text = filename ' Once the file is loaded, refresh the ' picturebox to show the image PictureBox1.Refresh() End Sub '------------------------------------------------------------------- ' Open the file dialog and load an image. '------------------------------------------------------------------- Private Sub LoadToolStripMenuItem_Click(_ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles LoadToolStripMenuItem.Click ' Open the open file dialog If OpenFileDialog1.ShowDialog() = _ Windows.Forms.DialogResult.OK Then ' If the user clicks OK, then load up the file LoadFile(OpenFileDialog1.FileName) ' Set the filename for the Save dialog, so that the user ' can choose to save it back to the same file name. SaveFileDialog1.FileName = OpenFileDialog1.FileName End If End Sub
这里的 LoadFile 子程序需要一点解释。我们没有在 .NET 包装器中使用 Image 对象,而是使用了我们创建的 ImageList 对象。这个 ImageList 对象包装了一个 C++ STL 列表。但我们的真正目的是为了能够读取多帧图像。多帧图像是包含多个帧的图像,这些帧通常循环播放以产生动画效果。GIF 文件是少数具有多个帧的格式之一。因此,当您调用 ImageList.ReadImages 并传入 GIF 文件作为参数时,该 GIF 中的所有帧都将被加载。与使用 Image.Read 方法相比,只有 GIF 文件的第一帧会被加载。当您使用 ImageList.ReadImages 方法加载 JPG、PNG 等单帧格式时,ImageList 中将只有一帧。 - 为保存菜单创建菜单
Click
事件,并在事件中显示保存文件对话框并保存文件。'------------------------------------------------------------------- ' Save file from the path specified. '------------------------------------------------------------------- Private Sub SaveFile(ByVal filename As String) ' write all images into a single file ' ' Notice that we pass in true as the second parameter ' to indicate that we want an adjoin file, ie, all frames ' in one file. ' ' If we pass in false, each frame will be saved as a separate ' as according to the Magick++ documentation. ' imageList.WriteImages(filename, True) End Sub '------------------------------------------------------------------- ' Open the Save file dialog and save the image. '------------------------------------------------------------------- Private Sub SaveToolStripMenuItem_Click(_ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles SaveToolStripMenuItem.Click ' Open the save file dialog If SaveFileDialog1.ShowDialog() = _ Windows.Forms.DialogResult.OK Then ' If the user clicks OK, we do a check to see if the file ' format is a JPG. If so, show the ' compression quality dialog ' If (SaveFileDialog1.FileName.ToUpper().EndsWith(".JPG")) Then ConvertQuality.ShowDialog() SetQuality() End If ' We save the file SaveFile(SaveFileDialog1.FileName) End If End Sub
- 编译应用程序。但在运行应用程序之前,请确保以下事项
- 确保您的机器上安装了 ImageMagick Q8 DLL 版本。
- 确保将 ImageMagick Q8 DLL(在源代码包中提供)复制到应用程序的输出 bin 文件夹中。
现在,有了这个简单的应用程序,您可以加载和保存不同格式的文件。
设置压缩质量
通常在保存 JPG 文件时,会要求用户指定压缩质量。更好的压缩(即更小的输出文件)会导致更差的视觉质量;相反,更差的压缩(或更大的文件)会导致更好的视觉质量。Image .NET 包装器将此压缩质量公开为一个属性。
- 因此,我们将创建一个名为
ConvertQuality
的新窗体。 - 将一个滚动条、两个标签(一个的 Caption='Quality',另一个用于在滚动条值改变时更新)和一个 OK 按钮拖放到窗体上。
- 将滚动条的最小属性设置为 1,最大属性设置为 100。
- 创建以下事件以在滚动条更改时更新标签。
'------------------------------------------------------------------- ' Set the label to the value of the scroll bar when it changes '------------------------------------------------------------------- Private Sub HScrollBar1_ValueChanged(ByVal sender As System.Object,_ ByVal e As System.EventArgs) Handles HScrollBar1.ValueChanged Label2.Text = HScrollBar1.Value.ToString() End Sub
- 公开一个名为
Quality
的公共整数变量。Public Quality As Integer
- 创建以下事件以设置公共变量
Quality
并在单击按钮时关闭窗体。'------------------------------------------------------------------- ' Close and return to the caller '------------------------------------------------------------------- Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Me.Quality = HScrollBar1.Value Me.Close() End Sub
- 我们将在主 Convert 窗体中添加一个子程序来设置 ImageList 中所有图像的质量。图像可以使用 For Each 语法访问,因为 ImageList 公开枚举器。
'------------------------------------------------------------------- ' Set the compression quality of the image. '------------------------------------------------------------------- Private Sub SetQuality() ' Set quality for all images in the list ' but normally set quality only applies to JPG or ' coders that use the quality property. For Each image As ImageMagickNET.Image In imageList image.Quality(ConvertQuality.Quality) Next End Sub
- 现在回到 Convert 窗体,我们将对
SaveToolStripMenuItem_Click
事件进行一些小的更改'-------------------------------------------------------------------- ' Open the Save file dialog and save the image. '------------------------------------------------------------------- Private Sub SaveToolStripMenuItem_Click(_ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles SaveToolStripMenuItem.Click ' Open the save file dialog If SaveFileDialog1.ShowDialog() = _ Windows.Forms.DialogResult.OK Then ' If the user clicks OK, we do a check to see if the file ' format is a JPG. If so, show the compression quality dialog ' If (SaveFileDialog1.FileName.ToUpper().EndsWith(".JPG")) Then ConvertQuality.ShowDialog() SetQuality() End If ' We save the file SaveFile(SaveFileDialog1.FileName) End If End Sub
请注意,我们尝试检测输出格式。如果它是 JPG 文件,则会弹出 ConvertQuality 窗体以提示用户设置压缩质量。用户设置后,所有图像帧的压缩质量都会设置好。 - 我们还包含了一个简单的功能,可以在窗体上显示加载的图像。我们在窗体上创建了一个 PictureBox,并钩住了它的 Paint 事件。在此 paint 事件中,我们调用图像第一帧上的
ToBitmap()
方法,然后将此位图绘制到 PictureBox 上。此外,我们已确保在任何调整大小事件中,PictureBox 都会占用窗体客户端的宽度和高度。'-------------------------------------------------------------------- ' Resize form '------------------------------------------------------------------- Private Sub MenuStrip1_Resize(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _Handles MenuStrip1.Resize PictureBox1.Height = Me.ClientSize.Height - MenuStrip1.Height PictureBox1.Width = Me.ClientSize.Width End Sub '------------------------------------------------------------------- ' PictureBox1 Paint event '------------------------------------------------------------------- Private Sub PictureBox1_Paint(ByVal sender As System.Object,_ ByVal e As _ System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint ' just draw the first image (regardless of how many there are in ' the list) ' of course, it would be trivial to show animated GIF by cycling ' through all images in the list, via a timer. ' For Each image As ImageMagickNET.Image In imageList If image.Columns > PictureBox1.Width Or image.Rows > _ PictureBox1.Height Then PictureBox1.SizeMode = PictureBoxSizeMode.Zoom Else PictureBox1.SizeMode = PictureBoxSizeMode.CenterImage End If ' This is where we can attach the ' .NET Bitmap constructed from ' the image to our PictureBox PictureBox1.Image = image.ToBitmap Exit For Next End Sub
在我们提供的源代码中,图像调整大小操作是可用的。但当然可以添加更多图像操作。
在其他平台上运行 .NET 包装器
C++ 包装器尚未在 Mono 和 Mono 支持的所有平台上进行测试。我们渴望知道是否有人曾尝试在其各自的平台上针对 ImageMagick 编译 C++ 代码,并在这些平台上生成 .NET 包装器。如果有可能,请告诉我们并与我们分享您的经验,无论是积极的还是消极的。