Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

FreeImage Display Demo

0.00/5 (No votes)
4 Jun 2008 1  
How to display a bitmap in your MFC SDI application using FreeImage. Various rescaling algorithms considered.
fi_test_demo.gif

Introduction

This article was inspired by another Code Project article - Load, show and convert miscellaneous file-formats using freeimage by Markus Loibl.

The main purpose of this article is to answer the simple question: "How do I display a bitmap in my MFC application if I use the FreeImage library"?

This is still one of the key problems when using the FreeImage library under Windows. It is even posted on the FreeImage FAQ page.

Using the code

Markus Loibl has built his article around the idea of using the StretchDIBits and SetDIBitsToDevice functions to display a bitmap. My idea is to use the CreateDIBSection function instead.

This is the further development of the CreateDIBSection bitmap-display approach described in the book Programming Windows / Charles Petzold. 5th ed. 1998. In that book the CreateDIBSection function is used to display a BMP-file - and I just expanded this idea over the FreeImage library. This happened to be possible because the FIBITMAP structure luckily turned out to be programmatically just exactly the same as the BMP internal file structure! So the idea of the FreeImage library is to read any popular graphical-format bitmap and unpack it in the memory as a "BMP internal file structure" (e.g. "FIBITMAP").

Using CreateDIBSection would offer many advantages over any other bitmap-display choices - because we deal now with a DIB section - instead of dealing with either DIB or DDB.

The main piece of the code is quite simple. We load a graphical file into a FIBITMAP structure and pass the pointer to its BITMAPINFO header to the CreateDIBSection function. This function creates an empty HBITMAP and returns it.

We attach this empty HBITMAP GDI structure to a CBitmap MFC object and select the latter into a previously created memory device context.

After we copy the bitmap bits from our FIBITMAP structure into a CreateDIBSection-allocated memory buffer - using the m_pBits pointer provided by CreateDIBSection and finally bit-blit our ready-to-use CBitmap onto the screen. That's it.

//CMyDoc::DisplayBitmap()

FIBITMAP* m_dib;
BYTE* m_pBits;
CBitmap m_OffscreenBitmap, *m_pOldBitmap;
CDC m_dcOffscreen;
BITMAPINFO* m_pbi;
CString FileName; // containing the path of the to-be-opened file

CClientDC dc (NULL);

m_dcOffscreen.CreateCompatibleDC(&dc);

m_dib = GenericLoader(FileName.GetBuffer(FileName.GetLength()), 0);
// This function is described in the FreeImage PDF Help - see there the "FreeImage_GetFileType"
// function description

m_pbi = FreeImage_GetInfo(m_dib);

HBITMAP hBitmap = CreateDIBSection (NULL, m_pbi, DIB_RGB_COLORS, (void**)&m_pBits, NULL, 0);

m_OffscreenBitmap.Attach(hBitmap);

m_pOldBitmap = m_dcOffscreen.SelectObject(&m_OffscreenBitmap);
	
// Fill the DIB Section bitmap buffer (e.g. content to be displayed)
	
CopyMemory(m_pBits, FreeImage_GetBits(m_dib),
	FreeImage_GetPitch(m_dib) * FreeImage_GetHeight(m_dib));
...
 
//CMyScrollView::OnDraw(CDC* pDC)

CRect rcClip;

pDC->GetClipBox(rcClip);
  
pDC->BitBlt(rcClip.left, rcClip.top, rcClip.Width(), rcClip.Height(),
&pDoc->m_dcOffscreen, rcClip.left, rcClip.top, SRCCOPY);

Points of Interest

The sourcecode of the demo program is well-commented. Some interesting key moments are:

  • Not using the CDC::StretchBlt function for zooming. Instead the program uses the FreeImage_Rescale function - because it is much more flexible and high-qualitative. TIP: Do not ever use all these standart "stretch"-functions! Or you'll get a terrible-quality zoomed image.
  • Drag-and-Drop enabled
  • The main program window is opened in maximized mode (although this is an SDI app - where this is problematic).
  • Double buffering and flicker-free bitmap scrolling enabled

This demo program was kept as simple as possible. That's why the program's performance is not the best yet. Some ways of the improvement are:

  • Replace FreeImage_Rescale with an analoguous function - which would additionally accept the pointer to the DIB section bits and fill it with the resampled image - rather then simply return a new FIBITMAP. That approach would allow to eliminate the excessive bits copying (from FIBITMAP to the DIB section bits' buffer) thus cutting down the zooming-time significantly. As now the zooming-procedure is too lenghty (which is totally inappropriate).
  • Create a child window for the displayed bitmap - not to display it directly into the CMyScrollView as now. That would allow the flicker-free zooming - because the program would not have trouble determining where is the image and where is the background (as now). Yet the image flickers at the zooming-procedure (that is totally inappropriate too).

Version 2.0

This version has 2 major improvements:

  • The background memory device context and the background bitmap are introduced. The image does not flicker at the zooming-procedure anymore. The image and the background are now clearly distinguished.
  • The FreeImage_Rescale function is replaced in the way which was planned in Version 1.0.

Using the code

1. New background objects:

//CMyDoc::DisplayBitmap()

CDC m_dcBitmapHolder;  // a new memory DC - to hold our image
CPoint m_OffscreenSize;     // background bitmap size
CBitmap m_Bitmap, *m_pOldBitmap; // a new CBitmap - to hold our image

CClientDC dc (NULL);

m_dcBitmapHolder.CreateCompatibleDC(&dc); // a new CDC - to hold our image

HBITMAP hBitmap = CreateDIBSection (NULL, m_pbi, DIB_RGB_COLORS, (void**)&m_pBits, NULL, 0) ;
	
if (hBitmap == NULL) return false;
	
m_Bitmap.Attach(hBitmap); // our image in a MFC CBitmap

// Put the image into the CDC-holder
m_pOldBitmap = m_dcBitmapHolder.SelectObject(&m_Bitmap);

// Find out the maximum viewable sizes - for the future background bitmap
// Choose max between client area sizes and zoomed image sizes
m_OffscreenSize.x = max(m_view_init_width, m_zdib_width);	
m_OffscreenSize.y = max(m_view_init_height, m_zdib_height);
	
// Create a background bitmap
// ATTENTION: It is made here compatible with CDC-holder!!!
m_OffscreenBitmap.CreateCompatibleBitmap(&m_dcBitmapHolder,
    m_OffscreenSize.x, m_OffscreenSize.y);

// Select the background bitmap into the background CDC
m_pOldBtmp = m_dcOffscreen.SelectObject(&m_OffscreenBitmap);
	
// Fill the background bitmap with a backcolor
m_dcOffscreen.FillSolidRect(0, 0, m_OffscreenSize.x,
    m_OffscreenSize.y, RGB(160, 160, 160));
	
// Put the image-bitmap onto the center of the background bitmap
// (all this - in the memory device context yet)
m_dcOffscreen.BitBlt((m_OffscreenSize.x - m_zdib_width)/2,		
(m_OffscreenSize.y - m_zdib_height)/2, m_zdib_width, m_zdib_height,
    &m_dcBitmapHolder, 0, 0, SRCCOPY);
...

The general idea is simple: we create a background bitmap - sized at the maximum possible viewable area - and select it into a new (second) memory device context. Then we bit-blit our image from the old memory device context to the new one - and after we use it right in place of the old CDC.

I borrowed this technique from the WinDjView Project sourcecodes. It allows to easily "erase" the background without the flicker. I called it "Double memoring" - because 2 memory device contexts are used to display one image (instead of one CDC as in Version 1.0).

This method has a performance flaw - it requires one extra bit-blitting of the display image. This happens at the zooming-procedure. Nethertheless the possibility to completely eliminate the zoom-flicker is worth it I believe.

2. Rearranged resampling function:

//CMyDoc::DisplayBitmap()

// Calculate the zoomed image width
m_zdib_width = (int)(FreeImage_GetWidth(m_dib)/zoom);
	
// Calculate the zoomed image height
m_zdib_height = (int)(FreeImage_GetHeight(m_dib)/zoom);
	
// Allocate the "empty" FIBITMAP - just as a BITMAPINFO holder
m_dib2 = FreeImage_AllocateT(FreeImage_GetImageType(m_dib),
    1, 1, // this is a one-by-one pixel sized bitmap ("empty")
    bpp_dst,
    FreeImage_GetRedMask(m_dib),
    FreeImage_GetGreenMask(m_dib),
    FreeImage_GetBlueMask(m_dib));

if (!m_dib2) return false;	

m_pbih = FreeImage_GetInfoHeader(m_dib2);	

m_pbih->biWidth = m_zdib_width;  // replace 1 by the real rescaled width

m_pbih->biHeight = m_zdib_height;  // replace 1 by the real rescaled height

m_pbi = FreeImage_GetInfo(m_dib2);  // now we have a full-fledged
// BITMAPINFO structure for our rescaled image

...

// Fill the DIB Section bitmap buffer (e.g. content to be displayed)

// This function is remade out of FreeImage_Rescale. 
// It rescales a src FIBITMAP placing the result into the provided 
// BYTE* m_pBits buffer
	
FI_Rescale2(m_dib, m_zdib_width, m_zdib_height,
   
    FILTER_BILINEAR, // the best choice here probably
		
    bpp_dst, m_pBits);
...		

The general idea here is to separate the destination FIBITMAP into 2 parts: the header (BITMAPINFO structure) and the data bits (m_pBits pointer).

This makes sense in order to eliminate one extra bits copying - after a rescale-function returns.

I rearranged the standart FreeImage_Rescale function - to make it accept a m_pBits pointer - where the function directly writes a rescaled image. To do so I had to dig quite deeply into the FreeImage_Rescale - see the project's sourcecodes.

First we create an "empty" fake FIBITMAP (sized 1x1, but we write the true bitmap's width and height there) - which serves just as a BITMAPINFO container - for the CreateDIBSection function.

Then we call the FI_Rescale2 function - it fills the CreateDIBSection-allocated buffer using the m_pBits pointer - which is one of this function's parameters.

Conclusion on ver.2

1. The demo program works now in completely flicker-free mode - in any sense.

2. The introduction of the FI_Rescale2 function actually did not bring any remarkable zooming performance speed-up. The problem is that the FILTER_BILINEAR resampling method showed to be too slow by itself. It should be replaced with a faster method (but not with FILTER_BOX which is ugly).

3. I changed the default project configuration from Debug to Release. The reason is that the FI_Rescale2 function works incredibly slow in the Debug configuration - most likely due to some non-evident lenghty ASSERT check-ups.

Version 2.1

This subversion implements another one rescaling mode: PNM fixed scaling.

It is a NetPBM Project rescaling algorithm. I took its sourcecode from the WinDjView Project sourcecodes (Scaling.cpp) and adopted it for the FreeImage library. Yet it supports only 1-, 8- and 24-bit bitmaps scaling - although it can be easily modified to support all the FreeImage-supported bitdepths.

In the WinDjView program this rescaling algorithm serves as an optional one - it is used there when the "Sharp scaling at low zoom levels (slower)" checkbox is set. Normally WinDjView uses some natural DjVuLibre-provided rescaling algorithm.

The PNM fixed rescaling algorithm works significantly faster then FreeImage's Bilinear (and even Box) rescaling algorithms - providing high-quality zoomed images.

But it has a great flaw: it hangs at the big-zoomed-images zoom-values. I suspect that this algorithm is just too much memory-consuming.

The demo program in Version 2.1 incorporates 3 zoom modes: PNM, Bilinear and Box. You can visually compare the performance of all of these 3 algorithms.

Conclusion on ver.2.1

The PNM fixed scaling algorithm is unfortunately completely inappropriate for a good bitmap-display program. And the Bilinear and Box algorithms (from FreeImage) are even much more inappropriate for that purpose.

The main goal now - is to find and FreeImage-implement some good, fast, low-memory consuming and high-qulitative bitmap-rescaling algorithm.

Version 2.2

This subversion implements another two rescaling algorithms: Linear interpolation and Nearest pixel sampling. I took their sourcecodes from the Leptonica Project sourcecodes (scalelow.c) and adopted them for the FreeImage library. Yet they support only 1-, 8- and 24-bit bitmaps scaling - although they can be easily modified to support all the FreeImage-supported bitdepths.

These rescaling algorithms are visibly almost twice faster as PNM fixed scaling (from the previous subversion). But unfortunately they almost do not smoothe the rescaled image. And they also hang at big-zooms (like all the others) - but the good news is that they actually hang at much bigger zooms than PNM fixed scaling does.

I also found an appropriate article in Wikipedia - Image scaling.

Conclusion on ver.2.2

These two new scaling algorithms are also most likely inappropriate for a good bitmap-display program. But they already look like the algorithm I am looking for. If they would smoothe the destination image as good as the PNM fixed scaling algorithm and wouldn't hang at big-zooms - that would be it.

So the search of a good rescaling algorithm still continues.

Version 3.0

This version brings back the single-bit-blitting at the zooming - but on a new level already. The rescaled image is written now directly on the background bitmap surface - rather than in the temp bitmap (to be blitted later on the background bitmap surface).

This is done in a bit tricky way: every rescaled scanline is written separately in a specified place (around the center) of the background bitmap.

That approach eliminates one extra whole image copying - which definitely increases the speed of the zoom-procedure.

I chose the fastest rescaling algorithm from the previous version - Nearest neighbor scaling (from the Leptonica Project) sourcecodes. All the other rescaling algorithms are left off in this version (perhaps temporarily).

Conclusion on ver.3.0

As was said earlier this version's rescaling algorithm has a flaw: it does not smoothe the image. So I have it my plans to search for a additional good enough smoothing algorithm.

Another problem is that this algorithm (like all the others) hangs at big-zooms. So this issue is to be solved also.

Nethertheless this version bring more progress in the studied field.

Version 3.1

This subversion brings back all the previously mentioned rescaling algorithms - but adopted for the single-bit-blitting at the zooming (first introduced in ver. 3.0). It also introduces a new rescaling algorithm: Low-pass filtering - taken from the Leptonica Project sourcecodes.

You can visually compare the quality and the performance of each one of them. I guess that some of them even double each other in terms of the algorithmic logic - since they all were taken from the different sources. Some of them are fast, some are visually high-qualitative - but not both together.

Conclusion on ver.3.1

The main improvement-goals are still not reached - the optimal combination "speed + quality" is not found yet. But now it is much easier to look for one than it was earlier - we have now much ready-to-use stuff to dig in.

Version 3.2

This subversion introduces a new rescaling algorithm - Areamap rescaling. It is taken from the Leptonica Project sourcecodes and adopted for FreeImage.

This algorithm is both fast and visually high-qualitative. Yes, it is not as fast as Nearest neighbor - but much faster than Bilinear - and offers almost the same visual quality.

But it has a great flaw: when a bitmap is maximized (step by step) - at some moment you see a grid of vertical and horizontal lines which suddenly appear over all the image.

I guess that this is just a poorly implemented algorithm - probably the corresponding mathematical algorithm is good enough and could be well-implemented.

Conclusion on ver.3.2

If not the described flaw - that would right an algorithm I am looking for. Fast enough and visually great. But! - nothing to do yet... So the search continues.

Version 3.3

Nothing much special here. Just one bug is fixed. I have to align the displayed-picture-bitmap at a 6-byte boundary on the background bitmap (by the way, I don't know why, this was figured out experimentally).

I used to align it on-the-right from the center - and that caused memory-access exception on many images (all earlier ver.3.x are affected). Now I just align it on-the-left from the center - which prevents wrong memory access (at bigger zooms).

I added a "Useful Links" section to this article.

Version 3.4

This subversion introduces a new rescaling algorithm. It is just a combination of the Areamap and Linear rescaling algorithms (both taken from the Leptonica Project sourcecodes and adopted for FreeImage).

The minified images are displayed with Areamap, the magnified ones - with Linear. These algorithms are switched on-the-fly - as a user rotating the mousewheel passes the 1.0-zoom barrier in any direction. I called this "algorithm" AreaLinear (just to give it some name).

Why did I do this? The answer is: speed. AreaLinear is quite fast - but also it offers a sufficient smoothing - on any zoom value. So this is a best-compromise algorithm among all the previously considered.

The difference between how do Areamap and Linear smoothe a bitmap is very small, almost visually unnoticeable. If you don't know that there are actually 2 rescaling algorithms - not just one - you wouldn't notice it. In this subversion I display the current zoom value on the Status Bar - so you can watch yourself how few does the smoothing visually change when passing 1.0 zoom barrier - it can be hardly seen mainly on some 24-bit color images only.

Yes, the standart FreeImage rescaling algorithms offer a better-quality smoothing. But they are dramatically slow. The main reason why - is that they all are the 2-pass algorithms - and, on the contrary, all Leptonica rescaling algorithms are 1-pass.

I tried to look for faster implementations of the 2-pass rescaling algorithms. I took the Image Resampler sourcecode and adopted it for FreeImage. It is the code of the Graphic Gems III Filtered Image Rescaling by Dale Schumacher revised by Ray Gardener, Daylon Graphics Ltd. See FilterRcg.cpp in my sourcecode. Then I took the original code of the Graphic Gems III Filtered Image Rescaling by Dale Schumacher - see FilterRcg2.cpp in my sourcecode.

Unfortunately, both these implementations have the bad quality. When zooming they bring different visual artifacts. I tried hard to fix them - but not succeeded yet (although I eliminated about a half of the artifacts already).

One thing is clear at least: all these 2-pass algorithms implementations (including the one from FreeImage) are basically the same - Paul Heckbert, C code to zoom raster images up or down, with nice filtering. So it is hard to expect to get some speed-up if "to find a better implementation" of them.

Conclusion on ver.3.4

The introduced AreaLinear rescaling mode is, I believe, already good-enough - to be used in a real FreeImage-based MFC image viewer. The only problem yet unsolved - is that is hangs at big zooms - like all the other algorithms. This issue is yet to be solved.

AreaLinear has a little flaw: on very small zooms the image gets some visual artifacts - they look like black teeth on the borders. But this is not much annoying.

Additionally it is a good idea to look for some faster implementations of the 2-pass rescaling algorithms. But this is not much critical now (as it was earlier) - AreaLinear is already a good enough basis for a FreeImage bitmap viewer.

Version 4.0

This version brings the multithreading bitmap-display mechanism. It uses a User-Interface MFC thread (which accepts messages and has its own message loop).

When a bitmap file is opened, a new thread is launched. When the file is closed this thread is closed. The thread displays the bitmap on the screen.

The reason for such multithreading is to make the zooming more visually comfortable. Wnen a user rotates the mousewheel the bitmap image is redrawn in multiple steps - reflecting the intermediary zoom values. Whereas earlier only the finally-zoomed image was displayed - and the act of zooming looked more jerky. Now it looks visually more "fluent".

The thread's constructor accepts some parameters on thread's creation. Two of them are of the most interest: a handle to the CMainFrame object and a pointer to the CSrollView object. The handle is used to send controlling messages to the main program's window, and the pointer is used to control the bitmap-displaying in the CSrollView-deriven view window. For more details see the sourcecodes - they are well commented as usually.

I also optimized a little bit the AreaLinear rescaling algorithm for Black-and-White bitmaps - it works now noticeably faster (visually) with them than before. The 6-byte boundary alignment is replaced to the 3-byte alignment - and left only for color images (for other color modes it is unnecessary).

Version 5.0

This version solves the "big-zoom-hangup" problem. To make it possible I had to re-architect again the image-display mechanism.

In the previous versions an image was layed on the CDC memory device context which was sized differently at every new zoom - to encapsulate the max scroll extent possible. This approach was quite simple - but it produced hang-ups because the bigger the zoom was the wider the CDC object was created - and at some point any further big-zooming became impossible.

To address this issue I made the CDC object of a fixed size - e.g. client-area-sized - no matter what the zoom value is. The image now is clipped exactly to the size of the CDC object and as a result no more big-zoom hang-ups occur.

To compensate the fixed (rather than variable) size of a CDC object I introduced a new CDC helper object - to display only the appearing-after-scroll parts of an image. This new CDC helper object has variable size - it is created on every scroll movement and is sized to the current invalid rectangle.

Additionally this version limits the maximum scroll size to 32767 - to take into consideration the Win98 max scroll size limitation.

Once again I optimized a little bit the AreaLinear rescaling algorithm for Black-and-White bitmaps - it works now even faster (visually) with B/W than in previous version. By the way all the other rescaling algorithms are left off in this version (perhaps temporarily).

Conclusion on ver.5

This version already looks like some "real-life image viewer" prototype. But I especially emphasize - it is not yet some ready-to-use program. Many problems still persist. For example there is some small visual artifact when scrolling at extra-big zooms.

References

  1. Load, show and convert miscellaneous file-formats using freeimage
  2. E-book Programming Windows / Charles Petzold. 5th ed. 1998 (CHM Eng)
  3. E-book Programming Windows with MFC / Jeff Prosise. 2nd ed. 1999 (CHM Eng)
  4. E-book Windows Graphics Programming: Win32 GDI and DirectDraw / Feng Yuan. 2000 (CHM Eng)
  5. WinDjView GPL Project Sourcecodes
  6. Image scaling - Wikipedia
  7. General Scaling - Leptonica

Useful Links

  1. Interpolating Bitmap Resampler for Delphi
  2. Graphics Gems Repository
  3. Image Resampler
  4. Google Answer: algorithm to zoom images
Note: All the mentioned E-books are freely available for download in the Internet.

History

Version 1.0. Posted 26 February 2008.

Version 2.0. Posted 2 March 2008.

Version 2.1. Posted 5 March 2008.

Version 2.2. Posted 6 March 2008.

Version 3.0. Posted 9 March 2008.

Version 3.1. Posted 10 March 2008.

Version 3.2. Posted 13 March 2008.

Version 3.3. Posted 14 March 2008.

Version 3.4. Posted 17 March 2008.

Version 4.0 Posted 3 April 2008.

Version 5.0 Posted 4 June 2008.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here