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 CListCtrl
-derived class is not so obvious, because we cannot use our own CListCtrl
-derived class into a CListView
.
Implementation
The solution presented in this article uses a JPEG reader class 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.
HIMAGELIST hImageList=ImageList_Create(100, 100, ILC_COLOR32, 0, 10);
m_imageList.Attach(hImageList);
CBitmap dummy;
dummy.LoadBitmap(IDB_NAILS100);
m_imageList.Add(&dummy, RGB(0, 0, 0));
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:
struct threadParamImage
{
CString folder;
CThumbnailView* pView;
HANDLE readPipe;
};
struct threadParam
{
CThumbnailView* pView;
HANDLE readPipe;
};
HANDLE hThread = NULL;
HANDLE readPipe = NULL;
HANDLE writePipe = NULL;
HANDLE skipImages = NULL;
HANDLE imageFiller = NULL;
HANDLE imageFillerSemaphore = NULL;
HANDLE imageFillerCR = NULL;
HANDLE imageFillerWait = NULL;
BOOL CThumbnailView::FillInImages(CString folder)
{
if (!imageFillerSemaphore)
{
imageFillerSemaphore=CreateSemaphore(NULL, 0,1, NULL);
imageFillerCR=CreateSemaphore(NULL, 1,1, NULL);
imageFillerWait=CreateSemaphore(NULL, 1,1, NULL);
WaitForSingleObject(imageFillerCR, 0);
threadParamImage* pParam=new threadParamImage;
pParam->folder=folder;
pParam->pView=this;
DWORD dummy;
imageFiller=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ImageFillerThread,
pParam, 0, &dummy);
return TRUE;
}
While the image filler thread is:
DWORD ImageFillerThread(DWORD param)
{
threadParamImage* pParam=(threadParamImage*)param;
CString folder=pParam->folder;
CThumbnailView* pView=pParam->pView;
HANDLE readPipe=pParam->readPipe;
delete pParam;
WaitForSingleObject(imageFillerWait, INFINITE);
pView->GetListCtrl().DeleteAllItems();
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);
if (find==INVALID_HANDLE_VALUE)
{
ReleaseSemaphore(imageFillerWait, 1, NULL);
ExitThread(0);
return 0;
}
ReleaseSemaphore(imageFillerCR, 1, NULL);
do
{
if (WaitForSingleObject(imageFillerSemaphore, 0)==WAIT_OBJECT_0)
{
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:
DWORD ImageLoaderThread(DWORD param)
{
CThumbnailView* pView=(CThumbnailView*)param;
DWORD dummy;
char buffer[1024];
while(1)
{
int itemIndex;
int size;
if (WaitForSingleObject(skipImages, 0)==WAIT_OBJECT_0)
{
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);
}
ReadFile(readPipe, &itemIndex, sizeof(int), &dummy, NULL);
ReadFile(readPipe, &size, sizeof(int), &dummy, NULL);
ReadFile(readPipe, buffer, size, &dummy, NULL);
buffer[size]=0;
OFSTRUCT ofs;
if(OpenFile(buffer, &ofs, OF_EXIST)==HFILE_ERROR)continue;
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;
}
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);
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.
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.