Victor Image Processing Library How-to Tips

Print a Proofsheet

A proofsheet contains a collection of small pictures. A photo lab creates a proofsheet with pictures that are the same size as the photographic negatives. This helps the photographer select those photos to make into large prints. This example is a console application that prints a proofsheet on the default printer.

This little utility program is very useful for producing proofsheets from a list of images generated by a digital camera.

Download the zipped executable program and source file: (20 kb). The program prfsheet.exe requires the presence of the Victor Library vic32.dll in the same directory. If you don't already have vic32.dll download a zipped eval copy of the Victor Library dll (119 kb). (Programmers: for all the evaluation modules necessary to create a Victor Library application visit

In this example we create a Win32 console application that allows a 2 x 2-inch area for each picture and reads a list of JPEG image file names from an ASCII text file. The proofsheet is created by running the console application with the name of the text file as the command line argument, for example:

    prfsheet myimages.txt
The image file names are specified in an ASCII text file, such as "myimages.txt" shown here:


How prfsheet.exe works

The steps to print a proofsheet are

This example allows up to 100 pages to be printed. Each image is printed in a 2 x 2-inch area with 1/2-inch spacing between horizontal and vertical rows of images.

Print a Proofsheet - the C/C++ Source Code

Requires Victor Image Processing Library v 5.3 or higher.
// Prfsheet.c -- console app to print a photo proofsheet 
// Usage:  prfsheet myimages.txt
// Reads the text file containing the file names of the image files
// Prints 12 images per page, each image is allowed 2 x 2 inch area

#include <windows.h>
#include <stdio.h>
#include <vicdefs.h>
#include <tchar.h>
typedef TCHAR TSTR256[258]; // Holds one image filename 
// Struct to hold data about printing  
typedef struct {
   HDC hPrn;    // Handle to the printer DC
   int mode;    // Print mode
   RECT prtrc;  // Area to print in mils
   int frame;   // Border width in pixels
   int devCaps; // Device capabilities (status word)
   int ready;   // Printer driver has sufficient capability
   int xRes, yRes; // Printer resolution in dots per inch
   int xSize, ySize; // Printable width, height in mils
   } PrtInfo;
static HDC GetPrinterDC(void);
static void GetPrinterInfo(PrtInfo *prtvals);
static int loadTheImageFile(LPTSTR fname, imgdes *image, int *iwidth, int *ilength);
static int readInImgFilenames(LPTSTR imgFilename, TSTR256 **filelist);
#define NOMOFILES -112 // No more image filenames in filename array
int _cdecl _tmain(int argc, TCHAR *argv[])
   int fileNameMax, fileCtr, rcode=NO_ERROR;
   TSTR256 *filelist = NULL;
   PrtInfo prtvals;
   HWND hWnd = GetActiveWindow();
   TCHAR docName[256];
   int xx, yy, page, picsOnPage=0;
   LPTSTR listFilename; // Filename of image filename list
            // Make sure a filename list is present
   if(argc < 2) {
      printf(_T("Usage: prfsheet <filename list>\n"));
      printf(_T("Press ENTER to continue\n")); getchar();
            // Second arg is the name of the image filename list
   listFilename = argv[1];
            // Read in data file
   fileNameMax = readInImgFilenames(listFilename, &filelist);
   if(fileNameMax == 0) // Exit if there are no image filenames in the list
            // Set up printer: Get hPrn and printer characteristics
   if(prtvals.hPrn == NULL)
            // Since pics will be printed with their longest dimension equal to two inches, 
            // make sure printable width is sufficient to print three per row.
   if((2000 * 3 + 500 * 3) > prtvals.xSize) {
      printf(_T("Printable width insufficient for three pics per row: %d\n"), prtvals.xSize);
      printf(_T("Press ENTER to continue\n")); getchar();
            // Initialize for multi-image printing
   _tcscpy(docName, _T("Victor Proof Sheets"));
   fileCtr = 0; // Count filenames in array that we process
   for(page=0,picsOnPage=0; page<100 && rcode!=NOMOFILES; page++) {
      rcode = printimagestartdoc(prtvals.hPrn, docName);
      if(rcode != NO_ERROR) {
         printf(_T("printimagestartdoc error\n"));
         printf(_T("Press ENTER to exit\n")); getchar();
      printf(_T("Printing page %d\n"), page+1);
      for(yy=500; yy<prtvals.ySize-2000 && rcode!=NOMOFILES; yy+=2500) {
         for(xx=500; xx<prtvals.xSize-2000 && rcode!=NOMOFILES; xx+=2500) {
            if(fileCtr >= fileNameMax)
               rcode = NOMOFILES; // No more filenames in image filename list
            else { // Process next image filename
               LPTSTR fname = (LPTSTR)filelist[fileCtr];
               imgdes image;
               int iwidth, ilength, prtWide, prtLong;
            // Load the image file to print
               rcode = loadTheImageFile(fname, &image, &iwidth, &ilength);
               if(rcode == NO_ERROR) {
            // Calc printing dimensions: Make longest dimension = 2 inches and keep aspect ratio the same as in image
                  prtWide = (iwidth >  ilength) ? 2000 : (iwidth * 2000 / ilength);
                  prtLong = (iwidth <= ilength) ? 2000 : (ilength * 2000 / iwidth);
            // Set area to print on page
                  SetRect(&prtvals.prtrc, xx, yy, xx + prtWide, yy + prtLong);
                  printf(_T("Printing file %d: %s...\n"), fileCtr+1, fname);
            // Print the pic on the page
                  rcode = printimagenoeject(hWnd, prtvals.hPrn, prtvals.mode, &image, &prtvals.prtrc, prtvals.frame, NULL);
                  if(rcode != NO_ERROR) {
                     printf(_T("printimagenoeject() error: %d for %s\n"), rcode, fname);
                     printf(_T("Press ENTER to continue\n")); getchar();
                     picsOnPage++; // Keep track of number of pics on the page
            } // xx
         } // yy
            // Done printing current page, eject it
      printimageenddoc(prtvals.hPrn, TRUE);
      picsOnPage = 0; // Reset pics on the page we've printed
      } // page
   if(picsOnPage > 0) // If there are pics on the page, we need to call prtimgendoc() to eject it
      printimageenddoc(prtvals.hPrn, TRUE);
      DeleteDC(prtvals.hPrn); // Delete printer DC
   if(filelist != NULL)
// Load the image. Prints error message if there is a problem.
// Returns NO_ERROR so we just continue if we can't find an image.
static int loadTheImageFile(LPTSTR fname, imgdes *image, int *iwidth, int *ilength)
   JpegData jdat;      // Reserve space for struct
   int rc, rcode = NO_ERROR;
            // Make sure file exists and get its dimensions
   *iwidth = 0;
   *ilength = 0;
   rc = jpeginfo(fname, &jdat);
   if(rc != NO_ERROR) {
      printf(_T("jpeginfo() error: %d for %s\n"), rcode, fname);
      printf(_T("Press ENTER to continue\n")); getchar();
   else { // Allocate image buffer to the file dimensions
      *iwidth = jdat.width; *ilength = jdat.length;
      rc = allocimage(image, jdat.width, jdat.length, jdat.vbitcount);
      if(rc != NO_ERROR) {
         printf(_T("allocimage() error: %d for %s\n"), rcode, fname);
         printf(_T("Press ENTER to continue\n")); getchar();
      else { // Load the image
         rc = loadjpg(fname, image);
         if(rc != NO_ERROR) {
            printf(_T("loadjpg() error: %d for %s\n"), rcode, fname);
            printf(_T("Press ENTER to continue\n")); getchar();
// Get default printer and create a DC. Returns a handle to the DC or 0 if unsuccessful.
static HDC GetPrinterDC(void)
   TCHAR szPrinter[80];
   LPTSTR szDevice, szDriver, szOutput;
   GetProfileString(_T("windows"), _T("device"), _T(""), szPrinter, sizeof(szPrinter));
   if((szDevice = _tcstok(szPrinter, _T(","))) != 0 &&
      (szDriver = _tcstok(0, _T(","))) != 0 &&
      (szOutput = _tcstok(0, _T(","))) != 0)
      return(CreateDC(szDriver, szDevice, szOutput, 0));
// Get info we need from printer
static void GetPrinterInfo(PrtInfo *prtvals)
   memset(prtvals, 0, sizeof(PrtInfo)); // Zero all members of struct
   prtvals->devCaps = 0;   // Assume no raster capabilities
   prtvals->hPrn = GetPrinterDC();  // Get printer DC
   if(prtvals->hPrn) {
      prtvals->devCaps = GetDeviceCaps(prtvals->hPrn, RASTERCAPS);
            // Set flag to enable/disable print menu item
      prtvals->ready = (prtvals->devCaps & RC_BITBLT);
            // Get info we need
      prtvals->xRes  = GetDeviceCaps(prtvals->hPrn, LOGPIXELSX);
      prtvals->yRes  = GetDeviceCaps(prtvals->hPrn, LOGPIXELSY);
      prtvals->xSize = (int)(GetDeviceCaps(prtvals->hPrn, HORZRES) * 1000L / prtvals->xRes);
      prtvals->ySize = (int)(GetDeviceCaps(prtvals->hPrn, VERTRES) * 1000L / prtvals->yRes);
      prtvals->mode = 0; // Print mode: Default
// Area to print (prtvals->prtrc) will be filled in later
      prtvals->frame = 0; // Border width in pixels
   else {
      printf(_T("Could not get printer DC\n"));
      printf(_T("Press ENTER to continue\n")); getchar();
// Read in image filenames and store them in a buffer 258 TCHARs wide. 
// Returns number of image files in name array.
static int readInImgFilenames(
   LPTSTR imgFilename,
   TSTR256 **filelist)
   DWORD fileSizeLow, fileSizeHigh;
   int rcode = NO_ERROR;
   int nelem = 0;
   BYTE *databuff = NULL;
            // Open existing file for reading that contains image filenames
   if(shandle == INVALID_HANDLE_VALUE)
      rcode = BAD_OPN;
   else {
            // Get size of file
      fileSizeLow = GetFileSize(shandle, &fileSizeHigh);
            // Allocate space for file
      databuff = (BYTE *)calloc(fileSizeLow, sizeof(BYTE));
      if(databuff == NULL) 
         rcode = BAD_MEM;
      else {
         DWORD j, dwBytsRd;
         BYTE *pp;
         unsigned ch;
            // Read in the file
         ReadFile(shandle, (LPSTR)databuff, (unsigned)fileSizeLow, &dwBytsRd, NULL);
            // First time thru just count filename entries (CR marks the end of a line, ';' is comment)
         pp = databuff;
         nelem = 0; // Image filename counter
         j = 0; 
         while(j < fileSizeLow) {
            if( __iscsym(*pp) ) // Check first char in filename is letter, digit, or underscore
               nelem++; // Not in a comment string, count entry
            // Find LF
            do {
               ch = *pp++;
               } while(ch != '\n');
            } // while
            // Allocate space for image filename array
         *filelist = (TSTR256 *)calloc(nelem, sizeof(TSTR256));
         if(*filelist == NULL)
            rcode = BAD_MEM;
            // Second time thru copy filenames into filelist[]
         else {
            BYTE *dp;
            TSTR256 *fn;
            pp = databuff;
            fn = *filelist; // Point fn and dp at first filename buffer
            dp = (TCHAR *)fn;
            j = 0;
            while(j < fileSizeLow) {
               BOOL isFilename = (__iscsym(*pp) == TRUE); // Check that first char in string is char, digit or underscore
            // Find LF
               do {
                  ch = *pp++;
                  if(isFilename == TRUE && isprint(ch))
                     *dp++ = ch;
                  } while(ch != '\n');
               if(isFilename == TRUE) {
                  fn++; // Point fn and dp at next filename buffer
                  dp = (TCHAR *)fn;
               } // while
            } // else
   if(shandle != INVALID_HANDLE_VALUE)
            // Handle any errors
   if(rcode != NO_ERROR) {
      if(rcode == BAD_OPN)
         printf(_T("Could not open %s\n"), imgFilename);
      else if(rcode == BAD_MEM)
         printf(_T("Insufficient memory\n"));
      printf(_T("Press ENTER to continue\n")); getchar();
Victor Image Processing Library

Victor Image Processing Library homepage | Victor Product Summary | more source code

Copyright © 2002 Catenary Systems Inc. All rights reserved. Victor Image Processing Library is a trademark of Catenary Systems.