Sample
application using the
thumbnail view
Introduction
In an
image viewer
application it may be useful to display a list of
image thumbnails.
Also, one may need to display a list of images
into a list control or a
list view. Displaying the images in a
list control inside a dialog box or a
CFormView-derived
class may be somehow easier to implement, since a
CListCtrl-derived
object can be used, but displaying it in a
CListView-derived
class is not so obvious, because we cannot use our
own CListCtrl-derived
class into a CListView
(there are some articles
describing this issue: How do I use a derived
CListCtrl with
a CListView,
and Using derived
CListCtrl in
CListView - Undocumented ).
Implementation
The solution
presented in this article uses a
JPEG reader class
(Class
library for image file decoding )
to read JPEG images
and display them into a
CListCtrl. The idea is to create an
image list that holds the icons created from the
JPEG thumbails.
The first problem
arises with the fact that
MFC class CImageList does not
support higher color depths than 16 colors (4 bits
per pixel). Another interesting issue is that
image loading takes quite some time. This article
addresses both these issues.
1. Creating an
image list with higher color depth.
The less-known
SDK macro ImageList_Create meets this problem.
// Create the image list with 100*100 icons and 32 bpp color depth
HIMAGELIST hImageList=ImageList_Create(100, 100, ILC_COLOR32, 0, 10);
m_imageList.Attach(hImageList);
// load the starting bitmap ("Loading..." and "Corrupt file")
CBitmap dummy;
dummy.LoadBitmap(IDB_NAILS100);
m_imageList.Add(&dummy, RGB(0, 0, 0));
// Use the image list in the list view
GetListCtrl().SetImageList(&m_imageList, LVSIL_NORMAL);
GetListCtrl().SetImageList(&m_imageList, LVSIL_SMALL);
2. Adding
items to the list control
Adding items to
the list control is quite simple if we are not
writing a time-critical application. If there is a
great number of thumbnails to load, the user will
see just a moving scroll bar flashing on the
screen while the images are being loaded from
disk. Instead we can use a simple thread mechanism
to add the list items while another thread just
loads the images, and all this while the user
continues to interact with the application.
The member
function that loads items into the list control
will be something like:
// structure used to pass parameters to a image adder thread
struct threadParamImage
{
CString folder; // the folder to be scanned for thumbnails
CThumbnailView* pView; // the view that accomodates them
HANDLE readPipe; // the pipe used to pass thumbnail filenames to the JPEG loader thread
};
struct threadParam
{
CThumbnailView* pView; // the view that shows thumbnails
HANDLE readPipe; // the pipe used to pass thumbnail filenames to the JPEG loader thread
};
HANDLE hThread =NULL; // handle to the JPEG image loader thread
HANDLE readPipe =NULL; // read and write ends of the communication pipe
HANDLE writePipe =NULL;
HANDLE skipImages =NULL;// handle to the semaphore that signals the pipe does no longer hold consistent data
HANDLE imageFiller =NULL;// handle to the thumbnail adder thread
HANDLE imageFillerSemaphore =NULL;// thread termination flag (when this semaphore goes signaled, the thread must exit)
HANDLE imageFillerCR =NULL;// thumbnail adder thread critical section semaphore
HANDLE imageFillerWait =NULL;// second thumbnail adder thread critical section semaphore
// Fill in list control with thumbails from a specified folder
BOOL CThumbnailView::FillInImages(CString folder)
{
// create semaphores the first time only
if (!imageFillerSemaphore)
{
imageFillerSemaphore=CreateSemaphore(NULL, 0,1, NULL);
imageFillerCR=CreateSemaphore(NULL, 1,1, NULL);
imageFillerWait=CreateSemaphore(NULL, 1,1, NULL);
}
// critical region starts here
WaitForSingleObject(imageFillerCR, 0);
// create thread parameters
threadParamImage* pParam=new threadParamImage;
pParam->folder=folder;
pParam->pView=this;
// and the thread
DWORD dummy;
imageFiller=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ImageFillerThread, pParam, 0, &dummy);
return TRUE;
}
While the image
filler thread is:
// thumbnail adder thread
DWORD ImageFillerThread(DWORD param)
{
// get thread parameters
threadParamImage* pParam=(threadParamImage*)param;
CString folder=pParam->folder;
CThumbnailView* pView=pParam->pView;
HANDLE readPipe=pParam->readPipe;
// cleanup
delete pParam;
// wait for previous copies to stop
WaitForSingleObject(imageFillerWait, INFINITE);
// clear previous images from list control
pView->GetListCtrl().DeleteAllItems();
// start scanning designated folder for thumbnails
WIN32_FIND_DATA fd;
HANDLE find;
BOOL ok=TRUE;
fd.dwFileAttributes=FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_COMPRESSED|FILE_ATTRIBUTE_READONLY;
find=FindFirstFile(folder+"\\_thumbs\\*.*", &fd);
// return if operation failed
if (find==INVALID_HANDLE_VALUE)
{
ReleaseSemaphore(imageFillerWait, 1, NULL);
ExitThread(0);
return 0;
}
// critical section ends here
ReleaseSemaphore(imageFillerCR, 1, NULL);
// start adding items to the list control
do
{
if (WaitForSingleObject(imageFillerSemaphore, 0)==WAIT_OBJECT_0)
{
// thread is signaled to stop
// signal skip to JPEG file loader
int skip=-1;
DWORD dummy;
WriteFile(writePipe, &skip, sizeof(int), &dummy, NULL);
ReleaseSemaphore(skipImages, 1, NULL);
break;
}
ok=FindNextFile(find, &fd);
if(fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)continue;
if (ok)
{
int item=pView->GetListCtrl().InsertItem(pView->GetListCtrl().GetItemCount(), fd.cFileName, 0);
pView->GetListCtrl().SetItemPosition(item, CPoint(105*item, 5));
pView->AddImage(CString(folder+"\\_thumbs\\")+fd.cFileName, item);
}
}
while (find&&ok);
// done adding items
FindClose(find);
ReleaseSemaphore(imageFillerWait, 1, NULL);
ExitThread(0);
return 0;
}
The last but not
the least is the JPEG
image loader thread:
// JPEG image loader thread
DWORD ImageLoaderThread(DWORD param)
{
// wait to get a filename, then build the image, add it to the image list then update list control
CThumbnailView* pView=(CThumbnailView*)param;
DWORD dummy;
char buffer[1024];
while(1)
{
int itemIndex;
int size;
if (WaitForSingleObject(skipImages, 0)==WAIT_OBJECT_0)
{
// skip to marker
do
{
ReadFile(readPipe, &itemIndex, sizeof(int), &dummy, NULL);
if(itemIndex==-1)break;
ReadFile(readPipe, &size, sizeof(int), &dummy, NULL);
ReadFile(readPipe, buffer, size, &dummy, NULL);
}
while (1);
}
// get data from pipe
ReadFile(readPipe, &itemIndex, sizeof(int), &dummy, NULL);
ReadFile(readPipe, &size, sizeof(int), &dummy, NULL);
ReadFile(readPipe, buffer, size, &dummy, NULL);
buffer[size]=0;
// is the file name valid ?
OFSTRUCT ofs;
if(OpenFile(buffer, &ofs, OF_EXIST)==HFILE_ERROR)continue;
// load an image from disk (using PaintLib)
CWinBmp bitmap;
CAnyPicDecoder decoder;
try
{
decoder.MakeBmpFromFile(buffer, &bitmap, 0);
}
catch (CTextException exc)
{
pView->GetListCtrl().SetItem(itemIndex, 0, LVIF_IMAGE, NULL, 1, 0, 0, 0);
continue;
}
// create a CBitmap object from the data within the CWinBmp object
BITMAPINFOHEADER& bmiHeader=*bitmap.GetBMI();
BITMAPINFO& bmInfo=*(BITMAPINFO*)bitmap.GetBMI();
LPVOID lpDIBBits = (LPVOID)((LPDWORD)(bmInfo.bmiColors + bmInfo.bmiHeader.biClrUsed) +
((bmInfo.bmiHeader.biCompression == BI_BITFIELDS) ? 3 : 0));
CClientDC dc(NULL);
HBITMAP hBmp=CreateDIBitmap(dc.m_hDC, &bmiHeader, CBM_INIT, lpDIBBits, &bmInfo, DIB_RGB_COLORS);
CBitmap bmp;
bmp.Attach(hBmp);
// add the thumbnail to the image list
int imgPos=pView->m_imageList.Add(&bmp, RGB(0, 0, 0));
pView->GetListCtrl().SetItem(itemIndex, 0, LVIF_IMAGE, NULL, imgPos, 0, 0, 0);
}
ExitThread(1);
return 0;
}
The code uses SDK
semaphores, pipes and threads because they are
easier to handle and much straightforward than MFC
threads and synchronization mechanisms. There is
also a piece of code took from the
article
Creating a bitmap object from a BMP file and
adapted to work with data returned by the
PaintLib image
handling library.
The code is quite
easy to follow and change to meet your needs, but
if you need assistance, contact me. Also please
send me bugs or updates, to keep this solution
up-to-date. For more details on the sample
application, contact me.