/**
 * FDT2 CameraCanvas
 * Rob Taft, Bob White
 * Written by Perigee LCC
 * Written for Welch Allyn 
 * Property of Welch Allyn 
 *
 * Version info:
 *
 * 1.05:
 *   Changed tickSpacing to a float.
 *
 * 1.04:
 *   Added method to set retical diameter
 *   Now draws tick marks
 *   Added method to set tick spacing
 *   Added method to set tick length
 *
 * 1.03:
 *   Added version method.
 *
 * 1.02:
 *   Memory Leak fixed.
 *
 * 1.01:
 *   Changed to YV12 format from Y800.
 *
 * 1.00:
 *   Initial camera release.
 */





/* standard includes */
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <memory.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

/* for v4l - Video4Linux */
#include <linux/videodev.h>

/* for shared memory support */
#include <sys/ipc.h>
#include <sys/shm.h>

/* all necessary X includes */
#include <X11/Xlib.h>
#include <X11/extensions/Xv.h>
#include <X11/extensions/XShm.h>
#include <X11/extensions/Xvlib.h>

/*threads*/
#include <pthread.h>
/* Need to include "-lpthread -lsemaphore" in gcc command

/* JNI includes */
#include "fdt2_gui_screens_CameraCanvas.h"
#include "jawt_md.h"
#include "jawt.h"

static const char *device_cap[] = {
   "capture", "tuner", "teletext", "overlay", "chromakey", "clipping",
   "frameram", "scales", "monochrome", NULL
};

/* This is GREYSCALE mode */
#define GUID_Y800_PACKED 0x30303859
#define GUID_YV12_PLANAR 0x32315659
#define GUID_I420_PLANAR 0x30323449

#define INITIAL_WIDTH 160
#define INITIAL_HEIGHT 144

#define VIDEO_DEVICE "/dev/video0"
#define VERSION 1.06f

typedef struct Semaphore
{
    int         v;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
}
Semaphore;


static int         semaphore_down (Semaphore * s);
static int         semaphore_decrement (Semaphore * s);
static int         semaphore_up (Semaphore * s);
static void        semaphore_destroy (Semaphore * s);
static void        semaphore_init (Semaphore * s);
static int         semaphore_value (Semaphore * s);
static int         local_pthread_cond_signal (pthread_cond_t * c);
static int         local_pthread_cond_wait (pthread_cond_t * c, pthread_mutex_t * m);
static int         local_pthread_mutex_unlock (pthread_mutex_t * m);
static int         local_pthread_mutex_lock (pthread_mutex_t * m);
static void        do_error (char *msg);



int fd;

Boolean videoOn = FALSE;
Boolean videoCancelled = FALSE;
Boolean zoomed = FALSE;

int yuv_width = INITIAL_WIDTH;
int yuv_height = INITIAL_HEIGHT;

int xv_port = -1;
int adaptor;
int i, ret, _d, _w, _h;
long secsb, secsa, frames;

XvAdaptorInfo *ai;

XShmSegmentInfo shm;
XvImage *yuv_image = NULL;

unsigned int p_version, p_release,
	p_request_base, p_event_base, p_error_base;
int numAdaptors;

Display *dpy;
Window window, _dw;
//Colormap colormap;
int screen;
XEvent event;
XButtonEvent *eb = (XButtonEvent *) & event;
XKeyEvent *ek = (XKeyEvent *) & event;

struct video_capability capability;
struct video_mbuf mbuf;
struct video_mmap *buf_v4l;
unsigned char *mmapCamera;

int freeze = 0;

int zoom_width = INITIAL_WIDTH;
int zoom_height = INITIAL_HEIGHT;
int zoom_offset_x = 0;
int zoom_offset_y = 0;

int reticalDiameter = INITIAL_WIDTH/2;
float tickSpacing = 6.8f;
int tickLength = 4;

//bool needsRefresh  = false;
bool drawAxis = false;

Semaphore interFrame;

JAWT awt;
JAWT_DrawingSurface* ds;


/************************SEMAPHORE CODE*********************************/
/*
 * function must be called prior to semaphore use.
 *
 */
void
semaphore_init (Semaphore * s)
{
    s->v = 1;
    if (pthread_mutex_init (&(s->mutex), NULL) == -1)
     do_error ("Error setting up semaphore mutex");

    if (pthread_cond_init (&(s->cond), NULL) == -1)
     do_error ("Error setting up semaphore condition signal");
}

/*
 * function should be called when there is no longer a need for
 * the semaphore.
 *
 */
void
semaphore_destroy (Semaphore * s)
{
    if (pthread_mutex_destroy (&(s->mutex)) == -1)
     do_error ("Error destroying semaphore mutex");

    if (pthread_cond_destroy (&(s->cond)) == -1)
     do_error ("Error destroying semaphore condition signal");
}

/*
 * function increments the semaphore and signals any threads that
 * are blocked waiting a change in the semaphore.
 *
 */
int
semaphore_up (Semaphore * s)
{
    int         value_after_op;

    local_pthread_mutex_lock (&(s->mutex));

    (s->v)++;
    value_after_op = s->v;

    local_pthread_mutex_unlock (&(s->mutex));
    local_pthread_cond_signal (&(s->cond));

    return (value_after_op);
}

/*
 * function decrements the semaphore and blocks if the semaphore is
 * <= 0 until another thread signals a change.
 *
 */
int
semaphore_down (Semaphore * s)
{
    int         value_after_op;

    local_pthread_mutex_lock (&(s->mutex));
    while (s->v <= 0)
    {
     local_pthread_cond_wait (&(s->cond), &(s->mutex));
    }

    (s->v)--;
    value_after_op = s->v;

    local_pthread_mutex_unlock (&(s->mutex));

    return (value_after_op);
}

/*
 * function does NOT block but simply decrements the semaphore.
 * should not be used instead of down -- only for programs where
 * multiple threads must up on a semaphore before another thread
 * can go down, i.e., allows programmer to set the semaphore to
 * a negative value prior to using it for synchronization.
 *
 */
int
semaphore_decrement (Semaphore * s)
{
    int         value_after_op;

    local_pthread_mutex_lock (&(s->mutex));
    s->v--;
    value_after_op = s->v;
    local_pthread_mutex_unlock (&(s->mutex));

    return (value_after_op);
}

/*
 * function returns the value of the semaphore at the time the
 * critical section is accessed.  obviously the value is not guarenteed
 * after the function unlocks the critical section.  provided only
 * for casual debugging, a better approach is for the programmar to
 * protect one semaphore with another and then check its value.
 * an alternative is to simply record the value returned by semaphore_up
 * or semaphore_down.
 *
 */
int
semaphore_value (Semaphore * s)
{
    /* not for sync */
    int         value_after_op;

    local_pthread_mutex_lock (&(s->mutex));
    value_after_op = s->v;
    local_pthread_mutex_unlock (&(s->mutex));

    return (value_after_op);
}



/* -------------------------------------------------------------------- */
/* The following functions replace standard library functions in that   */
/* they exit on any error returned from the system calls.  Saves us     */
/* from having to check each and every call above.                      */
/* -------------------------------------------------------------------- */


int
local_pthread_mutex_unlock (pthread_mutex_t * m)
{
    int         return_value;

    if ((return_value = pthread_mutex_unlock (m)) == -1)
     do_error ("pthread_mutex_unlock");

    return (return_value);
}

int
local_pthread_mutex_lock (pthread_mutex_t * m)
{
    int         return_value;

    if ((return_value = pthread_mutex_lock (m)) == -1)
     do_error ("pthread_mutex_lock");

    return (return_value);
}

int
local_pthread_cond_wait (pthread_cond_t * c, pthread_mutex_t * m)
{
    int         return_value;

    if ((return_value = pthread_cond_wait (c, m)) == -1)
     do_error ("pthread_cond_wait");

    return (return_value);
}

int
local_pthread_cond_signal (pthread_cond_t * c)
{
    int         return_value;

    if ((return_value = pthread_cond_signal (c)) == -1)
     do_error ("pthread_cond_signal");

    return (return_value);
}


/*
 * function just prints an error message and exits 
 *
 */
void
do_error (char *msg)
{
    printf ("%s\n", msg);
    exit (1);
}

/******************************************************************************************/



/*
 * Create a shared memory segment for the Xvideo Image
 * Returns:
 * 	The XvImage structure.
 */

XvImage *
xv_create_image(Display * dpy, int xv_port, int width, int height,
		XShmSegmentInfo * shm)
{
   XvImage *xvimage = NULL;
   memset(shm, 0, sizeof(XShmSegmentInfo));
   xvimage = XvShmCreateImage(dpy, xv_port, GUID_YV12_PLANAR, 0,
			      width, height, shm);
   if (NULL == xvimage) {
     printf("XvShmCreateImage\n");
     goto shm_error;
   }

   shm->shmid = shmget(IPC_PRIVATE, xvimage->data_size, IPC_CREAT | 0777);
   if (-1 == shm->shmid) {
      printf("shmget [xv]\n");
      goto shm_error;
   }
   shm->shmaddr = (char *)shmat(shm->shmid, 0, 0);
   if ((void *)-1 == shm->shmaddr) {
      printf("shmat\n");
      goto shm_error;
   }
   xvimage->data = shm->shmaddr;
   shm->readOnly = False;
   XShmAttach(dpy, shm);
   XSync(dpy, False);
   shmctl(shm->shmid, IPC_RMID, 0);
   
   memset(xvimage->data,0x80,xvimage->data_size);
   return xvimage;

shm_error:
   if (xvimage) {
      XFree(xvimage);
      xvimage = NULL;
   }
   if ((void *)-1 != shm->shmaddr && NULL != shm->shmaddr)
      shmdt(shm->shmaddr);
   free(shm);
   return xvimage;
}

/*
 * Destroy the shared memory segment and the Xvideo Image
 * Returns:
 * 	(void)
 */

void
xv_destroy_image(Display * dpy, XvImage * xvimage, XShmSegmentInfo * shm)
{
   XShmDetach(dpy, shm);
   XFree(xvimage);
   shmdt(shm->shmaddr);
   XSync(dpy, False);
}

/*
 * Copies the source camera data into the Xv image area.
 * If the source image dimensions are the same as the destination, we
 * can just memcpy the data.
 * If we're panning/zooming, then we offset to the source and then
 * memcpy. The hardware scaler will do the rest for us.
 * Returns:
 * 	(void)
 */

void
xv_subimage(unsigned char *dst, unsigned char *src, int dstw, int dsth,
	    int srcw, int srch, int srcxoffset, int srcyoffset)
{
   if (srcw == dstw && srch == dsth) {
      memcpy(dst, src, srcw * srch);
   } else {
      int i;

      src += srcxoffset + (srcw * srcyoffset);

      for (i = 0; i < dsth; i++) {
         memcpy(dst, src, dstw);
         src += srcw;
         dst += dstw;
      }
   }
}

/**********************************************************/

int GetGCFromAWT(JNIEnv* env, jobject canvas, GC *pgc)
{
   JAWT_DrawingSurfaceInfo* dsi;
   JAWT_X11DrawingSurfaceInfo* dsi_x11;
   jboolean result;
   jint lock;
   GC gc;

   /* Get the AWT */
   awt.version = JAWT_VERSION_1_3;
   if (JAWT_GetAWT(env, &awt) == JNI_FALSE) {
      printf("AWT Not found\n");
      return -10;
   }
   //printf("AWT found\n");

   /* Get the drawing surface */
   ds = awt.GetDrawingSurface(env, canvas);
   if (ds == NULL) {
      printf("NULL drawing surface\n");
      return -11;
   }
   //printf("Got Drawing serface\n");

   /* Lock the drawing surface */
   lock = ds->Lock(ds);
   if((lock & JAWT_LOCK_ERROR) != 0) {
      printf("Error locking surface\n");
      awt.FreeDrawingSurface(ds);
      return -12;
   }
   //printf("Locked Drawing surface\n");

   /* Get the drawing surface info */
   dsi = ds->GetDrawingSurfaceInfo(ds);
   if (dsi == NULL) {
      printf("Error getting surface info\n");
      ds->Unlock(ds);
      awt.FreeDrawingSurface(ds);
      return -13;
   }
   //printf("Got Drawing Surface info\n");
   
   /* Get the platform-specific drawing info */
   dsi_x11 = (JAWT_X11DrawingSurfaceInfo*)dsi->platformInfo;
   //printf("Get Platform Info\n");
    
   //window = hwnd;
   window = dsi_x11->drawable;
   //printf("Set Window\n");

   /* Create a drawing context for the window */
   gc = XCreateGC(dpy, window, 0, 0);
   //printf("Created GC\n");

   /* Free the drawing surface info */ 
   ds->FreeDrawingSurfaceInfo(dsi); 

   /* Unlock the drawing surface */ 
   ds->Unlock(ds); 

   /* Free the drawing surface */ 
   awt.FreeDrawingSurface(ds); 

   *pgc = gc;

   return 0;
}

/**
 * Initialize the camera.
 */

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_initialize
(JNIEnv* env, jobject canvas) 
{
   int rc = 0; 
  
   dpy = XOpenDisplay(NULL);
   if (dpy == NULL) {
      printf("Cannot open Display.\n");
      rc = -1;
      goto jv_init_exit;
   }
  
   /* Do all the querying of the Xserver first */
  
   /* Query the server for the SHM-MIT extension */
   if (!XShmQueryExtension(dpy)) {
      printf("no shmem available.\n");
      rc = -2;
      goto jv_init_exit;
   }

   /* Query the Xserver for the Xvideo Extension */
   if ((ret = XvQueryExtension(dpy, &p_version, &p_release, &p_request_base,
			       &p_event_base, &p_error_base))) {
      printf("failed XvQueryExtension, return code %d.\n", ret);
      rc = -3;
      goto jv_init_exit;
   }

   /* Now query the Xv adaptors available */
   if ((ret = XvQueryAdaptors(dpy, DefaultRootWindow(dpy),(unsigned int*) &numAdaptors,
			      &ai))) {
      printf("failed XvQueryAdaptors, return code %d.\n", ret);
      rc = -4;
      goto jv_init_exit;
   }

   /* Now, we open the Video4Linux device */
   if (!(fd = open(VIDEO_DEVICE, O_RDWR))) {
      printf("v4l: open %s: %s\n", VIDEO_DEVICE, strerror(errno));
      rc = -5;
      goto jv_init_exit;
   }

   /* Query the v4l device's capabilities */
   if (-1 == ioctl(fd, VIDIOCGCAP, &capability)) {
      printf("v4l: ioctl %s: %s\n", VIDEO_DEVICE, strerror(errno));
      close(fd);
      rc = -6;
      goto jv_init_exit;
   }

   /* Display some useful capability information */
   printf("v4l: opened: %s (%s)\n", VIDEO_DEVICE, capability.name);
   printf("  capabilities: ");
   for (i = 0; device_cap[i] != NULL; i++)
      if (capability.type & (1 << i))
         printf(" %s", device_cap[i]);
   printf("\n");
   printf("  minimum image size    : %dx%d\n", capability.minwidth, capability.minheight);
   printf("  maximum image size    : %dx%d\n", capability.maxwidth, capability.maxheight);

   /* If we didn't find a CAPTURE device, exit now */
   if (!(capability.type & VID_TYPE_CAPTURE)) {
      printf(" No capture device found.\n");
      close(fd);
      rc = -7;
      goto jv_init_exit;
   }

   /* map the capture device's grab buffer */
   if (ioctl(fd, VIDIOCGMBUF, &mbuf) != 0) {
      printf("failed to ioctl camera buffer\n");
      rc = -8;
      goto jv_init_exit;
   }

   printf("  mbuf: size=%d frames=%d\n", mbuf.size, mbuf.frames);

   /* Get a pointer to the capture devices's memory mapped image */
   mmapCamera = (unsigned char*)mmap(0, mbuf.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
   if (!mmapCamera) {
      printf("failed to mmap camera buffer\n");
      rc = -9;
      goto jv_init_exit;
   }

   /* Allocate storage space for double buffering, and setup the capture
    * device for greyscale, and an initial YUV width and height */
   buf_v4l = (video_mmap*)malloc(mbuf.frames * sizeof(struct video_mmap));
   memset(buf_v4l, 0, mbuf.frames * sizeof(struct video_mmap));
   for (i = 0; i < mbuf.frames; i++) {
      buf_v4l[i].format = VIDEO_PALETTE_GREY;
      buf_v4l[i].frame = i;
      buf_v4l[i].width = yuv_width;
      buf_v4l[i].height = yuv_height;
   }

   adaptor = -1;

   screen = DefaultScreen(dpy);
   //colormap = DefaultColormap(dpy,screen);


   if (numAdaptors > 1)
      printf("Found more than one adaptor, using first anyway....\n");

   /* Get the XVideo port of the Xv adaptor we are interested in */
   xv_port = ai[0].base_id;

   if (numAdaptors > 0)
      XvFreeAdaptorInfo(ai);

   /* Grab the Xvideo port, so that no other application tries to get it */
   XvGrabPort(dpy, xv_port, CurrentTime);

   /* Initialize semaphore -- starts "up" at 1 */
   semaphore_init(&interFrame);

jv_init_exit:
   return rc;
}

/**
 * Pan x and y.
 */

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_pan
  (JNIEnv* env, jobject canvas, jint dx, jint dy)
{
   int rc = 0;

   semaphore_down(&interFrame);

   //Let java control the pan and zoom #'s
   zoom_offset_x = dx;
   zoom_offset_y = dy;

   semaphore_up(&interFrame);
   //printf("Pan (%d,%d)\n",zoom_offset_x,zoom_offset_y);
   return rc;
}

/**
 * Zoom in.
 */

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_zoom
(JNIEnv* env, jobject canvas, jint dx, jint dy)
{
   int rc = 0;

   semaphore_down(&interFrame);

   //Let java control the pan and zoom #'s
   zoom_width = dx;
   zoom_height = dy;

   /* signal main thread to recreate yuv_image */
   zoomed = TRUE;

   semaphore_up(&interFrame);
   //printf("Zoom (%d,%d)\n",zoom_width,zoom_height);
   return rc;
}

/**
 * Freeze the image.
 */

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_freeze
(JNIEnv* env, jobject canvas, jboolean freezeFrame)
{
   int rc = 0;

   semaphore_down(&interFrame);

   freeze = freezeFrame;

   semaphore_up(&interFrame);

   return rc;
}

/**
 * Start showing the video -- this call is intended to be made in its own thread
 */

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_on
(JNIEnv* env, jobject canvas)
{
   int db = 1; /* double buffer flag */
   int badGC;
   GC gc;
   float j;


   //printf("Entered On Method\n");

   /* signal video thread active */
   videoOn = TRUE;

   /* this will force allocation of yuv_image first time thru loop */
   zoomed = TRUE; 

   badGC = GetGCFromAWT(env, canvas, &gc);
   if (badGC)
      return badGC;

   //XColor color;
   //char colorname[] = "Blue";

   //XParseColor(dpy,colormap,colorname,&color);
   //XAllocColor(dpy, colormap, &color);
   //XSetForeground (dpy,gc,color.pixel);
     

   /* As we're double buffering, get the initial image so CSYNC doesn't fail */
   if (-1 == ioctl(fd, VIDIOCMCAPTURE, buf_v4l))
      printf("failed mmap ioctl copy for buffer 0\n");

   if (-1 == ioctl(fd, VIDIOCMCAPTURE, buf_v4l + 1))
      printf("failed mmap ioctl copy for buffer 1\n");

   /* MAIN LOOP */
   while (!videoCancelled) {

      /* Our frame counter variables */
      frames = secsa = secsb = 0;
      /* start the timer */
      time(&secsa);

      while (frames < 200 && !videoCancelled) { 
         
         //printf(".");

         /* Get the current window size, just in case we've been resized */
         XGetGeometry(dpy, window, &_dw, &_d, &_d,(unsigned int*) &_w,(unsigned int*) &_h, (unsigned int*)&_d,(unsigned int*) &_d);

         semaphore_down(&interFrame);
	 /* Draw reticle */
	 XDrawArc(dpy, window, gc, (_w/2) - (reticalDiameter/2),(_h/2) - (reticalDiameter/2),reticalDiameter,reticalDiameter,0,360*64);        
	 if (drawAxis) {
	   XDrawLine(dpy, window, gc, _w/2, 0, _w/2, _h);
	   XDrawLine(dpy, window, gc, 0, _h/2, _w, _h/2);
	   
	   
	   
	   /* Draw Ticks */
	   
	   for (j=_w/2.0f;j<_w;j+=tickSpacing) {
	     XDrawLine(dpy,window,gc,((int)(j+0.5f)),_h/2,((int)(j+0.5f)),(_h/2)+tickLength);
	     XDrawLine(dpy,window,gc,((int)(j+0.5f)),_h/2,((int)(j+0.5f)),(_h/2)-tickLength); 
	   }
	   
	   for (j=_w/2;j>0;j-=tickSpacing) {
	     XDrawLine(dpy,window,gc,((int)(j+0.5f)),_h/2,((int)(j+0.5f)),(_h/2)+tickLength);
	     XDrawLine(dpy,window,gc,((int)(j+0.5f)),_h/2,((int)(j+0.5f)),(_h/2)-tickLength);  
	   }
	   
	   for (j=_h/2;j<_h;j+=tickSpacing) {
	     XDrawLine(dpy,window,gc,_w/2,((int)(j+0.5f)),(_w/2)+tickLength,((int)(j+0.5f)));
	     XDrawLine(dpy,window,gc,_w/2,((int)(j+0.5f)),(_w/2)-tickLength,((int)(j+0.5f)));
	   }
	   
	   for (j=_h/2;j>0;j-=tickSpacing) {
	     XDrawLine(dpy,window,gc,_w/2,((int)(j+0.5f)),(_w/2)+tickLength,((int)(j+0.5f)));
	     XDrawLine(dpy,window,gc,_w/2,((int)(j+0.5f)),(_w/2)-tickLength,((int)(j+0.5f)));
	   }
	   
	 }
         
	 /* double-buffered camera frames */
         if (db == 0)
            db = 1;
         else
            db = 0;

         /* Sync and grab the next frame from the capture device */
         if (-1 == ioctl(fd, VIDIOCSYNC, buf_v4l + db))
            printf("failed csync ioctl copy\n");
         if (-1 == ioctl(fd, VIDIOCMCAPTURE, buf_v4l + db))
            printf("failed mmap ioctl copy\n");

         /* Now scale the image for our current Zoom parameters */
         if (zoomed) {
            zoomed = FALSE;

            /* destroy old xv_image */
            if (yuv_image)
               xv_destroy_image(dpy, yuv_image, &shm);

            /* create/re-create new image with zoom parameters */
            yuv_image = xv_create_image(dpy, xv_port, zoom_width, zoom_height, &shm);
            //printf("created image\n");
         }

         if (yuv_image) {
            xv_subimage((unsigned char*)yuv_image->data, mmapCamera, zoom_width,
                        zoom_height, yuv_width, yuv_height,
                        zoom_offset_x, zoom_offset_y);

            if (!freeze) {
               XvShmPutImage(dpy, xv_port, window, gc, yuv_image, 0, 0, zoom_width, zoom_height, 0, 0, _w, _h, False);
         
	    } else {
	      XDrawLine(dpy, window, gc, _w/2, 0, _w/2, _h);
	      XDrawLine(dpy, window, gc, 0, _h/2, _w, _h/2);
	      
	      
	      
	      /* Draw Ticks */
	      
	      for (j=_w/2.0f;j<_w;j+=tickSpacing) {
		XDrawLine(dpy,window,gc,((int)(j+0.5f)),_h/2,((int)(j+0.5f)),(_h/2)+tickLength);
		XDrawLine(dpy,window,gc,((int)(j+0.5f)),_h/2,((int)(j+0.5f)),(_h/2)-tickLength); 
	      }
	      
	      for (j=_w/2;j>0;j-=tickSpacing) {
		XDrawLine(dpy,window,gc,((int)(j+0.5f)),_h/2,((int)(j+0.5f)),(_h/2)+tickLength);
		XDrawLine(dpy,window,gc,((int)(j+0.5f)),_h/2,((int)(j+0.5f)),(_h/2)-tickLength);  
	      }
	      
	      for (j=_h/2;j<_h;j+=tickSpacing) {
		XDrawLine(dpy,window,gc,_w/2,((int)(j+0.5f)),(_w/2)+tickLength,((int)(j+0.5f)));
		XDrawLine(dpy,window,gc,_w/2,((int)(j+0.5f)),(_w/2)-tickLength,((int)(j+0.5f)));
	      }
	      
	      for (j=_h/2;j>0;j-=tickSpacing) {
		XDrawLine(dpy,window,gc,_w/2,((int)(j+0.5f)),(_w/2)+tickLength,((int)(j+0.5f)));
		XDrawLine(dpy,window,gc,_w/2,((int)(j+0.5f)),(_w/2)-tickLength,((int)(j+0.5f)));
	      }
	      
	      
	    }
	 } else {
            printf("yuv_image NULL\n");

	 }

         /* If we've freezed the frame we don't call Xv to put the image up */
	 
         semaphore_up(&interFrame);

         frames++;

         pthread_yield();
      }

      time(&secsb); 
      int dt = secsb-secsa;
      printf("%ld frames in %ld seconds; %.4f fps\n", frames, dt, ((double)frames / ((dt>0) ? dt : 1)));
 
   }

   XFreeGC(dpy, gc); 

   if (yuv_image) 
   {
      xv_destroy_image(dpy, yuv_image, &shm);
      yuv_image = NULL;
   }

   videoCancelled = FALSE;

   videoOn = FALSE;

   return 0;
}

/**
 * Stop the on loop.
 */

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_off
(JNIEnv* env, jobject canvas)
{
   int rc = 0;

   semaphore_down(&interFrame);

   videoCancelled = TRUE;

   semaphore_up(&interFrame);

   //printf("Turning Off\n");

   /* wait while video thread shuts down */
   while (videoOn) 
   {
      pthread_yield();
   }

   return rc;
}

/**
 * Free resources
 */

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_cleanup
(JNIEnv* env, jobject canvas)
{
   int rc = 0;

   //(void)Java_fdt2_gui_screens_CameraCanvas_off(env, canvas);

   semaphore_down(&interFrame);

   semaphore_destroy(&interFrame);

   XvUngrabPort(dpy, xv_port, CurrentTime);

   /* Close down v4l */
   printf("v4l: close\n");

   if ((unsigned char *)-1 != mmapCamera)
      munmap(mmapCamera, mbuf.size);

   close(fd);

   return rc;
}

JNIEXPORT jfloat JNICALL Java_fdt2_gui_screens_CameraCanvas_version
(JNIEnv* env, jobject canvas)
{
  return VERSION;
}

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_setRetical
(JNIEnv* env, jobject canvas,jint retical)
{
  reticalDiameter = retical;
  return reticalDiameter;
}

JNIEXPORT jfloat JNICALL Java_fdt2_gui_screens_CameraCanvas_setTickSpacing
(JNIEnv* env, jobject canvas,jfloat ticks)
{
  tickSpacing = ticks;
  return tickSpacing;
}

JNIEXPORT jint JNICALL Java_fdt2_gui_screens_CameraCanvas_setTickLength
(JNIEnv* env, jobject canvas,jint ticks)
{
  tickLength = ticks;
  return tickLength;
}

JNIEXPORT jboolean JNICALL Java_fdt2_gui_screens_CameraCanvas_drawAxis
(JNIEnv* env, jobject canvas,jboolean enable)
{
  drawAxis = enable;
  return enable;
}
