Introduction
ImageMagick is a powerful image manipulation tool that recognizes a wide range of image formats, including your usual JPG, GIF, PNG formats commonly used on the web. Among some of its manipulation capabilities are the ability to apply graphical effects such as charcoal, gaussian blur, solarization, (and some effects that you'd see in professional image manipulation software such as Adobe Photoshop).
ImageMagick exposes its function via a set of command-line executables, and effects can be applied by specifying switches and options. But it also exposes its functionalities through its MagickCore and MagickWand C libraries, as well as its Magick++ C++ library, so that developers can take advantage of its functionalities within their software. Interfaces for other languages such as Perl, Python, Java, etc. have also been developed by the ImageMagick community.
In this exercise we have taken to creating a .NET ImageMagick wrapper as a study of ImageMagick functionalities. We have also attempted to create a VB.NET application that calls the .NET wrapper to access the ImageMagick functionalities. Although there already exists an ImageMagick .NET wrapper, we've created one that models the Magick++ classes available closer than the existing one. As the Magick++ exposes the ImageMagick in C++ classes as well as Standard Template Libraries, marshalling types between .NET and C++ becomes a little less daunting.
Before we get started, we would like to highlight the formats that ImageMagick now supports. (Please note that some formats require additional plugins to be installed.)
Ext | Mode | Description | Notes |
ART | R | PFS: 1st Publisher | Format originally used on the Macintosh (MacPaint?) and later used for PFS: 1st Publisher clip art. |
AVI | R | Microsoft Audio/Visual Interleaved | |
AVS | RW | AVS X image | |
BMP | RW | Microsoft Windows bitmap | |
CGM | R | Computer Graphics Metafile | Requires ralcgm to render CGM files. |
CIN | RW | Kodak Cineon Image Format | Use -set to specify the image gamma or black and white points (e.g. -set gamma 1.7, -set reference-black 95, -set reference-white 685). |
CMYK | RW | Raw cyan, magenta, yellow, and black samples | Use -size and -depth to specify the image width, height, and depth. To specify a single precision floating-point format, use -depth 32 -define quantum:format=floating-point. Set the depth to 64 for a double precision floating-point format. |
CMYKA | RW | Raw cyan, magenta, yellow, black, and alpha samples | Use -size and -depth to specify the image width, height, and depth. To specify a single precision floating-point format, use -depth 32 -define quantum:format=floating-point. Set the depth to 64 for a double precision floating-point format. |
CUR | R | Microsoft Cursor Icon | |
CUT | R | DR Halo | |
DCM | R | Digital Imaging and Communications in Medicine (DICOM) image | Used by the medical community for images like X-rays. |
DCX | RW | ZSoft IBM PC multi-page Paintbrush image | |
DIB | RW | Microsoft Windows Device Independent Bitmap | DIB is a BMP file without the BMP header. Used to support embedded images in compound formats like WMF. |
DJVU | R | | |
DNG | R | Digital Negative | |
DOT | R | Graph Visualization | Use -define to specify the layout engine (e.g. -define dot:layout-engine=neato). |
DPX | RW | SMPTE Digital Moving Picture Exchange | Use -set to specify the image gamma or black and white points (e.g. -set gamma 1.7, -set reference-black 95, -set reference-white 685). |
EMF | R | Microsoft Enhanced Metafile (32-bit) | Only available under Microsoft Windows. |
EPDF | RW | Encapsulated Portable Document Format | |
EPI | RW | Adobe Encapsulated PostScript Interchange format | Requires Ghostscript to read. |
EPS | RW | Adobe Encapsulated PostScript | Requires Ghostscript to read. |
EPS2 | W | Adobe Level II Encapsulated PostScript | Requires Ghostscript to read. |
EPS3 | W | Adobe Level III Encapsulated PostScript | Requires Ghostscript to read. |
EPSF | RW | Adobe Encapsulated PostScript | Requires Ghostscript to read. |
EPSI | RW | Adobe Encapsulated PostScript Interchange format | Requires Ghostscript to read. |
EPT | RW | |
OTB | RW | On-the-air Bitmap | |
P7 | RW | Xv's Visual Schnauzer thumbnail format | |
PALM | RW | Palm pixmap | |
PAM | W | Common 2-dimensional bitmap format | |
PBM | RW | Portable bitmap format (black and white) | |
PCD | RW | Photo CD | The maximum resolution written is 768x512 pixels since larger images require huffman compression (which is not supported). |
PCDS | RW | Photo CD | Decode with the sRGB color tables. |
PCL | W | HP Page Control Language | For output to HP laser printers. |
PCX | RW | ZSoft IBM PC Paintbrush file | |
PDB | RW | Palm Database ImageViewer Format | |
PDF | RW | Portable Document Format | Requires Ghostscript to read. By default, ImageMagick sets the page size to the MediaBox. Some PDF files, however, have a CropBox that is smaller than the MediaBox and may include white space, registration or cutting marks outside the CropBox. To force ImageMagick to use the CropBox rather than the MediaBox, use -define (e.g. -define pdf:use-cropbox=true). |
PFA | R | Postscript Type 1 font (ASCII) | Opening as file returns a preview image. |
PFB | R | Postscript Type 1 font (binary) | Opening as file returns a preview image. |
PGM | RW | Portable graymap format (gray scale) | |
PICON | RW | Personal Icon | |
PICT | RW | Apple Macintosh QuickDraw/PICT file | |
PIX | R | Alias/Wavefront RLE image format | |
PNG | RW | Portable Network Graphics | Requires libpng-1.0.2 or later, libpng-1.2.5 or later recommended. |
PNM | RW | Portable anymap | PNM is a family of formats supporting portable bitmaps (PBM) , graymaps (PGM), and pixmaps (PPM). There is no file format associated with pnm itself. If PNM is used as the output format specifier, then ImageMagick automatically selects the most appropriate format to represent the image. The default is to write the binary version of the formats. Use -compress none to write the ASCII version of the formats. |
PPM | RW | Portable pixmap format (color) | |
PS | RW | Adobe PostScript file | Requires Ghostscript to read. |
PS2 | RW | Adobe Level II PostScript file | Requires Ghostscript to read. |
PS3 | RW | Adobe Level III PostScript file | Requires Ghostscript to read. |
PSD | RW | Adobe Photoshop bitmap file | |
PTIF | RW | Pyramid encoded TIFF | Multi-resolution TIFF containing successively smaller versions of the image down to the size of an icon. The desired sub-image size may be specified when reading via the -size option. |
PWP | R | Seattle File Works multi-image file | |
RAD | R | Radiance image file | Requires that ra_ppm from the Radiance software package be installed. |
RGB | RW | Raw red, green, and blue samples | Use -size and -depth to specify the image width, height, and depth. To specify a single precision floating-point format, use -depth 32 -define quantum:format=floating-point. Set the depth to 64 for a double precision floating-point format. |
RGBA | RW | Raw red, green, blue, and alpha samples | Use -size and -depth to specify the image width, height, and depth. To specify a single precision floating-point format, use -depth 32 -define quantum:format=floating-point. Set the depth to 64 for a double precision floating-point format. |
RLA | R | Alias/Wavefront image file | |
RLE | R | Utah Run length encoded image file | |
SCT | R | Scitex Continuous Tone Picture | |
SFW | R | Seattle File Works image | |
SGI | RW | Irix RGB image | |
SHTML | W | Hypertext Markup Language client-side image map | Used to write HTML clickable image maps based on a the output of montage or a format which supports tiled images such as MIFF. |
SUN | RW | SUN Rasterfile | |
SVG | RW | Scalable Vector Graphics | Requires libxml2 and freetype-2. Note that SVG is a very complex specification so support is still not complete. |
TGA | RW | Truevision Targa image | Also known as formats ICB, VDA, and VST. |
TIFF | RW | Tagged Image File Format | Also known as TIF. Requires tiff-v3.6.1.tar.gz or later. Use -define to specify the rows per strip (e.g. -define tiff:rows-per-strip=8). To specify a signed format, use -define quantum:format signed. To specify a single-precision floating-point format, use -depth 32 -define quantum:format=floating-point. Set the depth to 64 for a double-precision floating-point format. Use -define tiff:photometric=min-is-black or -define tiff:photometric=min-is-white to toggle the photometric interpretation for a bilevel image. Specify the extra samples as associated or unassociated alpha with, for example, -define tiff:alpha=unassociated. |
TIM | R | PSX TIM file | |
TTF | R | TrueType font file | Requires freetype 2. Opening as file returns a preview image. Use -set if you do not want to hint glyph outlines after their scaling to device pixels (e.g. -set type:hinting off). |
TXT | RW | Raw text file | |
UIL | W | X-Motif UIL table | |
UYVY | RW | Interleaved YUV raw image | Use -size and -depth command line options to specify width and height. Use -sampling-factor to set the desired subsampling (e.g. -sampling-factor 4:2:2). |
VICAR | RW | VICAR rasterfile format | |
VIFF | RW | Khoros Visualization Image File Format | |
WBMP | RW | Wireless bitmap | Support for uncompressed monochrome only. |
WMF | R | Windows Metafile | Requires libwmf. By default, renders WMF files using the dimensions specified by the metafile header. Use the -density option to adjust the output resolution, and thereby adjust the output size. The default output resolution is 72DPI so -density 144 results in an image twice as large as the default. Use -background color to specify the WMF background color (default white) or -texture filename to specify a background texture image. |
WPG | R | Word Perfect Graphics File | |
X | RW | display or import an image to or from an X11 server | Use -define to obtain the image from the root window (e.g. -define x:screen=true). |
XBM | RW | X Windows system bitmap, black and white only | Used by the X Windows System to store monochrome icons. |
XCF | R | GIMP image | |
XPM | RW | X Windows system pixmap | Also known as PM. Used by the X Windows System to store color icons. |
XWD | RW | X Windows system window dump | Used by the X Windows System to save/display screen dumps. |
YCbCr | RW | Raw Y, Cb, and Cr samples | Use -size and -depth to specify the image width, height, and depth. |
YCbCrA | RW | Raw Y, Cb, Cr, and alpha samples | Use -size and -depth to specify the image width, height, and depth. |
YUV | RW | CCIR 601 4:1:1 | Use -size and -depth command line options to specify width, height, and depth. Use -sampling-factor to set the desired subsampling (e.g. -sampling-factor 4:2:2). |
Compiling the Source Codes
We have tried to make it as easy as possible to compile the sources. But we need the following environment on your machine to be set up:
- You must have a C: available.
- You must have Visual Studio .NET 2005 available.
If you do not, you could still use Visual Studio 2005 Express Editions (C++ and Visual Basic), but you may have to break up the .SLN file.
For some reason, ImageMagick 6.3.2 is unable to load the IM_MOD*.DLLs from the same directory as the executing application. Instead it always refers to the registry for the path to the IM_MOD*.DLL files. This may cause problems for those who wish to use ImageMagick on their hosting servers, but have no access to the hosting server's registry. For that matter, I've taken the ImageMagick 6.3.2 source code and modified it very slightly to allow it to read the IM_MOD*.DLLs from the executing application directory. As a result, in this updated release, you no longer need to set up anything in the registry.
To install, download the ImageMagick-Source.zip, ImageMagick-DLLs-Q8.zip, ImageMagick-DLLs-Q16.zipand unzip them to the same folder location. (We apologize for the separate files since CodeProject was unable to accept all of them as one big zip file) If you have unzipped them correctly, your directory structure should end up like this:
Folder
+-DLLs
| +-DebugQ8
| +-DebugQ16
+-ImageMagickNET
| +-include8
| +-include16
| +-lib8
| +-lib16
+-VBForms
+-My Project
You will notice one batch file there: install.bat. Double click on install.bat to execute the batch file. The batch file does the following:
- Create a duplicate copy of DLLs\DebugQ8 to DLLs\ReleaseQ8, and DLLs\DebugQ16 to DLLs\ReleaseQ16.
Once you are done, open up the ImageMagick.NET.sln file in Visual Studio .NET:
- There are four configurations possible for compilation: DebugQ8, DebugQ16, ReleaseQ8, ReleaseQ16. Choose either of Q8 or Q16 for compilation. It does not matter if you have previously set up ImageMagick on your machine.
- Rebuild the entire solution.
- Run the VB Demo.
Compiling with Subsequent Releases of ImageMagick
There are a few things to take note should you need to compile the .NET Wrapper with subsequent releases of ImageMagick. The first way:
- Download the latest release of ImageMagick from: http://www.imagemagick.org/script/binary-releases.php. Remember to download on the DLLs version and not the static versions
- Obtain the latest *.h files and copy them to the ImageMagickNET\include8 or ImageMagickNET\include16 folders (depending on the pixel quantization)
- Obtain the latest *.lib files and copy them to the ImageMagickNET\lib8 or ImageMagickNET\lib16
- Obtain the latest CORE_RL*.DLLs, mfc71, msvcp71, msvcr71.dll and all other non-IM_MOD_RL*.DLLs that exist in the root folder of the ImageMagick installation. Copy these into the DLLs\DebugQX and DLLs\ReleaseQX folder. The VB project has a post-build event that copies the appropriate DLLs based on the configuration you chose into your application output folder. (See notes on the CORE_RL*.DLLs below)
- Obtain the IM_MOD_RL*.DLL files and the *.xml files from ImageMagick and copy them to the C:\ImageMagick
The following notes are important for the setting up of the development environment, and maybe useful for those who wish to deploy their application onto production sites.
Notes on the CORE_RL*.DLLs
When running your application, make sure that the correct pixel quantization version Q8/Q16 of the CORE_RL*.DLLs, msvcp71.dll, msvcr71.dll and all other non-IM_MOD_RL*.DLLs reside on the same folder as your application. This is to ensure that your application loads the DLLs directly from its current path, instead of loading it from the ImageMagick folder.
Why do we do this? You may ask. This is because if you have both the Q8 and the Q16 installations of ImageMagick set up on your machine, Windows does not automatically know which CORE_RL*.DLLs to look for. It only looks for the DLLs, we believe, first in the current application directory, and then in the order of the folders specified in the PATH environment variable. Thus, if you have both Q8 and Q16 set up on your machine, then Windows will always look for either the Q8 DLLs everytime or Q16 DLLs everytime. This will cause your application to crash when the wrong pixel quantization libraries are used. It is thus safer to copy the CORE_RL*.DLLs to your application folder.
Notes on the IM_MOD_RLL*.DLLs
When you set up the latest ImageMagick from the installers, the installers will automatically set up on your machine some registry entries. These DLLs are responsible for decoding and encoding the various supported image file formats. Now, when the CORE_RL*.DLLs needs to load up one of these it does NOT (at least in version 6.3.2) search for them in the current application folder. Instead it searches for them in the folders specified by the CoderModulesPath key in the registry entries. (Which is why we had to recompile the ImageMagick sources to enable it to look under the currrent executing folder) The ImageMagick registry entries, one set for Q8 and Q16 each, are defined as follows:
[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"
Managed C++ Extensions
We will be exposing the Magick++ interface to .NET. But since Magick++ is in C++, we will be creating the .NET interface with C++, with the Managed Extensions. Please note that the C++ Managed Extensions that we are using will only work on .NET Framework 2.0, and thus the files will compile only on Visual Studio 2005.
A wide variety of articles on the web already cover Managed C++ Extensions in-depth, so we will not go too much into the details. In this article, we will only briefly cover the tip of the iceberg, sufficient for us to create a wrapper in C++.
Creating the Wrapper Class
We are interested in creating the wrapper class for the Image class of the Magick++ library (Magick::Image). Here's how we declare the header in Image.h:
#include "Magick++.h"
using namespace System;
using namespace System::Runtime::InteropServices;
namespace ImageMagickNET
{
public ref class Image
{
Magick::Image* image;
protected:
~Image() { this->!Image(); }
!Image()
{
if( image!=NULL && !isReferenceOnly )
{
delete image;
image = NULL;
}
}
public:
Image();
Image(System::String^ imageSpec);
};
}
Notice in the second constructor, the .NET Image class accepts a System::String, which is the equivalent of the .NET String class. However, the equivalent Magick::Image
constructor has the following signature:
Image(std::string imageSpec);
The std::string class is the C++ STL string format. Now, this immediately poses a problem to us because the C++ STL string class and the .NET String class is clearly incompatible. This is where the Marshaller has to enter.
Marshalling Strings
A bulk of the marshalling work to convert a .NET type to an equivalent C++ type involves strings, at least, in our case here. So we wrote a separate class specifically to handle the marshalling in a fairly abstract manner. This is the Marshaller class that we designed to handle the string marshalling. The Marshaller.h is declared as follows:
#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);
};
}
It consists of only 3 static methods.
The first of which is the StdStringToSystemString
, whose responsibility is to convert the C++ STL string to a .NET string.
The other 2 converts a .NET string to a C++ STL string. Most of the time we will be using the last method to pass in strings from .NET to the ImageMagick library, as it is the easiest and all work involved in dealing with the StringToHGlobalAnsi gets abstracted away. There's a small caveat, however, for using the last method, as this method requires a static pool of strings in memory. We've opted to create a pool of 8 strings that are used for marshaling on a round-robin basis.
The Marshaller.cpp is implemented as follows:
namespace ImageMagickNET
{
int stringPoolCounter = 0;
std::string strings[8];
System::String^ Marshaller::StdStringToSystemString(const std::string& s)
{
return gcnew System::String(s.c_str());
}
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;
}
std::string& Marshaller::SystemStringToStdString(System::String^ s)
{
int c = stringPoolCounter;
stringPoolCounter = (stringPoolCounter+1) % 8;
return Marshaller::SystemStringToStdString(s, strings[c]);
}
}
Implementing the Image Constructors
Now that we've implemented the Marshaller, we can proceed to implement the constructors of the Image class.
Image::Image()
{
image = new Magick::Image();
}
Image::Image(System::String^ imageSpec)
{
image = new Magick::Image(
Marshaller::SystemStringToStdString(imageSpec));
}
Implementing the Loading and Saving
Now that the basic constructors and destructors are ready, we can go on to implement the many other functionalities of the Magick::Image
class.
We shall first wrap the load and save methods. The Image.h is now implemented as follows:
#include "Magick++.h"
using namespace System;
using namespace System::Runtime::InteropServices;
namespace ImageMagickNET
{
public ref class Image
{
Magick::Image* image;
protected:
~Image() { this->!Image(); }
!Image()
{
if( image!=NULL && !isReferenceOnly )
{
delete image;
image = NULL;
}
}
public:
Image();
Image(System::String^ imageSpec);
void Read(String^ imageSpec_);
void Write(String^ imageSpec_);
}
}
Notice again that we made use of the Marshaller:SystemStringToStdString
method for marshalling. The Magick::Image
class has a few methods for loading and saving files, but out of those let's concentrate on the basic two methods:
Magick::Image::read(std::string imageSpec_)
Magick::Image::write(std::string imageSpec_)
We have tried to avoid making major changes to the method signatures, and the way they are being called except to capitalize the first letter of the method name. This is to retain, as much as possible, the original interfaces of the Magick::Image
classes, so that we don't have to re-document all our interfaces again.
And the Read and Write functions are implemented in Image.cpp as follows.
void Image::Read(System::String^ imageSpec)
{
image->read(Marshaller::SystemStringToStdString(imageSpec));
}
void Image::Write(System::String^ imageSpec)
{
image->write(Marshaller::SystemStringToStdString(imageSpec));
}
And viola! We now have an Image class that we can export to .NET to do simple file loading and saving.
Of course we aren't yet as satisfied, because there are a lot more functionality encapsulated in the Magick::Image
that we need to expose to our .NET program. But before we expose them all, some methods in the Magick::Image
class require references to objects of classes found in the Magick::
namespace. Examples of classes are: Magick::Geometry
, Magick::Color
. Some methods also require an enumerated type to be passed in as a parameter. This means that the classes and the enumerated types must be defined in our .NET wrapper.
Setting Up Enumerator Classes
We will be setting up plenty of enumerator classes as we proceed to implement the entire interface of our .NET Image class, but we shall highlight just one, since implementing the rest can be done in the exact same way.
The MagickLib::
namespace in the ImageMagick library has this enumerated type called PaintMethod. It allows the user to specify the method of flood filling and painting related operations on an image. It is declared in C++ this way:
typedef enum
{
UndefinedMethod,
PointMethod,
ReplaceMethod,
FloodfillMethod,
FillToBorderMethod,
ResetMethod
} PaintMethod;
When we implement this in .NET, we used the .NET enumeration class, as follows.
public enum class PaintMethod
{
UndefinedMethod = MagickLib::UndefinedMethod,
PointMethod = MagickLib::PointMethod,
ReplaceMethod = MagickLib::ReplaceMethod,
FloodfillMethod = MagickLib::FloodfillMethod,
FillToBorderMethod = MagickLib::FillToBorderMethod,
ResetMethod = MagickLib::ResetMethod
};
As a general rule, it is advisable to map each item in the enumeration to their corresponding constant declared in the C++ library. This is so as to avoid breaking changes that may occur, when the order of the enumeration is different in a newer release.
Setting Up Other Classes
As highlighted in the earlier section, the Magick::Image class requires as parameters references to classes like Magick::Color
, Magick::Geometry
. Here, we will be implemented the Color class as an example, and the rest of the classes can be implemented the same way.
The implementation of the Magick::Geometry
class, is essentially, the same way we had wrapped the Magick::Image
class. When implementing the wrapper, we try to match as many functions as the Magick::Geometry
class exposes. But to shorten the code we put up for illustration here, we only picked a few methods. And this is how we did it:
public ref class Geometry
{
internal:
Magick::Geometry* geometry;
protected:
~Geometry() { this->!Geometry(); }
!Geometry() { if( geometry!=NULL ) { delete geometry; geometry = NULL; } }
public:
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_ );
void Width ( unsigned int width_ );
unsigned int Width ( void );
void Height ( unsigned int height_ );
unsigned int Height ( void );
};
And then, we implement the class itself in Geometry.cpp.
#include "stdafx.h"
#include "Geometry.h"
using namespace System;
using namespace System::Runtime::InteropServices;
namespace ImageMagickNET
{
Geometry::Geometry ( )
{
geometry = new Magick::Geometry();
}
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);
}
Geometry::Geometry ( System::String^ pGeometry )
{
std::string geometryStr;
geometry = new Magick::Geometry(
Marshaller::SystemStringToStdString(pGeometry, geometryStr));
}
Geometry::Geometry ( Geometry^ pGeometry )
{
geometry = new Magick::Geometry(*(pGeometry->geometry));
}
void Geometry::Width ( unsigned int width_ )
{
geometry->width( width_ );
}
unsigned int Geometry::Width ( void )
{
return geometry->width();
}
void Geometry::Height ( unsigned int height_ )
{
geometry->height( height_ );
}
unsigned int Geometry::Height ( void )
{
return geometry->height();
}
}
Back to the Image Wrapper
Now, back to the Image Wrapper, we should be implementing as many methods as the Magick::Image
class has, using all the techniques we have learnt above about wrapping. But we will focus on this one method, that uses reference to the Magick::Geometry
and the Magick::Color
class.
The method signature in Magick::Image is as follows:
void resize( Magick::Geometry &geometry_ );
When we implement it in the Image.h, it will become like this:
namespace ImageMagickNET
{
public ref class Image
{
public:
void Resize( Geometry^ geometry_ );
};
}
When implementing this in the Image.cpp file, we must resolve the .NET-wrapped Geometry class to the C++ equivalent. In this case, we don't use the Marshaller anymore, but the reference object wrapped within the .NET Geometry class. This is how we do it:
void Image::Resize ( Geometry ^geometry_ )
{
image->resize( *(geometry_->geometry) );
}
The .NET Image class wrapper exposes a lot of functionality from the Magick::Image
object, though not all. But it serves as a good start to anyone who would like to use the ImageMagick libraries from .NET.
In the following sections we will be discussing on how to use this library in a VB.NET windows forms application.
Using ImageMagick from VB.NET
Using the ImageMagick wrapper that we developed for .NET from VB turns out to be fairly straightforward.
- In the same solution as your ImageMagick .NET wrapper, create a VB.NET Windows Application project in Visual Studio 2005. Select Other Languages > Visual Basic > Windows > Windows Application. Give the application a name and click on OK.
- Add a reference from your VB.NET project to the ImageMagick .NET wrapper project.
- In the .vb file, put the following line at the top of the file:
Imports ImageMagickNET
- Drop a MenuStrip object on the form and construct the following menu:
File
Load...
Save...
- Rename the form to be called
Convert
, and we create the Convert_Load
event and call the following in the Convert_Load
event:
Private Sub Convert_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
MagickNet.InitializeMagick(Application.ExecutablePath)
End Sub
- Drop the
OpenFileDialog
and the SaveFileDialog
onto the form. - Create the menu
Click
event for the Load menu, and in the event, show a dialog and then load the file specified by the user in the open file dialog:
Private Sub LoadFile(ByVal filename As String)
imageList = New ImageMagickNET.ImageList()
imageList.ReadImages(filename)
Me.Text = filename
PictureBox1.Refresh()
End Sub
Private Sub LoadToolStripMenuItem_Click(_
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles LoadToolStripMenuItem.Click
If OpenFileDialog1.ShowDialog() = _
Windows.Forms.DialogResult.OK Then
LoadFile(OpenFileDialog1.FileName)
SaveFileDialog1.FileName = OpenFileDialog1.FileName
End If
End Sub
The LoadFile subroutine here bears a little explanation. We did not use the Image object in our .NET Wrapper, but an ImageList object that we created. This ImageList object wraps a C++ STL list. But our real intent is so that we are able to read multi-frame images. Multi-frame images are images that have more than one frame in it and normally these frames are cycled to give the impression of animation. GIF files are one of the few formats with multiple frames. So when you call ImageList.ReadImages and pass in a GIF file as a parameter, all frames in that GIF will be loaded up. Contrast this with using the Image.Read method, only the first frame of the GIF file will be loaded. When you load single-frame formats like JPG, PNG using the ImageList.ReadImages method, the ImageList will only have one frame in it. - Create the menu
Click
event for the Save menu, and in the event, show the save file dialog and save the file.
Private Sub SaveFile(ByVal filename As String)
imageList.WriteImages(filename, True)
End Sub
Private Sub SaveToolStripMenuItem_Click(_
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles SaveToolStripMenuItem.Click
If SaveFileDialog1.ShowDialog() = _
Windows.Forms.DialogResult.OK Then
If (SaveFileDialog1.FileName.ToUpper().EndsWith(".JPG")) Then
ConvertQuality.ShowDialog()
SetQuality()
End If
SaveFile(SaveFileDialog1.FileName)
End If
End Sub
- Compile the application. But before the application can be run, make sure of the following things:
- Be sure that you have the ImageMagick Q8 DLL version installed on your machine.
- Be sure to copy the ImageMagick Q8 DLLs (available in the source code package) into the output bin folder of your application.
Now with this simple application, you can load and save files in different formats.
Setting Compression Quality
Often when saving a JPG file, the user would be required to specify the compression quality. A better compression (that is, a smaller output file) will result poorer visual quality; in contrast, a worse compress (or a bigger file) will result be better visual quality. And the Image .NET wrapper exposes this compression quality as a property.
- So we will create a new form called
ConvertQuality
. - Drop a scroll bar, two labels (one whose Caption='Quality', the other is meant to be updated with the scroll bar value when it changes), and an OK button onto the form.
- Set the scroll bar's minimum property to 1, and maximum property to 100.
- Create the following event to update the label when the scrollbar is changed.
Private Sub HScrollBar1_ValueChanged(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles HScrollBar1.ValueChanged
Label2.Text = HScrollBar1.Value.ToString()
End Sub
- Expose a public integer variable called
Quality
.
Public Quality As Integer
- Create the following events to set the public variable
Quality
and close the form when the button is clicked.
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
- We will add a subroutine to set the quality of all images in the ImageList in the main Convert form. The Images can be access using the For Each syntax, as the ImageList exposes the enumerator.
Private Sub SetQuality()
For Each image As ImageMagickNET.Image In imageList
image.Quality(ConvertQuality.Quality)
Next
End Sub
- Now going back to the Convert form, we are going to make some minor changes to the
SaveToolStripMenuItem_Click
event:
Private Sub SaveToolStripMenuItem_Click(_
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles SaveToolStripMenuItem.Click
If SaveFileDialog1.ShowDialog() = _
Windows.Forms.DialogResult.OK Then
If (SaveFileDialog1.FileName.ToUpper().EndsWith(".JPG")) Then
ConvertQuality.ShowDialog()
SetQuality()
End If
SaveFile(SaveFileDialog1.FileName)
End If
End Sub
Notice that we try to detect the output format. If it is a JPG file, then we pop up the ConvertQuality form as a prompt for the user to set the compression quality. After the user sets it, the compression quality is set for all frames in the image. - We have also included a simple feature to display the loaded image onto the form. We created a PictureBox onto the form, and hooked its Paint event. In this paint event, we call a method
ToBitmap()
on the first frame of the images, and then paint this bitmap onto the PictureBox. Additionally, we have ensured that the PictureBox took the size of the Form's client width and height in the event of any resize.
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
Private Sub PictureBox1_Paint(ByVal sender As System.Object,_
ByVal e As _
System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
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
PictureBox1.Image = image.ToBitmap
Exit For
Next
End Sub
In the source code that we have provided, an operation to resize the image is available. But more image operations can certainly be added.
Running .NET Wrapper on Other Platforms
The C++ wrapper has not yet been tested on Mono and all platforms that Mono supports. We are keen to know if anyone has endeavored compiling the C++ codes against ImageMagick in their respective platforms and producing a .NET wrapper on those platforms. If it is possible at all, do let us know and share your experience with us, positive, or negative.