List Control displaying image thumbnails






3.43/5 (37 votes)
Jan 5, 2000

397191
Demonstrates using a list control to display thumbnail views of images
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.
// 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 }; // structure used to pass parameters to a JPEG image 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.
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.