Preface
Years after the previous version, I can tell that the article has survived its author, and even if my opinion on the limits of this library has not changed, an update was really necessary.
Thank you very much to all the people that gave their contribution to this new release, with hundreds of small and big enhancements, my role was mainly to put all the pieces together.
Introduction & License
CxImage
is a C++ class that can load, save, display, and transform images in a very simple and fast way.
The class CxImage
is free; as for the TIFF, JPEG, PNG and ZLIB libraries : "If you use this source code in a product, acknowledgement is not required but would be appreciated."
CxImage
is open source and licensed under the
zlib license . In a nutshell, this means that you can use the code however you wish, as long as you don't claim it as your own.
What's New in Version 6.00
The complete list of bugfixes and enhancements is reported in the documentation, (see \doc\cximage_history.htm, or this link). Here I will highlight some of the new features.
An application written with CxImage
version 5.99 should work also with the new version; the interface of few methods is different, normally because of new parameters, but the default behaviour is the same.
The applications linked with the old DLL will not work with the new one; but if necessary you can edit the declarations, or add new overloads, and revert to the old interface for the DLL. An issue to take care is
ENUM_CXIMAGE_FORMATS
: in the old version the CXIMAGE_FORMAT_...
can change the value, depending on the supported formats enabled by the corresponding
CXIMAGE_SUPPORT_...
switch. In the new version, CXIMAGE_FORMAT_...
are assigned to unique values. Static methods like
GetNumTypes
, GetTypeIdFromName
, GetTypeIdFromIndex
,
GetTypeIndexFromId
will help the application to manage the new policy.
File Formats & Linked C Libraries
CxImage
works with the latest version of these libraries: Zlib (1.2.3), Jasper ( 1.900.1), LibMNG (1.0.10), LibPNG (1.2.24). LibTIFF 3.8.2 can be linked with
CxImage
, but the version included in the CxImage
distribution (3.5.7, patched) can read images with OJPEG compression, or with non standard bit per samples. The choice is up to you.
The j2k library (now openjpeg), and the associated class CxImageJ2K
, have been removed from the project. JPEG2000 images are supported through Jasper and CxImageJAS.
CxImage
6.00 includes a new class (CxImageRAW
) and a new library (LibDCR) to read RAW images from digital cameras; common file extension are: RAW, CRW, NEF, CR2, DNG, ORF, ARW, ERF, 3FR, DCR, X3F, MEF, RAF, MRW, PEF, SR2. LibDCR
is based on Dave Coffin's dcraw.c; and offers the same features of the original dcraw application (see the "dcr.dsw" project included in the \raw directory). The restricted features under the GPL Version
2 are disabled; please read the license terms in "libdcr.h" before enabling the restricted code.
CxImageRAW
implements the basic functions to decode the image, the only available option controlled by
SetCodecOption
, is about the interpolation quality (DECODE_QUALITY_LIN=0, DECODE_QUALITY_VNG=1, DECODE_QUALITY_PPG=2, DECODE_QUALITY_AHD=3
).
CxImagePNG
has been improved to read and write all of the PNG_COLOR_TYPE_...
combinations. PNGs with a "pixel depth" more that 8 bits per channel will be converted down to 8 bits, this is the major limit in
CxImage
.
CxImageGIF
: better support for reading animated GIF, now can decode all frames in a single pass, if enabled with
SetRetreiveAllFrames
. The CxImage
demo implements this option, and shows how to play an animated GIF (when the program asks "File with N images. Read all?", select "Cancel").
CxImageBMP
: reads and writes 32 bit images (with alpha layer).
CxImageICO
: reads and writes Vista (PNG) icons; and added support for writing multipage icons.
CxImageMNG
: reads MNGs with alpha layer.
CxImageSKA
: new class for SKA image format, used in some video rental application.
CxImageJPG
: new options for the JPEG format subsampling (ENCODE_SUBSAMPLE_422, ENCODE_SUBSAMPLE_444
), default is 4:1:1 (high), can be set to 4:2:2 (medium) or 4:4:4 (none).
The next table shows the different amount of artifacts in the compressed image, using to different subsampling. 4:4:4 subsampling is useful in images with sharp edges, to reduce the typical blurring effect of the JPG compression.
original image |
JPG image and artefacts, quality 75, subsampling 4:1:1 |
JPG image and artefacts, quality 75, subsampling 4:4:4 |
---|
Portability
The class and the projects have been tested with different compilers, from Microsoft VC++6 to VC++2008, with Borland C++ Builder 3 and 6, and partially with wxDev-C++ and MinGW.
UNICODE and non-UNICODE configurations are provided for all the libraries (thanks to Eric Jesover).
A light version (cximage600_lite) without the C libraries and with a small demo is provided for the first time users, or as a basic template, without the clutter of the full demo project.
The console (\demo2) and the CxImageCrtDll projects can be built with VC++Express2005 and the Microsoft Platform SDK. If you get linker errors like "unresolved external...", check if all the C libraries have been compiled (set the correct project dependencies), or add the gdi32.lib and user32.lib modules to the Linker\Input\Additional Dependencies property.
CxImage
works also with Pocket PC 2003; a working version and demo (cximage600_ce) are provided for the VC++2005 compiler (thanks to Vincent Richomme). For the old embedded VC compilers,
the main limit was the support for exception handling. To overcome this problem, the
try
, throw
and catch
statements have been replaced with 3 macros (defined in
ximadef.h), and with the definition of CXIMAGE_SUPPORT_EXCEPTION_HANDLING
; in this way it is possible to build the library without exception handling support. Maybe the solution is not so elegant, but the impact on the source code is minimal
when exception handling is disabled, while there are no changes in case of exception handling enabled.
The compatibility between little-endian and big-endian platforms, for the built in formats (bmp, ico, tga, pcx, gif, ska) is handled by
ntohs
and ntohl
.
Demo
Almost all the new features can be tested in the main CxImage
demo application. The demo is just a test bench, even if it offers some nice features, it is not intended to be a serious application.
- CQuantizer : the class is used with the
DecreaseBpp
menu. In the previous version there was a rounding error, clearly visible when in some case the white colour (255,255,255) was converted to (254,254,254). In the new release this error has been fixed. - Copy/Paste : now pastes also metafile pictures (for example, from Office applications). The demo internally uses a custom clipboard format, to test the
Dump/Undump
methods.
Copy works on the active selection, use \CxImage\Remove Selection to copy the full image. - FloodFill : (\View\Tools\Flood Fill) with a floating dialog, you can test the
FloodFill
colour, tolerance, opacity, and selection. The case with opacity = 0 and selection enabled acts as a "magic wand". - Graph data extraction : (\Filters\graph data extraction) extracts the numeric data from graphs (acquired from scanners or downloaded from the internet). The converted values are pasted into the clipboard, and can be saved to text
or Excel files.
Data Extraction dialog and results - RedEyeRemove : (\Filters\Non Linear\Remove Red Eye) removes the red-eye effect that frequently occurs in pictures. You must select the region around the pupil, where the function will filter the red channel. The selection can also
be rectangular, or including part of the iris: the filter works on a circular region centred on the selection.
original image
selection around the pupil
result afterRedEyeRemove
- SelectiveBlur / UnsharpMask : these non linear filters increase the image quality.
SelectiveBlur
removes the "granular" noise (jpeg artefacts, or digital camera noise) preserving the details,UnsharpMask
enhances the details without adding noise in the "flat" regions. - Custom linear filters : (\Filters\Linear\Custom) a small graphic interface to test new kernels for the
Filter
function (thanks to Priyank Bolia). - Histogram : (\Colors\Histogram\...) many menus are available to test
HistogramStretch
with different approaches (0 = luminance, 1 = linked channels , 2 = independent channels); thethreshold
parameter increases the robustness of the algorithm in case of noisy images. "Half Saturation" and "Full Saturation" test the combination ofConvertColorSpace
,Histogram
, andSaturate
, to stretch the histogram in the YUV colorspace.
original image
+ HistogramStretch(2,0.005f)
+ "Half saturation" - Thresholding : ( \Colors\Threshold... and \Colors\Adaptive Threshold).
OptimalThreshold
is a new method to find the optimal threshold for image binarization, the available algorithms are:
1 = Otsu;
2 = Kittler & Illingworth;
3 = maximum entropy;
4 = potential difference;
0 = average all methods (default, and used in the demo);The option "preserve colors less than the threshold" will test
Threshold2
, useful to filter colored images with a noisy background, the result will be a colored image with a uniform background.
AdaptiveThreshold
is an application ofOptimalThreshold
to build a variable threshold mask.AdaptiveThreshold
is useful in case of images with a non uniform lightness, where a single threshold can't be optimal for the whole image; but in general it will give bad results if the parameters are not well tuned.
original image
OptimalThreshold
+Threshold
AdaptiveThreshold
- Add shadow : (\Filters\Add Shadow...) this menu is a small example on how the simple
CxImage
methods (selections,GaussianBlur
,Mix
) can be combined to obtain a common effect.
purple shadow on white background
black shadow on red background - Text smoothing :
DrawStringEx
implements a new option, throughCXTEXTINFO::smooth
, that can be tested with the text tool (\View\Tools\Text), checking the "antialias" option.A similar effect can be obtained in post processing with
TextBlur
(\Filters\Non Linear\Text Blur): a non linear filter that works only on diagonal or round edges, without affecting vertical or horizontal lines. In the next table there is a comparison between different smoothing methods:
DrawStringEx
without smoothing
DrawStringEx
with smoothing
post processing withTextBlur
post processing with a linear 3x3 "soften"Filter
CxImage Structure
In the vertical hierarchy of the library, CxImage
stays on the top of the other modules, it's not a clean OOP approach, but the result was good since the first release and now it's too late to change again. Anyway you can always use the derived
classes to perform the format specific operations, like for CxImageTIF
to save multipage TIFFs.
The glue to connect all the modules and the C libraries is CxFile
, a virtual class that provides the standard methods to access the data from a file on the disk or in memory.
A Cximage
object is basically a bitmap, with the addition of some member variables to store useful information:
class CxImage { ... protected: void* pDib; //contains the header, the palette, the pixels BITMAPINFOHEADER head; //standard header CXIMAGEINFO info; //extended information BYTE* pSelection; //selected region BYTE* pAlpha; //alpha channel CxImage** ppLayers; //generic layers CxImage** ppFrames; //frames for animation }
CxImage::head
is the bitmap header and CxImage::pDib
is a normal bitmap (as you can see in the implementation of
CxImageBMP::Encode
).
CxImage::info
is a handy container of many information shared between different formats, and for all the member functions.
typedef struct tagCxImageInfo { DWORD dwEffWidth; //DWORD aligned scan line width BYTE* pImage; //THE IMAGE BITS void* pGhost; //if this is a ghost, pGhost point to the body DWORD dwType; //original image format char szLastError[256]; //debugging long nProgress; //monitor long nEscape; //escape long nBkgndIndex; //used for GIF, PNG, MNG RGBQUAD nBkgndColor; //used for RGB transparency BYTE nQuality; //used for JPEG long nFrame; //used for TIF, GIF, MNG : actual frame long nNumFrames; //used for TIF, GIF, MNG : total number of //frames DWORD dwFrameDelay; //used for GIF, MNG long xDPI; //horizontal resolution long yDPI; //vertical resolution RECT rSelectionBox; //bounding rectangle BYTE nAlphaMax; //max opacity (fade) bool bAlphaPaletteEnabled; //true if alpha values in the palette are // enabled. bool bEnabled; //enables the painting functions long xOffset; long yOffset; DWORD dwEncodeOption; //for GIF, TIF : 0=def.1=unc,2=fax3,3=fax4, // 4=pack,5=jpg RGBQUAD last_c; //for GetNearestIndex optimization BYTE last_c_index; bool last_c_isvalid; long nNumLayers; DWORD dwFlags; } CXIMAGEINFO;
A |
CxImage::pDib
is the background image. CxImage::pAlpha
is the transparency layer. CxImage::pSelection
is the selection layer, used to create regions of interest for image processing. Over these 3 specific planes, you can add other generic layers, stored in
CxImage::ppLayers
. The generic layers are full CxImage objects, so you can build complex structures of nested layers.
CxImage::ppFrames
is reserved for animated images (GIF)
CxImage Class Members & Operations
CxImage is documented using Doxygen , however for historical reasons, many uncommon features are still undocumented. The class members reference, together with release history, and license, can be found here
Supported Formats and Options
The whole library is quite big, in the main header file ximcfg.h you'll find the switches to enable or disable a specific graphic format or feature. Each JPG, PNG and TIFF library adds about 100KB to the final application, while the CxImage
impact is about 50KB. So you should support and link only the formats that your application really needs.
formats | #define | required libraries | size [Kbyte] |
BMP GIF ICO TGA PCX WBMP WMF SKA |
CXIMAGE_SUPPORT_BMP<br />CXIMAGE_SUPPORT_GIF<br />CXIMAGE_SUPPORT_ICO<br />CXIMAGE_SUPPORT_TGA<br />CXIMAGE_SUPPORT_PCX<br />CXIMAGE_SUPPORT_WBMP<br />CXIMAGE_SUPPORT_WMF<br />CXIMAGE_SUPPORT_SKA |
built in |
24 |
JPEG | CXIMAGE_SUPPORT_JPG |
jpeg |
88 |
PNG | CXIMAGE_SUPPORT_PNG |
png, zlib |
104 |
MNG | CXIMAGE_SUPPORT_MNG |
mng, zlib, jpeg |
148 |
TIFF | CXIMAGE_SUPPORT_TIF |
tiff, zlib, jpeg |
124 |
JBIG | CXIMAGE_SUPPORT_JBG |
jbig |
28 |
PNM,PPM,PGM RAS |
CXIMAGE_SUPPORT_PNM<br />CXIMAGE_SUPPORT_RAS |
jasper |
176 |
JPEG-2000 |
CXIMAGE_SUPPORT_JP2<br />CXIMAGE_SUPPORT_JPC<br />CXIMAGE_SUPPORT_PGX |
jasper |
176 |
RAW | CXIMAGE_SUPPORT_RAW |
libdcr | 132 |
Option | #define | Size [Kbyte] |
CxImage core | all switches off | 20 |
geometric transformations | CXIMAGE_SUPPORT_TRANSFORMATION |
16 |
image processing | CXIMAGE_SUPPORT_DSP |
24 |
drawing and windows specific functions | CXIMAGE_SUPPORT_WINDOWS |
12 |
transparency | CXIMAGE_SUPPORT_ALPHA |
4 |
selections | CXIMAGE_SUPPORT_SELECTION |
4 |
multiple layers | CXIMAGE_SUPPORT_LAYERS |
< 4 |
graphic formats conversion |
CXIMAGE_SUPPORT_DECODE<br />CXIMAGE_SUPPORT_ENCODE |
< 4 |
interpolation functions | CXIMAGE_SUPPORT_INTERPOLATION |
< 4 |
exception handling | CXIMAGE_SUPPORT_EXCEPTION_HANDLING |
< 4 |
Using CxImage in your Projects
The CxImgLib.dsw workspace shows the libraries required to build an application (demo.exe) including almost all the features and the formats available in CxImage. You must compile all the libraries before you can link the final application.
Building the projects will need some minutes to complete. When everything is done, select the demo project and launch the application. |
CxImgLib.dsw |
To use CxImage in your project, you must edit these settings:
Project Settings
|- C/C++
| |- Code Generation
| | |- Use run-time library : Multithreaded DLL (must be the same for
| | | all the linked libraries)
| | |- Struct member alignment : must be the same for all the linked
| | | libraries
| |- Precompiled headers : not using precompiled headers
| |- Preprocessor
| |- Additional Include Directories: ..\cximage
|- Link
|- General
|- Object/library modules: ../png/Debug/png.lib
../raw/Debug/libdcr.lib
../jpeg/Debug/jpeg.lib
../zlib/Debug/zlib.lib
../tiff/Debug/tiff.lib
../jasper/Debug/jasper.lib
../cximage/Debug/cximage.lib ...
In your source code you must add #include "ximage.h"
Note: don't mix debug and release modules; each configuration must use its respective library modules.
Adding your Custom Functions in CxImage
Writing a new function for image processing is not so hard with CxImage
. Here I'm going to describe
CxImage::Jitter
— it's very simple but it shows many aspects to take care of when you work inside CxImage. The first thing, of course, is the declaration :
bool Jitter(long radius=2);
in the CXIMAGE_SUPPORT_DSP
section of ximage.h, you can declare the function everywhere in the
public
scope of the class. And now the definition:
bool CxImage::Jitter(long radius)
{
// check if the image is valid, this should be always the first line in
// the function
if (!pDib) return false;
// local variables
long nx,ny;
// temporary image to store the partial results of the algorithm
CxImage tmp(*this,pSelection!=0,true,true);
// limit the effects of the functions only in the smallest rectangle that
// holds the selected region (defined with the Selection...() functions ),
// this will speed up the loops.
long xmin,xmax,ymin,ymax;
if (pSelection){
xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
} else {
xmin = ymin = 0;
xmax = head.biWidth; ymax=head.biHeight;
}
// main loop : scan the image in vertical direction
for(long y=ymin; y <ymax; y++){
// monitor the progress of the loops
info.nProgress = (long)(100*y/head.biHeight);
// let the application a way to exit quickly
if (info.nEscape) break;
// main loop : scan the image in horizontal direction
for(long x=xmin; x<xmax; x++){
// if the feature is enabled, process only the pixels inside the
// selected region
#if CXIMAGE_SUPPORT_SELECTION
if (SelectionIsInside(x,y))
#endif //CXIMAGE_SUPPORT_SELECTION
{
// main algorithm
nx=x+(long)((rand()/(float)RAND_MAX - 0.5)*(radius*2));
ny=y+(long)((rand()/(float)RAND_MAX - 0.5)*(radius*2));
if (!IsInside(nx,ny)) {
nx=x;
ny=y;
}
// save the result in the temporary image.
// if you can, use PixelColor only for 24 bpp images,
// and PixelIndex for 8, 4 and 1 bpp images : it's faster
if (head.biClrUsed==0){
tmp.SetPixelColor(x,y,GetPixelColor(nx,ny));
} else {
tmp.SetPixelIndex(x,y,GetPixelIndex(nx,ny));
}
// if the feature is enabled, process also the pixels
// in the alpha layer
#if CXIMAGE_SUPPORT_ALPHA
tmp.AlphaSet(x,y,AlphaGet(nx,ny));
#endif //CXIMAGE_SUPPORT_ALPHA
}
}
}
// save the result and exit
Transfer(tmp);
return true;
}
Examples: How to ...
... Convert from One Format to Another
CxImage image;
// bmp -> jpg
image.Load("image.bmp", CXIMAGE_FORMAT_BMP);
if (image.IsValid()){
if(!image.IsGrayScale()) image.IncreaseBpp(24);
image.SetJpegQuality(80);
image.Save("image.jpg",CXIMAGE_FORMAT_JPG);
}
// png -> tif
image.Load("image.png", CXIMAGE_FORMAT_PNG);
if (image.IsValid()){
image.Save("image.tif",CXIMAGE_FORMAT_TIF);
}
... Load an Image Resource
//Load the resource IDR_PNG1 from the PNG resource type
CxImage* newImage = new CxImage();
newImage->LoadResource(FindResource(NULL,MAKEINTRESOURCE(IDR_PNG1),
"PNG"),CXIMAGE_FORMAT_PNG);
or
//Load the resource IDR_JPG1 from DLL
CxImage* newImage = new CxImage();
HINSTANCE hdll=LoadLibrary("imagelib.dll");
if (hdll){
HRSRC hres=FindResource(hdll,MAKEINTRESOURCE(IDR_JPG1),"JPG");
newImage->LoadResource(hres,CXIMAGE_FORMAT_JPG,hdll);
FreeLibrary(hdll);
}
or
//Load a bitmap resource;
HBITMAP bitmap = ::LoadBitmap(AfxGetInstanceHandle(),
MAKEINTRESOURCE(IDB_BITMAP1)));
CxImage *newImage = new CxImage();
newImage->CreateFromHBITMAP(bitmap);
... Decode an Image from Memory
CxImage image((BYTE*)buffer,size,image_type);
or
CxMemFile memfile((BYTE*)buffer,size);
CxImage image(&memfile,image_type);
or
CxMemFile memfile((BYTE*)buffer,size);
CxImage* image = new CxImage();
image->Decode(&memfile,type);
... Encode an Image in Memory
long size=0;
BYTE* buffer=0;
image.Encode(buffer,size,image_type);
...
image.FreeMemory(buffer);
or
CxMemFile memfile;
memfile.Open();
image.Encode(&memfile,image_type);
BYTE* buffer = memfile.GetBuffer();
long size = memfile.Size();
...
image.FreeMemory(buffer);
... Create a Multipage TIFF
CxImage *pimage[3];
pimage[0]=&image1;
pimage[1]=&image2;
pimage[2]=&image3;
FILE* hFile;
hFile = fopen("multipage.tif","w+b");
CxImageTIF multiimage;
multiimage.Encode(hFile,pimage,3);
fclose(hFile);
or
FILE* hFile;
hFile = fopen("c:\\multi.tif","w+b");
CxImageTIF image;
image.Load("c:\\1.tif",CXIMAGE_FORMAT_TIF);
image.Encode(hFile,true);
image.Load("c:\\2.bmp",CXIMAGE_FORMAT_BMP);
image.Encode(hFile,true);
image.Load("c:\\3.png",CXIMAGE_FORMAT_PNG);
image.Encode(hFile);
fclose(hFile);
... Copy/Paste an Image
//copy
HANDLE hDIB = image->CopyToHandle();
if (::OpenClipboard(AfxGetApp()->m_pMainWnd->GetSafeHwnd())) {
if(::EmptyClipboard()) {
if (::SetClipboardData(CF_DIB,hDIB) == NULL ) {
AfxMessageBox( "Unable to set Clipboard data" );
} } }
CloseClipboard();
//paste
HANDLE hBitmap=NULL;
CxImage *newima = new CxImage();
if (OpenClipboard()) hBitmap=GetClipboardData(CF_DIB);
if (hBitmap) newima->CreateFromHANDLE(hBitmap);
CloseClipboard();
... Display a File in a Picture Box
HBITMAP m_bitmap = NULL;
CxImage image("myfile.png", CXIMAGE_FORMAT_PNG);
...
CDC* hdc = m_picture.GetDC();
HBITMAP m_bitmap = image.MakeBitmap(hdc->m_hDC);
HBITMAP hOldBmp = m_picture.SetBitmap(m_bitmap);
if (hOldBmp) DeleteObject(hOldBmp);
if (hdc->m_hDC) m_picture.ReleaseDC(hdc);
...
if (m_bitmap) DeleteObject(m_bitmap);
History and Credits
Starting form my CxDib
class, that implements memory DIBs only, I tried to add some members to read images from files. Looking for a solution, I found a nice MFC class named
CImage
on the net, release 1.4 (1998). CImage
supports BMP, GIF, PNG and JPG, but suffers many little bugs and uses a complex class structure, so I decided to strip it to the base and merge
CxDib
with the CImage
philosophy, to obtain the new
CxImage
class. Also I updated the libraries for JPG, PNG and ZLIB.
With CxImage
it is very easy to add new image types, so I added the TIFF library (rev. 6) and a minimal support for
ICON
s, MNG, TGA and PCX. Finally I added some specific functions to obtain an image from global
HANDLE
s (windows clipboard) and objects (windows resources). This is the story until the early release, the following is written in the
documentation.
- CImage © 1995-1998, Alejandro Aguilar Sierra.
- IJG JPEG library ©1994-1998, Thomas G. Lane.
- LibPNG version 1.2.7© 1998-2007 Glenn Randers-Pehrson
- LibTIFF version 3.5.7 © 1988-1997 Sam Leffler, © 1991-1997 Silicon Graphics, Inc.
- LibMNG version 1.0.10 © 2000,2002 Gerard Juyn.
- Gif-RLE © Hutchison Avenue Software Corporation, 1998
- LibJ2K© David Janssens, 2001 - 2002
- LibJBG version 1.6 © Markus Kuhn, 2004
- JasPer version 1.900.1 © Image Power, UBC, Michael David Adams, 2001 - 2007
- zlib version 1.2.3 © 1995-2005 Jean-loup Gailly and Mark Adler
- Thanks to Troels Knakkergaard for his precious work in the earlier versions of CxImage, Rajiv Ramachandran for
CTwain
code; to Abe for multi page tiffs code; to Chris Shearer Cooper for memory file suggestions and code; to Brent Corkum forBCMenu
code.
More specific credits and disclaimers are in every header file of each library.