/*****************************************************************************************/
/*
 * File name: VioFeaturesDemo.cpp
 * Location:  ...\Matrox Imaging\MILxxx\Examples\BoardSpecific\vio\C++\viofeaturesdemo
 *             
 *
 * Synopsis:  This example demonstrates how to:
 *             - set the Matrox Vio in minimum-latency pass-through mode;
 *             - set the color overlay in the Matrox auxiliary display;
 *             - detect when the video input source is plugged and unplugged by
 *               hooking on the camera present event;
 *             - display a splash screen when the video input source is not
 *               present or unplugged;
 *             - grab in host memory using MdigProcess;
 *             - display live video on a windowed display.
 *
 *            If you have the compression/decompression runtime license
 *            package installed:
 *             - compress the grab buffers in JPEG format and save them to disk;
 *
 *            This example is based on MDigProcess.cpp
 *
 *            It is recommended to have at least 256 MB of MIL non-paged memory.
 *            See MilConfig utility in the "Non-paged memory" page.
 */

// Headers.
#include <mil.h>
#include <queue>

#if M_MIL_USE_WINDOWS
#include <windows.h>
#define MosRefresh()
#else
#include <stdlib.h>
#include <ncurses.h>
#include <pthread.h>
#include <X11/Xlib.h>

// use printw curses function
#define MosPrintf  printw
#define MosRefresh refresh

#endif

// Number of images in the buffering grab queue.
#define BUFFERING_SIZE_MAX 24

#define PROC_BUFFERING_SIZE_MAX 50

// Splash Screen image file.
#define SPLASH_SCREEN_IMAGE_FILE  M_IMAGE_PATH MIL_TEXT("imaginglogo.mim")

// JPEG AVI file name.
#define SEQUENCE_FILE MIL_TEXT("MilVIOSequence.avi")

// Saved image file name.
#define IMAGE_FILE MIL_TEXT("MilVIOImage")


// Quantization factor to use during the compression.
// Valid values are 1 to 99 (higher to lower quality).
#define COMPRESSION_Q_FACTOR   50

// Scale factor for the thumbnail.
#define SCALE_FACTOR             3
#define OFFSET_X                 10
#define OFFSET_Y                 10
#define EVERY_THUMNAIL_IMAGES    10

// User's processing function hook data structure.
typedef struct
   {
   MIL_ID  MilSystem;
   MIL_ID  MilDigitizer;
   MIL_ID  MilDisplay;
   MIL_ID  MilImageDispSplashScreen;
   MIL_ID  MilWindowedDisplay;
   MIL_ID  MilImageWindowedDisp;
   MIL_ID  MilImageDispOvr;

   MIL_INT CameraPresent;
   MIL_INT SaveSequenceToDisk;
   MIL_INT SaveAnImageToDisk;
   MIL_INT ProcessedImageCount;

   double  DisplayUpdateTime;

   MIL_INT Exit;
   } HookDataStruct;


typedef struct
   {
   // Host YUV16 buffer used for compression.
   MIL_ID  MilBufHostYUV16;
   double  MilBufHostYUV16SizeInMB;

   // Host BGR32 image used for the thumbnail display.
   MIL_ID  MilBufHostScaled;
   double  MilBufHostScaledSizeInMB;
   bool    MilBufHostScaledInUse;
   MIL_INT MilBufHostScaledSizeX;
   MIL_INT MilBufHostScaledSizeY;

   // Compression buffer.
   MIL_ID MilCompressedImage;

   } BUFFERINGDATASTRUCT;

// User's processing function prototype.
MIL_INT MFTYPE ProcessingFunction(MIL_INT HookType, MIL_ID HookId, void* HookDataPtr);
MIL_UINT32 MFTYPE ProcessingThread(void *TPar);
MIL_INT MFTYPE CameraPresentHook(MIL_INT HookType, MIL_ID EventId, void* UserStructPtr);
void OverlayDraw(MIL_ID MilDisplay);

std::queue<BUFFERINGDATASTRUCT> FifoGrabInUse;
std::queue<BUFFERINGDATASTRUCT> FifoGrabFree;
#if M_MIL_USE_WINDOWS
CRITICAL_SECTION FifoLock;
#else
pthread_mutex_t FifoLock;
#endif

// Main function.
int MosMain(void)
   {
   HookDataStruct UserHookData;
   MIL_ID  MilApplication = M_NULL;
   MIL_ID  MilSystem = M_NULL;
   MIL_ID  MilDigitizer = M_NULL;
   MIL_ID  MilDisplay = M_NULL;
   MIL_ID  MilImageDispSplashScreen = M_NULL;
   MIL_ID  MilImageDispSplashScreenChild = M_NULL;
   MIL_ID  MilWindowedDisplay = M_NULL;
   MIL_ID  MilImageWindowedDisp = M_NULL;
   MIL_ID  MilImageDispOvr = M_NULL;
   MIL_ID  MilGrabBufferList[BUFFERING_SIZE_MAX] = { 0 };
   MIL_ID  ProcessingThreadId = M_NULL;
   BUFFERINGDATASTRUCT BufferingDataStruct[PROC_BUFFERING_SIZE_MAX];
   MIL_INT SaveSequenceToDisk = false, JPEGLicensePresent = false, LicenseModules;
   double  ProcessFrameRate   = 0;
   MIL_INT MilGrabBufferListSize = 0;
   MIL_INT MilProcListSize = 0;
   MIL_INT ProcessFrameMissed = 0, ProcessFrameCount  = 0, NbFrames = 0;
   MIL_INT i = 0, SizeBand = 0, SizeX = 0, SizeY = 0, SPSizeX = 0, SPSizeY = 0;
   MIL_TEXT_CHAR DisplayFormat[512] = {0};
   MIL_TEXT_CHAR DigitizerFormat[512] = {0};
   MIL_TEXT_CHAR SplashScreenImageFile[512] = {0};
   MIL_INT c = 0;

#if M_MIL_USE_WINDOWS
   InitializeCriticalSection(&FifoLock);
#else
   initscr();
   move(0,0);
   pthread_mutex_init(&FifoLock, NULL);
#endif

   MappAlloc(M_NULL, M_DEFAULT, &MilApplication);
   MsysAlloc(M_DEFAULT, M_SYSTEM_VIO , M_DEV0, M_DEFAULT, &MilSystem);

   // Allocate digitizer.
   MdigAlloc(MilSystem, M_DEFAULT, MIL_TEXT("M_DEFAULT"), M_DEFAULT, &MilDigitizer);

   SizeBand = MdigInquire(MilDigitizer, M_SIZE_BAND, M_NULL);
   SizeX    = MdigInquire(MilDigitizer, M_SIZE_X, M_NULL);
   SizeY    = MdigInquire(MilDigitizer, M_SIZE_Y, M_NULL);


   // Allocate VIO display output.
   MdispAlloc(MilSystem, M_DEFAULT, MIL_TEXT("M_DEFAULT"), M_AUXILIARY, &MilDisplay);

   // Allocate windowed display in no tearing mode.
   MdispAlloc(MilSystem, M_DEFAULT, MIL_TEXT("M_DEFAULT"), M_WINDOWED, &MilWindowedDisplay);

   // We try to enable no-tearing feature by default, but it is not 
   // supported by all displays adapters.
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
   MdispControl(MilWindowedDisplay, M_NO_TEARING, M_ENABLE);
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);

   // Allocate display buffer for Vio splash screen and select it.
   MbufAllocColor(MilSystem,
                  MdispInquire(MilDisplay, M_SIZE_BAND, M_NULL),
                  MdispInquire(MilDisplay, M_SIZE_X, M_NULL),
                  MdispInquire(MilDisplay, M_SIZE_Y, M_NULL),
                  8+M_UNSIGNED,
                  M_IMAGE + M_DISP + M_PROC,
                  &MilImageDispSplashScreen);
   MbufClear(MilImageDispSplashScreen, 255);

   // Allocate display buffer for the display screen and select it.
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
   MbufAllocColor(MilSystem, SizeBand, SizeX, SizeY, 8L+M_UNSIGNED,
                  M_IMAGE + M_DISP + (SizeBand == 3? M_YUV16 + M_PACKED:0)
                     + M_NON_PAGED + M_VIDEO_MEMORY,
                  &MilImageWindowedDisp);
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);
   // If it is not possible to allocate directly in the video memory,
   // allocate in the host memory.

   if(MilImageWindowedDisp == M_NULL)
      MbufAllocColor(MilSystem, SizeBand, SizeX, SizeY, 8L+M_UNSIGNED,
                     M_IMAGE + M_DISP + (SizeBand == 3? M_YUV16 + M_PACKED:0) + M_NON_PAGED,
                    &MilImageWindowedDisp);

   MbufClear(MilImageWindowedDisp, M_COLOR_BLACK);

   // Load splash screen.
   MosStrcpy(SplashScreenImageFile, 512, SPLASH_SCREEN_IMAGE_FILE);
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
   if(MbufDiskInquire(SplashScreenImageFile, M_FILE_FORMAT, M_NULL) == M_INVALID)
      {
      MosStrcpy(SplashScreenImageFile, 512, MIL_TEXT("..\\"));
      MosStrcat(SplashScreenImageFile, 512, SPLASH_SCREEN_IMAGE_FILE);
      }
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);

   MbufDiskInquire(SplashScreenImageFile, M_SIZE_X, &SPSizeX);
   MbufDiskInquire(SplashScreenImageFile, M_SIZE_Y, &SPSizeY);

   MbufChild2d(MilImageDispSplashScreen,
                  SizeX/2 - SPSizeX/2,
                  SizeY/2 - SPSizeY/2,
                  SPSizeX, SPSizeY,
                  &MilImageDispSplashScreenChild);

   MbufLoad(SplashScreenImageFile, MilImageDispSplashScreenChild);
   MbufFree(MilImageDispSplashScreenChild);

   // Select the splash screen on auxiliary display
   MdispSelect(MilDisplay, MilImageDispSplashScreen);

   UserHookData.MilSystem     = MilSystem;
   UserHookData.MilDigitizer  = MilDigitizer;
   UserHookData.MilDisplay    = MilDisplay;

   MdispInquire(MilDisplay, M_FORMAT, (MIL_TEXT_PTR)&DisplayFormat);
   MdigInquire(MilDigitizer, M_FORMAT, (MIL_TEXT_PTR)&DigitizerFormat);

   MosPrintf(MIL_TEXT("This example demonstrates the features of the Matrox Vio.\n\n"));
   MosPrintf(MIL_TEXT("The currently selected DCF is:\n%s\n\n"), DigitizerFormat);
   MosPrintf(MIL_TEXT("The currently selected VCF is: \n%s\n\n"), DisplayFormat);

   MappInquire(M_DEFAULT, M_LICENSE_MODULES, &LicenseModules);
   if(LicenseModules & M_LICENSE_JPEGSTD)
      {
      MosPrintf(MIL_TEXT("\nDo you want to compress and archive the input video? (y/n)\n\n"));
      MosRefresh();
      if(MosGetch() == 'y')
         {
         MosPrintf(MIL_TEXT("AVI file: %s\n\n"), SEQUENCE_FILE);
         SaveSequenceToDisk = true;
         MbufExportSequence(SEQUENCE_FILE, M_DEFAULT, M_NULL, M_NULL, M_DEFAULT, M_OPEN);
         }
      }

   MosRefresh();
   // Allocated host buffer for compression and to display the thumbnail.
   for(MilProcListSize = 0; MilProcListSize < PROC_BUFFERING_SIZE_MAX; MilProcListSize++)
      {
      MbufAllocColor(MilSystem, SizeBand, SizeX, SizeY,
                     8+M_UNSIGNED,
                     M_IMAGE + M_NON_PAGED + (SizeBand == 3? M_YUV16 + M_PACKED:0),
                     &BufferingDataStruct[MilProcListSize].MilBufHostYUV16);

      BufferingDataStruct[MilProcListSize].MilBufHostYUV16SizeInMB =
         (SizeX * SizeY * (SizeBand == 3? 2:1)) / (1024.0*1024.0);
      BufferingDataStruct[MilProcListSize].MilBufHostScaledInUse = false;

      BufferingDataStruct[MilProcListSize].MilBufHostScaledSizeInMB =
         (((SizeX * SizeY * (SizeBand == 3? 4:1)) / (1024.0*1024.0)))/
         (SCALE_FACTOR*SCALE_FACTOR);
      BufferingDataStruct[MilProcListSize].MilBufHostScaledSizeX =
         (MIL_INT) (SizeX / SCALE_FACTOR);
      BufferingDataStruct[MilProcListSize].MilBufHostScaledSizeY =
         (MIL_INT) (SizeY / SCALE_FACTOR);

      MbufAllocColor(MilSystem, SizeBand,
                     BufferingDataStruct[MilProcListSize].MilBufHostScaledSizeX,
                     BufferingDataStruct[MilProcListSize].MilBufHostScaledSizeY,
                     8+M_UNSIGNED,
                     M_IMAGE + M_NON_PAGED + (SizeBand == 3 ? M_BGR32 + M_PACKED:0),
                     &BufferingDataStruct[MilProcListSize].MilBufHostScaled);

      if(SaveSequenceToDisk)
         {
         MbufAllocColor(MilSystem, SizeBand, SizeX, SizeY,
                        8+M_UNSIGNED,
                        M_IMAGE + M_COMPRESS + M_JPEG_LOSSY +
                           (SizeBand == 3 ? M_YUV16+M_PACKED:0),
                        &BufferingDataStruct[MilProcListSize].MilCompressedImage);

         MbufControl(BufferingDataStruct[MilProcListSize].MilCompressedImage, M_Q_FACTOR,
                     COMPRESSION_Q_FACTOR);
         }
      else
         BufferingDataStruct[MilProcListSize].MilCompressedImage = M_NULL;

      FifoGrabFree.push(BufferingDataStruct[MilProcListSize]);
      }

   if(MappGetError(M_DEFAULT, M_CURRENT,M_NULL) == 0)
      {
      // Allocate the grab buffers and clear them.
      MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
      for(MilGrabBufferListSize = 0;
          MilGrabBufferListSize < BUFFERING_SIZE_MAX;
          MilGrabBufferListSize++)
         {
         MbufAllocColor(MilSystem, SizeBand, SizeX, SizeY,
                        8+M_UNSIGNED,
                        M_IMAGE + M_GRAB + M_ON_BOARD + (SizeBand == 3 ? M_YUV16 + M_PACKED:0),
                        &MilGrabBufferList[MilGrabBufferListSize]);

         if(MilGrabBufferList[MilGrabBufferListSize])
            MbufClear(MilGrabBufferList[MilGrabBufferListSize], 0xFF);
         else
            break;
         }

      MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);

      // Free a buffer to leave space for possible temporary buffer.
      MilGrabBufferListSize--;
      MbufFree(MilGrabBufferList[MilGrabBufferListSize]);

      // Enable display overlay annotations on auxiliary display.
      MdispControl(MilDisplay, M_OVERLAY, M_ENABLE);

      // Inquire the Vio overlay buffer associated with the display.
      MdispInquire(MilDisplay, M_OVERLAY_ID, &MilImageDispOvr);

      // Clear the overlay to transparent.
      MdispControl(MilDisplay, M_OVERLAY_CLEAR, M_DEFAULT);

      // Enable overlay annotations.
      MdispControl(MilDisplay, M_OVERLAY_SHOW, M_ENABLE);

      // Set graphic text to transparent background.
      MgraControl(M_DEFAULT, M_BACKGROUND_MODE, M_TRANSPARENT);

      // Set drawing color to red.
      MgraColor(M_DEFAULT, M_COLOR_WHITE);

      // This control enables direct acquisition to display.
      MdispControl(MilDisplay, M_SELECT_VIDEO_SOURCE, MilDigitizer);

      // Change display window title.
      MdispControl(MilWindowedDisplay, M_TITLE, M_PTR_TO_DOUBLE(MIL_TEXT("Display Window")));

      MdispSelect(MilWindowedDisplay, MilImageWindowedDisp);

      // Initialize the User's processing function data structure.
      UserHookData.ProcessedImageCount       = 0;
      UserHookData.MilSystem                 = MilSystem;
      UserHookData.MilDigitizer              = MilDigitizer;
      UserHookData.MilDisplay                = MilDisplay;
      UserHookData.MilImageDispSplashScreen  = MilImageDispSplashScreen;
      UserHookData.MilWindowedDisplay        = MilWindowedDisplay;
      UserHookData.MilImageWindowedDisp      = MilImageWindowedDisp;
      UserHookData.MilImageDispOvr           = MilImageDispOvr;
      UserHookData.CameraPresent             = true;
      UserHookData.SaveSequenceToDisk        = SaveSequenceToDisk;
      UserHookData.SaveAnImageToDisk         = false;
      UserHookData.Exit                      = false;

      // Add hook on camera present. It is used to display the splash screen when the
      // input source is unplugged.
      MdigHookFunction(MilDigitizer, M_CAMERA_PRESENT, CameraPresentHook,
                       (void *)(&UserHookData));

      // Draw Overlay.
      OverlayDraw(MilImageDispOvr);

      // Print a message.
      MosPrintf(MIL_TEXT("The Matrox Vio is now:\n"));
      MosPrintf(MIL_TEXT("   - grabbing in a windowed display\n"));
      MosPrintf(MIL_TEXT("   - displaying the input video on the auxiliary display in\n"));
      MosPrintf(MIL_TEXT("     minimum-latency pass-through mode with overlay\n"));
      if(SaveSequenceToDisk)
         MosPrintf(MIL_TEXT("   - compressing and archiving the grabbed video\n"));

      MosPrintf(MIL_TEXT("\nPress 's' to save the current image to disk.\n"));
      MosPrintf(MIL_TEXT("Press 'q' to stop grabbing.\n\n"));
      MosRefresh();


      // Allocate compress and disk buffering thread.
      MthrAlloc(M_DEFAULT, M_THREAD, M_DEFAULT, &ProcessingThread, &UserHookData,
                &ProcessingThreadId);

      MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
      // Start the processing. The processing function is called for every frame grabbed.
      MdigProcess(MilDigitizer, MilGrabBufferList, MilGrabBufferListSize,
                                M_START, M_DEFAULT, ProcessingFunction, &UserHookData);
      while(!MosKbhit())
         {
         c = MosGetch();

         if(c == 's')
            UserHookData.SaveAnImageToDisk = true;

         // exit.
         if(c == 'q')
            break;

         }

   #if M_MIL_USE_LINUX
      clear();  
      move(0,0);
      MosRefresh();
   #endif

      // Stop the processing thread.
      UserHookData.Exit = true;
      MthrWait(ProcessingThreadId, M_THREAD_END_WAIT, M_NULL);
      MthrFree(ProcessingThreadId);

      // Stop the processing.
      MdigProcess(MilDigitizer, MilGrabBufferList, MilGrabBufferListSize,
                  M_STOP, M_DEFAULT, ProcessingFunction, &UserHookData);

      MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);
      // Unhook camera present hook.
      MdigHookFunction(MilDigitizer, M_CAMERA_PRESENT + M_UNHOOK, CameraPresentHook,
                       (void *)(&UserHookData));

      // Print statistics.
      MdigInquire(MilDigitizer, M_PROCESS_FRAME_COUNT,  &ProcessFrameCount);
      MdigInquire(MilDigitizer, M_PROCESS_FRAME_RATE,   &ProcessFrameRate);
      MdigInquire(MilDigitizer, M_PROCESS_FRAME_MISSED, &ProcessFrameMissed);
      MosPrintf(MIL_TEXT("\n\n%ld frames grabbed at %.1f frames/sec (%.1f ms/frame).\n")
                MIL_TEXT("Frame(s) missed:%d .\n"),
                ProcessFrameCount, ProcessFrameRate, 1000.0/ProcessFrameRate, ProcessFrameMissed);

      // Close the windowed display.
      MdispSelect(MilWindowedDisplay, M_NULL);

      // Select auxiliary display option on exit.
      MosPrintf(MIL_TEXT("\nSelect auxiliary display option on exit:\n"));
      MosPrintf(MIL_TEXT("1: Splash screen visible (default)\n"));
      MosPrintf(MIL_TEXT("2: Live video with overlay\n"));
      MosPrintf(MIL_TEXT("3: Live video without overlay\n"));
      MosPrintf(MIL_TEXT("4: Auxiliary display disabled\n"));
      MosRefresh();
      c = MosGetch();
      switch(c)
         {
         case '1': // Splash-Screen.
         default:
         MdispControl(MilDisplay, M_OVERLAY, M_DISABLE);
         MdispControl(MilDisplay, M_OVERLAY_SHOW, M_DISABLE);
         MdispControl(MilDisplay, M_SELECT_VIDEO_SOURCE, M_NULL);
         MdispControl(MilDisplay, M_AUXILIARY_KEEP_DISPLAY_ALIVE, M_ENABLE);
         break;

         case '2': // Live video (with overlay).
         MdispControl(MilDisplay, M_OVERLAY, M_ENABLE);
         MdispControl(MilDisplay, M_OVERLAY_SHOW, M_ENABLE);
         MdispControl(MilDisplay, M_SELECT_VIDEO_SOURCE, MilDigitizer);
         MdispControl(MilDisplay, M_AUXILIARY_KEEP_DISPLAY_ALIVE, M_ENABLE);
         break;

         case '3': // Live video (without overlay).
         MdispControl(MilDisplay, M_OVERLAY, M_DISABLE);
         MdispControl(MilDisplay, M_OVERLAY_SHOW, M_DISABLE);
         MdispControl(MilDisplay, M_SELECT_VIDEO_SOURCE, MilDigitizer);
         MdispControl(MilDisplay, M_AUXILIARY_KEEP_DISPLAY_ALIVE, M_ENABLE);
         break;

         case '4': // Auxiliary display disabled.
         MdispControl(MilDisplay, M_SELECT_VIDEO_SOURCE, MilDigitizer);
         MdispControl(MilDisplay, M_AUXILIARY_KEEP_DISPLAY_ALIVE, M_DISABLE);
         break;
         }
   }

   MthrWait(M_DEFAULT, M_THREAD_WAIT, M_NULL);

      // Sequence file closing if required.
   if(SaveSequenceToDisk)
       MbufExportSequence(SEQUENCE_FILE, M_DEFAULT, M_NULL, M_NULL, ProcessFrameRate, M_CLOSE);

   // Free the grab buffers.
   while(MilGrabBufferListSize > 0)
      {
      MbufFree(MilGrabBufferList[--MilGrabBufferListSize]);
      };

   // Free the compress and host buffers.
   while(MilProcListSize>0)
      {
      MilProcListSize--;
      MbufFree(BufferingDataStruct[MilProcListSize].MilBufHostYUV16);
      MbufFree(BufferingDataStruct[MilProcListSize].MilBufHostScaled);
      MbufFree(BufferingDataStruct[MilProcListSize].MilCompressedImage);
      };

   if(MilWindowedDisplay)
      MdispFree(MilWindowedDisplay);

#if M_MIL_USE_WINDOWS
   DeleteCriticalSection(&FifoLock);
#else
   pthread_mutex_destroy(&FifoLock);
   endwin();     
#endif

   MdispFree(MilDisplay);
   MdigFree(MilDigitizer);
   MbufFree(MilImageDispSplashScreen);
   MbufFree(MilImageWindowedDisp);
   MsysFree(MilSystem);
   MappFree(MilApplication);

   return 0;
   }


// User's processing function called every time a grab buffer is modified.
// -----------------------------------------------------------------------
MIL_INT MFTYPE ProcessingFunction(MIL_INT HookType, MIL_ID HookId, void* HookDataPtr)
   {
   HookDataStruct *UserHookDataPtr = (HookDataStruct *)HookDataPtr;
   BUFFERINGDATASTRUCT  BufferingDataStruct;
   MIL_ID               ModifiedBufferId;
   double               TimeS, TimeE;
   MIL_INT              CameraPresent = false;

   if(UserHookDataPtr->Exit)
      return 0;

   // Inquire if the camera is present.
   MdigInquire(UserHookDataPtr->MilDigitizer, M_CAMERA_PRESENT, &CameraPresent);

   // If the state of camera present does not match the state of the CameraPresentHook
   // callback function, call it to enable/disable the splash screen.
   if((CameraPresent !=  UserHookDataPtr->CameraPresent))
      {
      MosSleep(300); // Wait a little for the camera present circuitry to stabilize.
      CameraPresentHook(M_CAMERA_PRESENT, 0, HookDataPtr);
      }

   // Retrieve the MIL_ID of the grabbed buffer.
   MdigGetHookInfo(HookId, M_MODIFIED_BUFFER+M_BUFFER_ID, &ModifiedBufferId);

   while(FifoGrabFree.empty() && !UserHookDataPtr->Exit)
      MosSleep(1);

   if(UserHookDataPtr->Exit)
      return 0;

   // If camera is not present, skip.
   if(!UserHookDataPtr->CameraPresent || !CameraPresent)
      return 0;

   // Retrieve a free host buffer to copy the on-board buffer into it.
#if M_MIL_USE_WINDOWS
   EnterCriticalSection(&FifoLock);
#else
   pthread_mutex_lock(&FifoLock);
#endif
   BufferingDataStruct = FifoGrabFree.front();
   FifoGrabFree.pop();
#if M_MIL_USE_WINDOWS
   LeaveCriticalSection(&FifoLock);
#else
   pthread_mutex_unlock(&FifoLock);
#endif

   // Copy the grab image to the host buffer.
   MbufCopy(ModifiedBufferId, BufferingDataStruct.MilBufHostYUV16);

   // Copy to display of the VGA display.
   MappTimer(M_DEFAULT, M_TIMER_READ, &TimeS);
   MbufCopy(ModifiedBufferId, UserHookDataPtr->MilImageWindowedDisp);
   MappTimer(M_DEFAULT, M_TIMER_READ, &TimeE);
   UserHookDataPtr->DisplayUpdateTime = TimeE - TimeS;

   // Copy the thumbnail scaled image to the host.
   if((UserHookDataPtr->ProcessedImageCount % EVERY_THUMNAIL_IMAGES) == 0)
      {
      BufferingDataStruct.MilBufHostScaledInUse = true;
      MbufTransfer(ModifiedBufferId, BufferingDataStruct.MilBufHostScaled, 0,
                   M_DEFAULT, M_DEFAULT, M_DEFAULT, M_DEFAULT,
                   M_DEFAULT, M_DEFAULT,
                   BufferingDataStruct.MilBufHostScaledSizeX,
                   BufferingDataStruct.MilBufHostScaledSizeY,
                   M_DEFAULT, M_COPY + M_SCALE, M_DRIVER_MODE, M_DEFAULT, M_NULL);
      }
   else
      {
      BufferingDataStruct.MilBufHostScaledInUse = false;
      }

#if M_MIL_USE_WINDOWS
   EnterCriticalSection(&FifoLock);
#else
   pthread_mutex_lock(&FifoLock);
#endif
   FifoGrabInUse.push(BufferingDataStruct);
#if M_MIL_USE_WINDOWS
   LeaveCriticalSection(&FifoLock);
#else
   pthread_mutex_unlock(&FifoLock);
#endif

   return 0;
   }


// User's processing thread called every time a host buffer is modified.
// -----------------------------------------------------------------------
MIL_UINT32 MFTYPE ProcessingThread(void *HookDataPtr)
   {
   HookDataStruct *UserHookDataPtr = (HookDataStruct *)HookDataPtr;
   BUFFERINGDATASTRUCT BufferingDataStruct;
   double TimeS, TimeE;
   double CompressionTime = 0.0;
   static double TumbnailUpdateTime = 0.0;
   double DisplayUpdateTime = 0.0;
   MIL_INT  SavedImageIndex = 0;

   while(!UserHookDataPtr->Exit)
      {
      while(FifoGrabInUse.empty() && !UserHookDataPtr->Exit)
        MosSleep(1);

      if(UserHookDataPtr->Exit)
         break;

      UserHookDataPtr->ProcessedImageCount++;

      // Retrieve a previously grabbed buffer to process it.
#if M_MIL_USE_WINDOWS
      EnterCriticalSection(&FifoLock);
#else
      pthread_mutex_lock(&FifoLock);
#endif
      BufferingDataStruct = FifoGrabInUse.front();
      FifoGrabInUse.pop();
#if M_MIL_USE_WINDOWS
      LeaveCriticalSection(&FifoLock);
#else
      pthread_mutex_unlock(&FifoLock);
#endif

      // Compress and save compressed image.
      if(UserHookDataPtr->SaveSequenceToDisk)
         {
         MappTimer(M_DEFAULT, M_TIMER_READ, &TimeS);
         MbufCopy(BufferingDataStruct.MilBufHostYUV16, BufferingDataStruct.MilCompressedImage);
         MbufExportSequence(SEQUENCE_FILE, M_DEFAULT,
                              &BufferingDataStruct.MilCompressedImage,
                              1, M_DEFAULT,
                              M_WRITE);
         MappTimer(M_DEFAULT, M_TIMER_READ, &TimeE);
         CompressionTime = (TimeE - TimeS);
         }

      // Save the host image to disk when 's' is pressed.
      if(UserHookDataPtr->SaveAnImageToDisk)
         {
         MIL_TEXT_CHAR FileName[512];
#if M_MIL_USE_WINDOWS
         COORD Coord;
         Coord.X = 0; Coord.Y = 24;
         SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Coord);
#endif
         MosSprintf(FileName, 512, MIL_TEXT("%s%d.mim"), IMAGE_FILE, SavedImageIndex++);
         MbufSave(FileName, BufferingDataStruct.MilBufHostYUV16);
         MosPrintf(MIL_TEXT("* Saved image in file: %s"), FileName);
         UserHookDataPtr->SaveAnImageToDisk = false;
         MosRefresh();
         }

      // Update the thumbnail on the overlay buffer.
      if((BufferingDataStruct.MilBufHostScaledInUse) && (UserHookDataPtr->CameraPresent))
         {
         MappTimer(M_DEFAULT, M_TIMER_READ, &TimeS);
         MbufCopyColor2d(BufferingDataStruct.MilBufHostScaled,
                           UserHookDataPtr->MilImageDispOvr,
                           M_ALL_BANDS,OFFSET_X,OFFSET_Y,
                           M_ALL_BANDS,0,0,
                           BufferingDataStruct.MilBufHostScaledSizeX - (OFFSET_X*2),
                           BufferingDataStruct.MilBufHostScaledSizeY -(OFFSET_Y*2));
         MappTimer(M_DEFAULT, M_TIMER_READ, &TimeE);
         TumbnailUpdateTime = (TimeE - TimeS);
         }

      // Update the statistics every 10 frames.
      if(UserHookDataPtr->ProcessedImageCount % 10 == 0)
         {
#if M_MIL_USE_WINDOWS
         COORD Coord;
         Coord.X = 0; Coord.Y = 26;
         SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Coord);
#else
         move(11,0);
#endif

         MosPrintf(MIL_TEXT("Frames grabbed:\t%d \n"), UserHookDataPtr->ProcessedImageCount);
         MosPrintf(MIL_TEXT("Frames missed:\t%d \n\n"),
                   MdigInquire(UserHookDataPtr->MilDigitizer, M_PROCESS_FRAME_MISSED, M_NULL));

         MosPrintf(MIL_TEXT("VGA display update time:\t%3.1f ms   \n"),
                   UserHookDataPtr->DisplayUpdateTime * 1000.0);
         MosPrintf(MIL_TEXT("VGA display update rate:\t%3.1f MB/s   \n\n"),
                   BufferingDataStruct.MilBufHostYUV16SizeInMB /
                   UserHookDataPtr->DisplayUpdateTime);

         if(UserHookDataPtr->SaveSequenceToDisk)
            {
            MosPrintf(MIL_TEXT("Compression time:\t%3.1f ms   \n"),
                      CompressionTime * 1000.0);
            MosPrintf(MIL_TEXT("Compression rate:\t%3.1f MB/s   \n\n"),
                      BufferingDataStruct.MilBufHostYUV16SizeInMB / CompressionTime);
            }

         MosPrintf(MIL_TEXT("Thumbnail update time:\t%3.1f ms \n"),
                   TumbnailUpdateTime * 1000.0);
         MosPrintf(MIL_TEXT("Thumbnail update rate:\t%3.1f MB/s   \n\n"),
                   BufferingDataStruct.MilBufHostScaledSizeInMB / TumbnailUpdateTime);

         MosRefresh();
         }

      // Put back the previously grabbed buffer into the Free buffer list.
#if M_MIL_USE_WINDOWS
      EnterCriticalSection(&FifoLock);
#else
      pthread_mutex_lock(&FifoLock);
#endif
      FifoGrabFree.push(BufferingDataStruct);
#if M_MIL_USE_WINDOWS
      LeaveCriticalSection(&FifoLock);
#else
      pthread_mutex_unlock(&FifoLock);
#endif
      }

   return 0;
   }


// CameraPresent hook function. Called every time the camera is plugged and unplugged
// -------------------------------------------------------------------------------
MIL_INT MFTYPE CameraPresentHook(MIL_INT HookType, MIL_ID EventId, void* UserStructPtr)
   {
   HookDataStruct *UserHookDataPtr = (HookDataStruct *)UserStructPtr;
   MIL_ID  MilDigitizer  = UserHookDataPtr->MilDigitizer;
   MIL_ID  MilDisplay    = UserHookDataPtr->MilDisplay;
   MIL_INT CameraPresent = 0;

   // Inquire if the camera is present or not.
   MdigInquire(MilDigitizer, M_CAMERA_PRESENT, &CameraPresent);

   // No change, return.
   if(CameraPresent == UserHookDataPtr->CameraPresent)
      return 0;

#if M_MIL_USE_WINDOWS
   COORD Coord;
   Coord.X = 0; Coord.Y = 23;
   SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Coord);
#endif

   if(CameraPresent)
      {
      // Camera is present, sync display on video input and enable overlay.
      MosPrintf(MIL_TEXT("* Camera is present, displaying video source.        \n"));
      MdispControl(MilDisplay, M_SELECT_VIDEO_SOURCE, MilDigitizer);
      MdispControl(MilDisplay, M_OVERLAY_SHOW, M_ENABLE);
      UserHookDataPtr->CameraPresent = true;
      }
   else
      {
      // Camera is not present, display splash screen and disable overlay.
      UserHookDataPtr->CameraPresent = false;
      MosPrintf(MIL_TEXT("* Camera is disconnected, displaying splash screen.         \n"));
      MdispControl(MilDisplay, M_SELECT_VIDEO_SOURCE, M_NULL);
      MdispControl(MilDisplay, M_OVERLAY_SHOW, M_DISABLE);
      }

   return 0;
   }

// This function draws annotations in the display overlay.
// -----------------------------------------------------------------------------------------
void OverlayDraw(MIL_ID MilOverlayImage)
   {
   MIL_INT   ImageWidth, ImageHeight;
   MIL_INT32 Count;
   MIL_TEXT_CHAR chText[80];


   // Inquire overlay size.
   ImageWidth  = MbufInquire(MilOverlayImage, M_SIZE_X, M_NULL);
   ImageHeight = MbufInquire(MilOverlayImage, M_SIZE_Y, M_NULL);

   /// Draw MIL overlay annotations.
   //////////////////////////////////
   if(ImageWidth > 768)
      MgraFont(M_DEFAULT, M_FONT_DEFAULT_LARGE);

   // Set the graphic text background to transparent.
   MgraControl(M_DEFAULT, M_BACKGROUND_MODE, M_TRANSPARENT);

   // Print a white string in the overlay image buffer.
   MgraColor(M_DEFAULT, M_COLOR_WHITE);
   MgraText(M_DEFAULT, MilOverlayImage, ImageWidth/9, ImageHeight/5,
            MIL_TEXT(" -------------------- "));
   MgraText(M_DEFAULT, MilOverlayImage, ImageWidth/9, ImageHeight/5+25,
            MIL_TEXT(" - MIL Overlay Text - "));
   MgraText(M_DEFAULT, MilOverlayImage, ImageWidth/9, ImageHeight/5+50,
            MIL_TEXT(" -------------------- "));

   // Print a green string in the overlay image buffer.
   MgraColor(M_DEFAULT, M_COLOR_GREEN);
   MgraText(M_DEFAULT, MilOverlayImage, ImageWidth*11/18, ImageHeight/5,
            MIL_TEXT(" ---------------------"));
   MgraText(M_DEFAULT, MilOverlayImage, ImageWidth*11/18, ImageHeight/5+25,
            MIL_TEXT(" - MIL Overlay Text - "));
   MgraText(M_DEFAULT, MilOverlayImage, ImageWidth*11/18, ImageHeight/5+50,
            MIL_TEXT(" ---------------------"));

   // Draw GDI color overlay annotation.
   ////////////////////////////////////

#if M_MIL_USE_WINDOWS
   // Create a device context to draw in the overlay buffer with GDI.
   MbufControl(MilOverlayImage, M_DC_ALLOC, M_DEFAULT);

   // Inquire the device context.
   HDC hCustomDC = ((HDC)MbufInquire(MilOverlayImage, M_DC_HANDLE, M_NULL));

   // Perform operation if GDI drawing is supported.
   if (hCustomDC)
      {
      POINT Hor[2];
      POINT Ver[2];
      SIZE  TxtSz;
      RECT  Txt;

      // Draw a blue cross.
      HPEN hpen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
      HPEN hpenOld = (HPEN)SelectObject(hCustomDC, hpen);

      Hor[0].x = 0;
      Hor[0].y = (LONG)ImageHeight/2;
      Hor[1].x = (LONG)ImageWidth;
      Hor[1].y = (LONG)ImageHeight/2;
      Polyline(hCustomDC, Hor, 2);

      Ver[0].x = (LONG)ImageWidth/2;
      Ver[0].y = 0;
      Ver[1].x = (LONG)ImageWidth/2;
      Ver[1].y = (LONG)ImageHeight;
      Polyline(hCustomDC, Ver, 2);

      SelectObject(hCustomDC, hpenOld);
      DeleteObject(hpen);

      // Prepare transparent text annotations.
      SetBkMode(hCustomDC, TRANSPARENT);
      MosStrcpy(chText, 80, MIL_TEXT("GDI Overlay Text"));
      Count = (MIL_INT32)MosStrlen(chText);
      GetTextExtentPoint(hCustomDC, chText, Count, &TxtSz);

      // Write red text.
      Txt.left = (LONG)ImageWidth*3/18;
      Txt.top  = (LONG)ImageHeight*17/24;
      Txt.right  = Txt.left + TxtSz.cx;
      Txt.bottom = Txt.top  + TxtSz.cy;
      SetTextColor(hCustomDC,RGB(255, 0, 0));
      DrawText(hCustomDC, chText, Count, &Txt, DT_RIGHT);

      // Write yellow text.
      Txt.left = (LONG)ImageWidth*12/18;
      Txt.top  = (LONG)ImageHeight*17/24;
      Txt.right  = Txt.left + TxtSz.cx;
      Txt.bottom = Txt.top  + TxtSz.cy;
      SetTextColor(hCustomDC, RGB(255, 255, 0));
      DrawText(hCustomDC, chText, Count, &Txt, DT_RIGHT);

      // Delete device context.
      MbufControl(MilOverlayImage, M_DC_FREE, M_DEFAULT);

      // Signal MIL that the overlay buffer was modified.
      MbufControl(MilOverlayImage, M_MODIFIED, M_DEFAULT);
      }
#else
   move(2,0);
   /* Try to create an XPixmap to draw in the overlay buffer with XLib. */
   /* can fail on a 16-bit display */
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_DISABLE);
   MbufControl(MilOverlayImage, M_XPIXMAP_ALLOC, M_COMPENSATION_ENABLE);
   MappControl(M_DEFAULT, M_ERROR, M_PRINT_ENABLE);

   /* Inquire the XPixmap. */
   Pixmap XPixmap = ((Pixmap)MbufInquire(MilOverlayImage, M_XPIXMAP_HANDLE, M_NULL));

   if(XPixmap)
   {
      /* init X */
      Display *dpy = XOpenDisplay("");
      int screen = DefaultScreen(dpy);
      GC gc = XCreateGC(dpy,XPixmap,0,0);
      XColor xcolors[3],exact;
      XPoint Hor[2];
      XPoint Ver[2];
      int i;
      const char *color_names[] = {
         "red",
         "yellow",
         "blue",
      };

      /* allocate colors */
      for(i=0;i<3;i++)
      {
         if(!XAllocNamedColor(dpy,DefaultColormap(dpy,screen),color_names[i],
            &xcolors[i],&exact))
         {
            fprintf(stderr, "cant't alloc color %s\n", color_names[i]);
            exit (1);
         }
      }

      /* Write a blue cross. */
      XSetForeground(dpy,gc, xcolors[2].pixel);
      Hor[0].x = 0;
      Hor[0].y = ImageHeight/2;
      Hor[1].x = ImageWidth;
      Hor[1].y = ImageHeight/2;
      XDrawLines(dpy,XPixmap,gc,Hor, 2, CoordModeOrigin);

      Ver[0].x = ImageWidth/2;
      Ver[0].y = 0;
      Ver[1].x = ImageWidth/2;
      Ver[1].y = ImageHeight;
      XDrawLines(dpy,XPixmap,gc,Ver, 2, CoordModeOrigin);

      /* Write Red text. */
      XSetForeground(dpy,gc, xcolors[0].pixel);
      MosStrcpy(chText, 80, MIL_TEXT("X Overlay Text"));
      Count = MosStrlen(chText);
      XDrawString(dpy,XPixmap,gc,
                  ImageWidth*3/18, ImageHeight*17/24,
                  chText,Count);

      /* Write yellow text. */
      XSetForeground(dpy,gc, xcolors[1].pixel);
      XDrawString(dpy,XPixmap,gc,
                  ImageWidth*12/18, ImageHeight*17/24,
                  chText,Count);

      XSetForeground(dpy,gc, BlackPixel(dpy,screen));
      XFlush(dpy);
      XFreeGC(dpy,gc);
      XCloseDisplay(dpy);

      /* Delete device context. */
      MbufControl(MilOverlayImage, M_XPIXMAP_FREE, M_DEFAULT);
      /* Signal MIL that the overlay buffer was modified. */
      MbufControl(MilOverlayImage, M_MODIFIED, M_DEFAULT);
   }
#endif

   MgraColor(M_DEFAULT, M_COLOR_WHITE);
   MgraFont(M_DEFAULT, M_FONT_DEFAULT_LARGE);
   MgraControl(M_DEFAULT, M_BACKGROUND_MODE, M_DEFAULT);
   }