- Version 1.0
- Version 2.0
- Version 2.1
- Version 2.2
- Version 3.0
- Version 3.1
- Version 3.2
- Version 3.3
- Version 3.4
- Version 4.0
- Version 5.0
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.
FIBITMAP* m_dib;
BYTE* m_pBits;
CBitmap m_OffscreenBitmap, *m_pOldBitmap;
CDC m_dcOffscreen;
BITMAPINFO* m_pbi;
CString FileName;
CClientDC dc (NULL);
m_dcOffscreen.CreateCompatibleDC(&dc);
m_dib = GenericLoader(FileName.GetBuffer(FileName.GetLength()), 0);
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);
CopyMemory(m_pBits, FreeImage_GetBits(m_dib),
FreeImage_GetPitch(m_dib) * FreeImage_GetHeight(m_dib));
...
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:
CDC m_dcBitmapHolder; CPoint m_OffscreenSize; CBitmap m_Bitmap, *m_pOldBitmap;
CClientDC dc (NULL);
m_dcBitmapHolder.CreateCompatibleDC(&dc);
HBITMAP hBitmap = CreateDIBSection (NULL, m_pbi, DIB_RGB_COLORS, (void**)&m_pBits, NULL, 0) ;
if (hBitmap == NULL) return false;
m_Bitmap.Attach(hBitmap);
m_pOldBitmap = m_dcBitmapHolder.SelectObject(&m_Bitmap);
m_OffscreenSize.x = max(m_view_init_width, m_zdib_width);
m_OffscreenSize.y = max(m_view_init_height, m_zdib_height);
m_OffscreenBitmap.CreateCompatibleBitmap(&m_dcBitmapHolder,
m_OffscreenSize.x, m_OffscreenSize.y);
m_pOldBtmp = m_dcOffscreen.SelectObject(&m_OffscreenBitmap);
m_dcOffscreen.FillSolidRect(0, 0, m_OffscreenSize.x,
m_OffscreenSize.y, RGB(160, 160, 160));
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:
m_zdib_width = (int)(FreeImage_GetWidth(m_dib)/zoom);
m_zdib_height = (int)(FreeImage_GetHeight(m_dib)/zoom);
m_dib2 = FreeImage_AllocateT(FreeImage_GetImageType(m_dib),
1, 1, 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;
m_pbih->biHeight = m_zdib_height;
m_pbi = FreeImage_GetInfo(m_dib2);
...
FI_Rescale2(m_dib, m_zdib_width, m_zdib_height,
FILTER_BILINEAR,
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
- Load, show and convert miscellaneous file-formats using freeimage
- E-book Programming Windows / Charles Petzold. 5th ed. 1998 (CHM Eng)
- E-book Programming Windows with MFC / Jeff Prosise. 2nd ed. 1999 (CHM Eng)
- E-book Windows Graphics Programming: Win32 GDI and DirectDraw / Feng Yuan. 2000 (CHM Eng)
- WinDjView GPL Project Sourcecodes
- Image scaling - Wikipedia
- General Scaling - Leptonica
Useful Links
- Interpolating Bitmap Resampler for Delphi
- Graphics Gems Repository
- Image Resampler
- 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.