#include "glslides.h" /* Stereo capable slide projector Images are on a circular projector Added screen centering for larger monitors November 1999 Added PPM, TGA, and RLE image types Added frame separation to test non alignment problems Added frame offsets, l,L and r,R for stereo movie alignment September 2000 Removed the 800x600 fixed size restriction Removed the need for the left and right images to be the same size The delays are in 1/10 of a second Raw images don't have a size, assumed to be 800x600 Autoplay is disabled on user contact with < or > Fixed lower left position less than -1 The input file consists of slide entry lines which contain up to four columns, left image, right image, delay, and flip. Lines starting with a # are comment lines. A slide line where the left image starts with a ! is a command line which consists of the directory, command to run. The optional third column is a delay in 1/20 seconds. The optional fourth column is a vertcal flip flag, -f For example ----------- left1.raw right1.raw left2.raw right2.raw 0 image.tga image.tga 0 -f # This is a comment left3.ppm right3.ppm !/home/user/pbourke runme left4.rle right4.rle 10 */ /* Initial image size */ int L_WIDTH = 800; int L_HEIGHT = 600; int R_WIDTH = 800; int R_HEIGHT = 600; int screenwidth = 800; int screenheight = 600; double lowleft=-1,lowright=-1; int leftoffset = 0,rightoffset = 0; double xoffset = 0,yoffset = 0; /* List of image names */ IMAGELIST *imagelist; int nimage = 0; int currentimage = 0; /* Image buffers */ PIXEL *left = NULL; PIXEL *right = NULL; /* Miscellaneous Flags */ int stereo = FALSE; int fullscreen = FALSE; int debug = FALSE; int framedelay = 0; int showhelp = FALSE; int autoplay = FALSE; int main(int argc,char **argv) { int i,n,c; FILE *fptr; int delay,flip; char s1[64],s2[64],s3[64],s4[64],aline[256]; /* Are we being called correctly */ if (argc < 2) { GiveUsage(argv[0]); exit(-1); } /* Check/get the command line arguments */ for (i=1;i= 3) { if (atoi(s3) >= 0 && atoi(s3) <= 10000) delay = atoi(s3); } /* Read the optional flipping flag */ flip = FALSE; if (n >= 4) { if (strcmp(s4,"-f") == 0 || strcmp(s4,"-F") == 0) flip = TRUE; } /* Add to the image list */ if ((imagelist = realloc(imagelist,(nimage+1)*sizeof(IMAGELIST))) == NULL) { fprintf(stderr,"Failed to allocate space for the imagelist\n"); return(-1); } strcpy(imagelist[nimage].left,s1); strcpy(imagelist[nimage].right,s2); imagelist[nimage].delay = delay; imagelist[nimage].flip = flip; nimage++; } fclose(fptr); if (debug) fprintf(stderr,"Found %d images\n",nimage); if (nimage < 1) { fprintf(stderr,"Didn't find any slides\n"); exit(-1); } HandleMemory('b',L_WIDTH,L_HEIGHT); currentimage = 0; UpdateCurrent(0); /* Get the screensize for full screen */ if (fullscreen) { screenwidth = glutGet(GLUT_SCREEN_WIDTH); if (screenwidth <= 0) screenwidth = 800; screenheight = glutGet(GLUT_SCREEN_HEIGHT); if (screenheight <= 0) screenheight = 600; } if (debug) fprintf(stderr,"Screen size = %d x %d\n",screenwidth,screenheight); /* Set things up and go */ if (!stereo) glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); else glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STEREO); glutCreateWindow("Stereo Slide Show"); glutReshapeWindow(screenwidth,screenheight); if (fullscreen) glutFullScreen(); glutDisplayFunc(HandleDisplay); glutReshapeFunc(HandleReshape); glutVisibilityFunc(HandleVisibility); glutKeyboardFunc(HandleKeyboard); glutSpecialFunc(HandleSpecialKeyboard); glutMouseFunc(HandleMouse); glutSetCursor(GLUT_CURSOR_NONE); CreateEnvironment(); CreateMenus(); if (debug) fprintf(stderr,"Here we go\n"); glutMainLoop(); return(0); } /* This is where global settings are made, things that will not change per frame */ void CreateEnvironment(void) { glEnable(GL_DEPTH_TEST); /* Assume RGBA capabilities */ glDisable(GL_DITHER); /* Antialiasing */ glDisable(GL_LINE_SMOOTH); glDisable(GL_POINT_SMOOTH); glDisable(GL_POLYGON_SMOOTH); /* Miscellaneous other options */ glLineWidth(1.0); glPointSize(1.0); glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); glFrontFace(GL_CW); glDisable(GL_CULL_FACE); glClearColor(0.0,0.0,0.0,0.0); glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); glPixelStorei(GL_UNPACK_ALIGNMENT,1); } /* Handle a window reshape/resize */ void HandleReshape(int w,int h) { glDrawBuffer(GL_BACK); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0,0,(GLsizei)w,(GLsizei)h); screenwidth = w; screenheight = h; } /* This is the basic display callback routine called to redraw the buffers optionally in stereo The left and right images are centered on the screen */ void HandleDisplay(void) { glDrawBuffer(GL_BACK); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (stereo) { lowleft = - R_WIDTH / (double)screenwidth; lowright = - R_HEIGHT / (double)screenheight; lowleft = MAX(lowleft,-1); lowright = MAX(lowright,-1); glDrawBuffer(GL_BACK_RIGHT); glRasterPos2f(lowleft-xoffset,lowright-yoffset); glDrawPixels(R_WIDTH,R_HEIGHT,GL_RGB,GL_UNSIGNED_BYTE,right); if (showhelp) DrawScreenInfo(); lowleft = - L_WIDTH / (double)screenwidth; lowright = - L_HEIGHT / (double)screenheight; lowleft = MAX(lowleft,-1); lowright = MAX(lowright,-1); glDrawBuffer(GL_BACK_LEFT); glRasterPos2f(lowleft,lowright); glDrawPixels(L_WIDTH,L_HEIGHT,GL_RGB,GL_UNSIGNED_BYTE,left); if (showhelp) DrawScreenInfo(); } else { lowleft = - L_WIDTH / (double)screenwidth; lowright = - L_HEIGHT / (double)screenheight; lowleft = MAX(lowleft,-1); lowright = MAX(lowright,-1); glRasterPos2f(lowleft-xoffset,lowright-yoffset); glDrawPixels(L_WIDTH,L_HEIGHT,GL_RGB,GL_UNSIGNED_BYTE,left); if (showhelp) DrawScreenInfo(); } glutSwapBuffers(); } double GetRunTime(void) { double sec = 0; struct timeval tp; gettimeofday(&tp,NULL); sec = tp.tv_sec + tp.tv_usec / 1000000.0; return(sec); } /* Deal with plain key strokes */ void HandleKeyboard(unsigned char key,int x, int y) { switch (key) { case 27: /* ESC */ case 'Q': /* Quit */ case 'q': exit(0); break; case 'g': /* Toggle autoplay */ case 'G': autoplay = !autoplay; break; case 'h': /* Go to start of sequence */ case 'H': currentimage = 0; UpdateCurrent(0); break; case '1': /* Decrease delay between frames */ framedelay -= 5; if (framedelay < 0) framedelay = 0; break; case '2': /* Increase delay between frames */ framedelay += 5; break; case '<': case ',': autoplay = FALSE; UpdateCurrent(-1); break; case '>': case '.': autoplay = FALSE; UpdateCurrent(1); break; case '+': case '=': xoffset += 1.0 / screenwidth; break; case '-': case '_': xoffset -= 1.0 / screenwidth; break; case '[': case '{': yoffset += 1.0 / screenwidth; break; case ']': case '}': yoffset -= 1.0 / screenwidth; break; case 'l': leftoffset--; UpdateCurrent(0); break; case 'L': leftoffset++; UpdateCurrent(0); break; case 'r': rightoffset--; UpdateCurrent(0); break; case 'R': rightoffset++; UpdateCurrent(0); break; } } /* Update the current image pointer Delays and frame delays are 1/10 of a second Choose between slides and programs */ void UpdateCurrent(int direction) { int index; char cmd[256]; static double lasttime = -1; double dt; /* Wait for the appropriate delay when in autoplay mode */ if (autoplay) { if (lasttime < 0) lasttime = GetRunTime(); dt = (framedelay + imagelist[currentimage].delay) / 10.0; if (debug) fprintf(stderr,"%lf %lf %lf\n",lasttime,GetRunTime(),dt); if (GetRunTime() - lasttime < dt) return; } /* Update the current image pointer */ currentimage += direction; currentimage += nimage; currentimage %= nimage; /* Do the right thing for command lines */ while (imagelist[currentimage].left[0] == '!') { /* Clear the image */ CreateDummy('l'); if (stereo) CreateDummy('r'); /* Issue the command */ sprintf(cmd,"cd %s ; %s", &(imagelist[currentimage].left[1]), imagelist[currentimage].right); system(cmd); /* Move onto the next image */ if (direction > 0) currentimage++; else if (direction < 0) currentimage -= 2; currentimage += nimage; currentimage %= nimage; } /* Read the image pairs */ index = (currentimage + leftoffset + nimage) % nimage; if (!ReadImage(imagelist[index].left,'l')) { fprintf(stderr,"Image read failed for image \"%s\"\n",imagelist[index].left); CreateDummy('l'); } else { if (debug) fprintf(stderr,"Successfully read file \"%s\"\n",imagelist[index].left); } if (stereo) { index = (currentimage+rightoffset+nimage)%nimage; if (!ReadImage(imagelist[index].right,'r')) { fprintf(stderr,"Image read failed for image \"%s\"\n",imagelist[index].right); CreateDummy('r'); } else { if (debug) fprintf(stderr,"Successfully read file \"%s\"\n",imagelist[index].right); } } lasttime = GetRunTime(); } /* Deal with special key strokes Arrow keys rotate about the focal position */ void HandleSpecialKeyboard(int key,int x, int y) { switch (key) { case GLUT_KEY_RIGHT: HandleKeyboard('>',0,0); break; case GLUT_KEY_LEFT: HandleKeyboard('<',0,0); break; case GLUT_KEY_UP: break; case GLUT_KEY_DOWN: break; case GLUT_KEY_F1: showhelp = !showhelp; break; } } /* Handle mouse events */ void HandleMouse(int button,int state,int x,int y) { if (state == GLUT_DOWN) { if (button == GLUT_LEFT_BUTTON) { } else if (button == GLUT_MIDDLE_BUTTON) { } /* Right button events are passed to menu handlers */ } } /* Form the menu */ void CreateMenus(void) { int mainmenu; mainmenu = glutCreateMenu(HandleMainMenu); glutAddMenuEntry("Toggle Help (F1)",1); glutAddMenuEntry("Toggle Autoplay (g)",2); glutAddMenuEntry("Go to start (h)",3); glutAddMenuEntry("Quit (q)",9); glutAttachMenu(GLUT_RIGHT_BUTTON); } /* Handle the main menu */ void HandleMainMenu(int whichone) { const char *fn; char fname[256]; switch (whichone) { case 1: showhelp = !showhelp; break; case 2: autoplay = !autoplay; break; case 3: currentimage = 0; UpdateCurrent(0); break; case 9: exit(0); break; } } /* How to handle visibility */ void HandleVisibility(int visible) { if (visible == GLUT_VISIBLE) HandleTimer(0); } /* What to do on an idle event */ void HandleTimer(int value) { if (autoplay) UpdateCurrent(1); if (!fullscreen) glutReshapeWindow(L_WIDTH,L_HEIGHT); glutPostRedisplay(); glutTimerFunc(20,HandleTimer,0); } /* Read a PPM, TGA, RLE, TIF, or RAW image Swap the r and b for tga images Optionally flip the images vertically */ int ReadImage(char *filename,int eye) { int i,j,k,d,w,h,c; int low,high; char id[32]; FILE *fptr; PIXEL tmp; int status = TRUE; int imagetype; if (debug) fprintf(stderr,"Attempting to read file \"%s\"\n",filename); if (strstr(filename,".ppm") != NULL || strstr(filename,".PPM") != NULL) { imagetype = PPM; } else if (strstr(filename,".raw") != NULL || strstr(filename,".RAW") != NULL) { imagetype = RAW; } else if (strstr(filename,".rle") != NULL || strstr(filename,".RLE") != NULL) { imagetype = RLE; } else if (strstr(filename,".tga") != NULL || strstr(filename,".TGA") != NULL) { imagetype = TGA; } else if (strstr(filename,".tif") != NULL || strstr(filename,".TIF") != NULL) { imagetype = TIF; } else { fprintf(stderr,"Unknown image \"%s\", expect (raw,ppm,tga,rle)\n",filename); return(FALSE); } if (debug) fprintf(stderr,"Think I've found image type %d\n",imagetype); if ((fptr = fopen(filename,"r")) == NULL) return(FALSE); /* -------------------------------------------------- RAW ----------------- */ if (imagetype == RAW) { w = L_WIDTH; /* Fixed size for raw images */ h = L_HEIGHT; HandleMemory(eye,w,h); } /* -------------------------------------------------- PPM ----------------- */ if (imagetype == PPM) { if (fscanf(fptr,"%s",id) != 1 || strcmp(id,"P6") != 0) { fprintf(stderr,"Unexpected file id of \"%s\", expected \"P6\"\n",id); status = FALSE; goto skip; } if (fscanf(fptr,"%d %d",&w,&h) != 2) { fprintf(stderr,"Failed to read width and height\n"); status = FALSE; goto skip; } HandleMemory(eye,w,h); if (fscanf(fptr,"%d",&d) != 1 || d != 255) { fprintf(stderr,"Unexpected image depth of %d, expected 255\n",d); status = FALSE; goto skip; } while ((c = fgetc(fptr)) != EOF && c != '\n') ; } /* -------------------------------------------------- RLE ----------------- */ if (imagetype == RLE) { if (fscanf(fptr,"%d %d",&w,&h) != 2) { fprintf(stderr,"Failed to read width and height\n"); status = FALSE; goto skip; } HandleMemory(eye,w,h); while ((c = fgetc(fptr)) != EOF && c != '\n') ; } /* -------------------------------------------------- TIF ----------------- */ if (imagetype == TIF) { for (i=0;i<8;i++) c = fgetc(fptr); w = L_WIDTH; /* Fixed size for tif images */ h = L_HEIGHT; HandleMemory(eye,w,h); } /* -------------------------------------------------- TGA ----------------- */ if (imagetype == TGA) { if ((c = fgetc(fptr)) != 0) { fprintf(stderr,"Can only handle 0 bytes in identification field\n"); status = FALSE; goto skip; } if ((c = fgetc(fptr)) != 0) { fprintf(stderr,"Can only handle colourmap type of 0\n"); status = FALSE; goto skip; } if ((c = fgetc(fptr)) != 2) { fprintf(stderr,"Can only handle uncompressed TGA\n"); status = FALSE; goto skip; } for (i=0;i<5;i++) c = fgetc(fptr); /* Skip the origin */ for (i=0;i<4;i++) c = fgetc(fptr); /* Read the dimension */ low = fgetc(fptr); high = fgetc(fptr); w = low + high * 256; low = fgetc(fptr); high = fgetc(fptr); h = low + high * 256; HandleMemory(eye,w,h); if ((c = fgetc(fptr)) != 24) { fprintf(stderr,"Can only handle 24 bit TGA, found %d\n",c); status = FALSE; goto skip; } c = fgetc(fptr); /* Image Descriptor Byte */ } if (debug) fprintf(stderr,"Successfully read the header\n"); if (eye == 'l') { if (fread(left,sizeof(PIXEL),L_WIDTH*L_HEIGHT,fptr) != L_WIDTH*L_HEIGHT) { fprintf(stderr,"Error reading left image data\n"); status = FALSE; goto skip; } if (debug) fprintf(stderr,"Read left raw image data successfully\n"); if (imagelist[currentimage].flip) { for (j=0;j - go forward (right arrow)",colour); y += 13; DrawGLText(10,screenheight-y," f1 - toggle this help information",colour); y += 13; DrawGLText(10,screenheight-y," q - quit (ESC)",colour); y += 13; } /* Display the program usage information */ void GiveUsage(char *cmd) { fprintf(stderr,"Usage: %s [-h] [-s] [-f] slidelistfile\n",cmd); fprintf(stderr," -h this text\n"); fprintf(stderr," -s stereo mode\n"); fprintf(stderr," -f full screen mode\n"); fprintf(stderr," -a start in autoplay mode\n"); fprintf(stderr,"Key Strokes and Menus:\n"); fprintf(stderr," q quit\n"); fprintf(stderr," g autoplay\n"); fprintf(stderr," h go to frame 1 (home)\n"); fprintf(stderr," l decrement left image offset\n"); fprintf(stderr," L increment left image offset\n"); fprintf(stderr," r decrement right image offset\n"); fprintf(stderr," R increment right image offset\n"); fprintf(stderr," < last frame\n"); fprintf(stderr," > next frame\n"); fprintf(stderr," left arrow last frame\n"); fprintf(stderr," right arrow next frame\n"); fprintf(stderr," f1 toggle help information\n"); } /* Write OpenGL test */ void DrawGLText(int x,int y,char *s,COLOUR c) { int lines; char *p; glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0,glutGet(GLUT_WINDOW_WIDTH), 0,glutGet(GLUT_WINDOW_HEIGHT),-1,1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glColor3f(c.r,c.g,c.b); glRasterPos2i(x,y); for (p=s,lines=0;*p;p++) { if (*p == '\n' || *p == '\r') { lines++; glRasterPos2i(x,y-(lines*13)); } else { glutBitmapCharacter(GLUT_BITMAP_8_BY_13,*p); } } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glEnable(GL_DEPTH_TEST); } /* Deal with memory for the buffers eye is either 'l', 'r', or 'b' for both */ void HandleMemory(int eye,int w,int h) { int i,j; if (eye == 'b' || eye == 'l') { if (L_WIDTH == w && L_HEIGHT == h && left != NULL) return; L_WIDTH = w; L_HEIGHT = h; if (left != NULL) free(left); if ((left = malloc(L_WIDTH*L_HEIGHT*sizeof(PIXEL))) == NULL) { fprintf(stderr,"Malloc of left image failed\n"); exit(-1); } } if (eye == 'b' || eye == 'r') { if (R_WIDTH == w && R_HEIGHT == h && right != NULL) return; R_WIDTH = w; R_HEIGHT = h; if (right != NULL) free(right); if ((right = malloc(R_WIDTH*R_HEIGHT*sizeof(PIXEL))) == NULL) { fprintf(stderr,"Malloc of right image failed\n"); exit(-1); } } } /* Create dummy image for when we can recover from a read failure */ void CreateDummy(int eye) { int i,j; PIXEL black = {0,0,0}; if (eye == 'l') { for (i=0;i