//////////////////////////////////////////////////////////////////////////////
//
//  Author      : Josh Grant
//  Date        : October 16th, 2001
//  File        : ExaminerViewerPlus.cpp
//  Description : Implementation of the ExaminerViewerPlus class
//
//////////////////////////////////////////////////////////////////////////////

#include <config.h>

#include <X11/Intrinsic.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <Xm/PushB.h>
#include <Xm/Form.h>
#include <Xm/RowColumn.h>
#include <Xm/FileSB.h>
#include <Xm/SelectioB.h>
#include <Xm/MessageB.h>
#include <Xm/Label.h>
#include <Xm/TextF.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream.h>
#include <Inventor/SoOutput.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/SoOffscreenRenderer.h>
#include <Inventor/actions/SoWriteAction.h>

#include "ExaminerViewerPlus.h"


//////////////////////////////////////////////////////////////////////////////
//
// ExaminerViewerPlus class
//
//////////////////////////////////////////////////////////////////////////////

static const char *thisClassName = "ExaminerViewerPlus";

// the available output file types
static char *imageTypes[] = {
  "rgb",
  "ppm",
#ifdef HAVE_JPEGLIB
  "jpg",
#endif
#ifdef HAVE_PNGLIB
  "png",
#endif
  "ps",
  "iv ASCII",
  "iv Binary",
  NULL
};

// macro used when getting the drawable object for determining the foreground
// and background colors for the button pixmaps
#define SCREEN(w) XScreenNumberOfScreen( XtScreen(w) )

// pixmap for the snapshot 'camera' button
#define PIXMAP_WIDTH   24
#define PIXMAP_HEIGHT  24
static char examinervp_cam_bits[] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x7e, 0x00, 0x18, 0x42, 0x00, 0xfe, 0xff, 0x7f, 0x02, 0x00, 0x40, 
  0x02, 0x00, 0x5c, 0x02, 0xff, 0x5c, 0x02, 0x81, 0x40, 0x02, 0x99, 0x40, 
  0x02, 0x81, 0x40, 0x02, 0xff, 0x40, 0x02, 0x00, 0x40, 0x02, 0x00, 0x40, 
  0xfe, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};


ExaminerViewerPlus::ExaminerViewerPlus (Widget parent,
					const char *name,
					SbBool buildInsideParent,
					SoXtFullViewer::BuildFlag b,
					SoXtViewer::Type t)
  : SoXtExaminerViewer(parent,
		       name,
		       buildInsideParent,
		       b,
		       t,
		       FALSE) // tell GLWidget not to build just yet  
{
    // In this case, render area is what the app wants, so buildNow = TRUE
    constructorCommon(TRUE);
}

ExaminerViewerPlus::ExaminerViewerPlus (Widget parent,
					const char *name,
					SbBool buildInsideParent,
					SoXtFullViewer::BuildFlag b,
					SoXtViewer::Type t,
					SbBool buildNow)
  : SoXtExaminerViewer(parent,
		       name,
		       buildInsideParent,
		       b,
		       t,
		       FALSE) // tell GLWidget not to build just yet  
{
    // In this case, render area may be what the app wants, 
    // or it may want a subclass of render area. Pass along buildNow
    // as it was passed to us.
    constructorCommon(buildNow);
}

ExaminerViewerPlus::~ExaminerViewerPlus ()
{
  unregisterWidget(snapshot);
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Overloaded to create an additional button for taking a snapshot of the
//    scene on the righthand column button list.
//
//////////////////////////////////////////////////////////////////////////////
void
ExaminerViewerPlus::createViewerButtons (Widget parent)
{
  Arg args[7];
  int n = 0, depth;
  Pixel fg, bg;
  Pixmap pixels;

  // create originial buttons
  SoXtExaminerViewer::createViewerButtons(parent);
  
  // the following block of code is used to add the extra button.  In order
  // to do this we have to find out what the background and foreground colors
  // of the window are so the extra button has the same look as the others.

  // grab the display
  Display *display = XtDisplay(parent);
  // get the drawable object 
  Drawable d = RootWindow(display, SCREEN(parent));

  XtVaGetValues(parent, XtNdepth, &depth, NULL);

  // extract the background and foreground colors
  XtSetArg(args[n], XmNforeground, &fg); n++;
  XtSetArg(args[n], XmNbackground, &bg); n++;
  XtGetValues(parent, args, n);

  // using the bitmap data from 'examinervp_cam_bits' create a pixmap
  pixels = XCreatePixmapFromBitmapData(display, d, examinervp_cam_bits,
				       PIXMAP_WIDTH, PIXMAP_HEIGHT,
				       fg, bg, depth);
  // create the button
  n = 0;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  XtSetArg(args[n], XmNmarginWidth, 0); n++;
  XtSetArg(args[n], XmNshadowThickness, 2); n++;
  XtSetArg(args[n], XmNhighlightThickness, 0); n++;
  XtSetArg(args[n], XmNlabelType, XmPIXMAP); n++;
  XtSetArg(args[n], XmNlabelPixmap, pixels); n++;
  snapshot = XtCreateWidget("snapshot", xmPushButtonWidgetClass,
			    parent, args, n);
  
  // add a callback to the button
  XtAddCallback(snapshot, XmNactivateCallback,
		(XtCallbackProc)ExaminerViewerPlus::snapshotCB, this);
  XtManageChild(snapshot);

  // add it to the list
  viewerButtonWidgets->append(snapshot);
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Called by all constructors to set up the widgets.
//
//////////////////////////////////////////////////////////////////////////////
void
ExaminerViewerPlus::constructorCommon (SbBool buildNow)
{
  setClassName(thisClassName);
  fileDialog = NULL;

  // build everything else like the parent viewer does
  if (buildNow) {
    Widget w = buildWidget(getParentWidget());
    setBaseWidget(w);
    // make this window a little bigger than usually because of the extra
    // button  
    setSize(SbVec2s(500, 415));
  }
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    This is called when the snapshot button is clicked.  It creates a
//    dialog window for selecting a file and selecting the image type and
//    size.
//
//////////////////////////////////////////////////////////////////////////////
void
ExaminerViewerPlus::createFileDialog (Widget parent)
{
  Widget insert, remove, form1, form2, label1, label2, optionMenu;
  Arg args[12];
  SbVec2s size;
  char buf[10];
  int n;

  // get the size of the window to set the text fields with
  size = getViewportRegion().getWindowSize();
    
  if (fileDialog == NULL) {

    // change the 'OK' button to 'Save'
    n = 0;
    XmString str = XmStringCreateLocalized("Save");
    XtSetArg(args[n], XmNokLabelString, str); n++;

    // create the file dialog
    fileDialog = XmCreateFileSelectionDialog(parent, "Snapshot Dialog",
					 args, n);
    // add the callbacks
    XtAddCallback(fileDialog, XmNokCallback,
		  (XtCallbackProc)ExaminerViewerPlus::fileSelectedCB, this);
    XtAddCallback(fileDialog, XmNcancelCallback,
                (XtCallbackProc)ExaminerViewerPlus::cancelCB, this);
    remove = XmSelectionBoxGetChild(fileDialog, XmDIALOG_HELP_BUTTON);
    XtUnmanageChild(remove);

    // get the separator widget so we can add the additional interface
    insert = XmSelectionBoxGetChild(fileDialog, XmDIALOG_SEPARATOR);
    n = 0;
    // create the form to contain the option menu and text fields
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, insert); n++;
    form1 = XtCreateWidget(NULL,
			   xmFormWidgetClass,
			   XtParent(insert),
			   args, n);

    // create the option menu
    n = 0;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    optionMenu = createOptionMenu(form1, imageTypes, args, n);
    XtManageChild(optionMenu);

    // create the form to contain the text fields
    n = 0;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNleftWidget, optionMenu); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    form2 = XtCreateWidget(NULL,
			   xmFormWidgetClass,
			   form1,
			   args, n);

    // create a label and attach it to the top of the text field form
    str = XmStringCreateLocalized("Image Size:");
    n = 0;
    XtSetArg(args[n], XmNlabelString, str); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    label1 = XtCreateWidget(NULL,
			    xmLabelWidgetClass,
			    form2, args, n);
    XtManageChild(label1);
    XmStringFree(str);

    // a row of widgets will be created, they will be two label,widget pairs.
    // one for the width, one for the height
    // create the width label
    str = XmStringCreateLocalized("Width:");
    n = 0;
    XtSetArg(args[n], XmNlabelString, str); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, label1); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition, 25); n++;
    label2 = XtCreateWidget(NULL,
			    xmLabelWidgetClass,
			    form2, args, n);
    XtManageChild(label2);
    XmStringFree(str);

    // create the width text field
    sprintf(buf, "%d", size[0]);
    n = 0;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, label1); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNleftWidget, label2); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition, 50); n++;
    XtSetArg(args[n], XmNcolumns, 4); n++;
    XtSetArg(args[n], XmNvalue, buf); n++;
    width = XtCreateWidget(NULL,
                           xmTextFieldWidgetClass, 
                           form2, args, n);
    XtManageChild(width);

    // create the height label
    str = XmStringCreateLocalized("Height:");
    n = 0;
    XtSetArg(args[n], XmNlabelString, str); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, label1); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNleftWidget, width); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
    XtSetArg(args[n], XmNrightPosition, 75); n++;
    label2 = XtCreateWidget(NULL,
			    xmLabelWidgetClass,
			    form2, args, n);
    XtManageChild(label2);
    XmStringFree(str);

    // create the height text field
    sprintf(buf, "%d", size[1]);
    n = 0;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNtopWidget, label1); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNleftWidget, label2); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNcolumns, 4); n++;
    XtSetArg(args[n], XmNvalue, buf); n++;
    height = XtCreateWidget(NULL,
			    xmTextFieldWidgetClass, 
			    form2, args, n);
    XtManageChild(height);


    XtManageChild(form2);
    XtManageChild(form1);

    // set the current image type to the first one in the list
    type = imageTypes[0];

  }
  else {
    // create the width text field
    sprintf(buf, "%d", size[0]);
    n = 0;
    XtSetArg(args[n], XmNvalue, buf); n++;
    XtSetValues(width, args, n);

    // create the height text field
    sprintf(buf, "%d", size[1]);
    n = 0;
    XtSetArg(args[n], XmNvalue, buf); n++;
    XtSetValues(height, args, n);
  }

  // display the popup menu
  XtManageChild(fileDialog);

}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Creates and returns an option menu based on the input character
//    pointers.
//
//////////////////////////////////////////////////////////////////////////////
Widget
ExaminerViewerPlus::createOptionMenu (Widget parent, char *options[],
                                      Arg args[], int num)
{
  Widget opt, pulldown;
  int index;

  // create the pulldown menu do add all the push buttons to
  pulldown = XmCreatePulldownMenu(parent, "optionmenu", args, num);
  index = 0;
  // for each item in options create a button and add it to the pulldown menu
  // widget 
  while (options[index] != NULL) {
    Widget item = XtCreateWidget(options[index],
                                 xmPushButtonWidgetClass,
                                 pulldown, NULL, 0);
    // any time a button is activated the callback will be executed
    XtAddCallback(item, XmNactivateCallback,
          (XtCallbackProc)ExaminerViewerPlus::optionCB,
          (XtPointer)this);
    XtManageChild(item);

    index++;
  }

  // create the optionmenu based on the pulldown menu previously created
  XtSetArg(args[num], XmNsubMenuId, pulldown); num++;
  // set the label next to the menu
  XmString str = XmStringCreate("Image Type: ", XmFONTLIST_DEFAULT_TAG);
  XtSetArg(args[num], XmNlabelString, str); num++;
  opt = XmCreateOptionMenu(parent, NULL, args, num);

  return opt;
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Called when the snapshot button on the viewer is clicked
//
//////////////////////////////////////////////////////////////////////////////
void
ExaminerViewerPlus::snapshotCB (Widget wdg,
				ExaminerViewerPlus *v,
				XtPointer wdgData)
{
  v->createFileDialog(SoXt::getShellWidget(v->mgrWidget));
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Called each time the option menu is modified, this way the current
//    output type can be saved.
//
//////////////////////////////////////////////////////////////////////////////
void
ExaminerViewerPlus::optionCB (Widget widget,
			      ExaminerViewerPlus *viewer,
			      XmRowColumnCallbackStruct *data)
{
  Arg args[1];
  int n = 0;

  // get the value label of the widget
  XmString str;
  XtSetArg(args[n], XmNlabelString, &str); n++;
  XtGetValues(widget, args, n);
  // convert the label string to a char * and set it to the viewer's 'type'
  // variable 
  XmStringGetLtoR(str, XmSTRING_DEFAULT_CHARSET, &viewer->type);
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    When a file has been selected and the 'OK' button is clicked this
//    function will be called.  This will check that the file selected is a
//    valid file and can be opened for writing.
//
//////////////////////////////////////////////////////////////////////////////
void
ExaminerViewerPlus::fileSelectedCB (Widget widget,
				    ExaminerViewerPlus *viewer,
				    XmFileSelectionBoxCallbackStruct *selection)

{
  struct stat buf;
  char *filename;
  FILE *fp;
  Widget parent = SoXt::getShellWidget(viewer->mgrWidget);
  
  // get the file
  XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &filename);

  if (!stat(filename, &buf)) {
    int n;
    Arg args[2];
    Widget dialog;
    char str[256];
    strcpy(str, "Overwrite existing ");
    strcat(str, filename);
    strcat(str, " ?");

    strcpy(viewer->existingFile, filename);

    // change the 'OK' button to 'Overwrite'
    n = 0;
    XmString xmstr1 = XmStringCreateLocalized("Overwrite");
    XtSetArg(args[n], XmNokLabelString, xmstr1); n++;
    XmString xmstr2 = XmStringCreateLocalized(str);
    XtSetArg(args[n], XmNmessageString, xmstr2); n++;

    // create the file dialog
    dialog = XmCreateErrorDialog(parent, "error",
				 args, n);
    // add the callbacks
    XtAddCallback(dialog, XmNokCallback,
		  (XtCallbackProc)ExaminerViewerPlus::overwriteCB, viewer);
    XtAddCallback(dialog, XmNcancelCallback,
                  (XtCallbackProc)ExaminerViewerPlus::cancelCB, NULL);
    XtManageChild(dialog);
    XmStringFree(xmstr1);
    XmStringFree(xmstr2);
  }
  else {
    // this is a hack to see if the file is writable, I realize there are
    // more elegant ways, but this is only two lines of code.
    fp = fopen(filename, "wb");
    if (!fp) {
      char str[256];
      strcpy(str, "Cannot write to ");
      strcat(str, filename);
      SoXt::createSimpleErrorDialog(parent,
				    "File Error Dialog", str);

      return;
    }
    fclose(fp);

    XtUnmanageChild(widget);
    viewer->writeImage(filename);
  }

}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Called when the cancel button is clicked
//
//////////////////////////////////////////////////////////////////////////////
void
ExaminerViewerPlus::cancelCB (Widget widget, XtPointer client_data,
                              XmFileSelectionBoxCallbackStruct *selection)

{
  XtUnmanageChild(widget);  /* undisplay widget */    
}

void
ExaminerViewerPlus::overwriteCB (Widget widget,
				 ExaminerViewerPlus *viewer,
				 XmFileSelectionBoxCallbackStruct *selection)

{
  XtUnmanageChild(widget);  /* undisplay widget */
  XtUnmanageChild(viewer->fileDialog);
  viewer->writeImage(viewer->existingFile);
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Writes a scene graph to file using the SoWriteAction action.
//
//////////////////////////////////////////////////////////////////////////////
void
writeIV (SoNode *root, const char *filename, SbBool binary)
{
  SoWriteAction action;

  root->ref();
  action.getOutput()->openFile(filename);
  action.getOutput()->setBinary(binary);
  action.apply(root);
  action.getOutput()->closeFile();
}

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Writes an image  to file in binary PPM format.  PPM stores its
//    images with the top left corner being the first values in the file.
//    However, the bytes are stored with the  bottom left corner
//    of the image indexed first.  This means the image cannot be written out
//    with one fwrite() call. 
//
//////////////////////////////////////////////////////////////////////////////
int
writePPM (const char *file, int width, int height,
	  int numComponents, unsigned char *bytes)
{
  int num, i;
  FILE *fptr;

  fptr = fopen(file, "w");

  if (!fptr)
    return 0;

  fprintf(fptr, "P6\n");
  fprintf(fptr, "%d %d\n", width, height);
  fprintf(fptr, "255\n");

  num = width*numComponents;
  for (i = height - 1; i >= 0; i--) {
    unsigned char *ptr;
    int index;

    index = i*width*numComponents;
    ptr   = bytes + index*sizeof(unsigned char);
    fwrite((void *)ptr, sizeof(unsigned char), num, fptr);

  }

  fclose(fptr);

  return 1;
}

#ifdef HAVE_JPEGLIB
// this is a hack to keep the compiler complaining if the JPEG library
// already has this defined
#ifdef HAVE_STDLIB_H
#undef HAVE_STDLIB_H
#endif
extern "C" {
#include <jpeglib.h>
}

int
writeJPEG (const char *file, int quality, int width, int height,
	   int numComponents, unsigned char *bytes)
{
  FILE *outfile;
  JSAMPROW row[1];
  int row_stride;
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr       jerr;
  
  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_compress(&cinfo);

  if (file == NULL)
    outfile = stdout;
  else {
    if ((outfile = fopen(file, "wb")) == NULL)
      return 0;
  }
  jpeg_stdio_dest(&cinfo, outfile);

  /* image width and height, in pixels */
  cinfo.image_width = width;           
  cinfo.image_height = height;
  /* # of color components per pixel */
  cinfo.input_components = numComponents; 
  if (numComponents == 1)
    cinfo.in_color_space = JCS_GRAYSCALE;
  else
    /* colorspace of input image */
    cinfo.in_color_space = JCS_RGB;             

  jpeg_set_defaults(&cinfo);
  jpeg_set_quality(&cinfo, quality, TRUE);
  jpeg_start_compress(&cinfo, TRUE);

  row_stride = cinfo.image_width * cinfo.input_components;
  while (cinfo.next_scanline < cinfo.image_height) {
    row[0] = bytes + (cinfo.image_height - cinfo.next_scanline - 1)
             * row_stride;
    jpeg_write_scanlines(&cinfo, row, 1);
  }

  jpeg_finish_compress(&cinfo);
  jpeg_destroy_compress(&cinfo);

  fclose(outfile);

  return 1;
}
#endif

#ifdef HAVE_PNGLIB
#include <png.h>
/*
  Copy and pasted from System in Motion's 'simage' library for Coin.
  (http://www.coin3d.org/), with some slight modifications.
*/
int
writePNG (const char *filename, int width, int height,
	  int numcomponents, unsigned char *bytes)
{
  FILE * fp;
  png_structp png_ptr;
  png_infop info_ptr;
  int colortype;
  int y, bytesperrow;
#ifdef PNG_TEXT_SUPPORTED
  png_text text_ptr[3];
#endif  

  /* open the file */
  if (filename == NULL)
    fp = stdout;
  else {
    fp = fopen(filename, "wb");
    if (fp == NULL)
      return 0;
  }
  
  /* Create and initialize the png_struct with the desired error handler
   * functions.  If you want to use the default stderr and longjump method,
   * you can supply NULL for the last three parameters.  We also check that
   * the library version is compatible with the one used at compile time,
   * in case we are using dynamically linked libraries.  REQUIRED.
   */
  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
				    NULL, NULL, NULL);

  if (png_ptr == NULL) {
    fclose(fp);
    return 0;
  }
  /* Allocate/initialize the image information data.  REQUIRED */
  info_ptr = png_create_info_struct(png_ptr);
  if (info_ptr == NULL) {
    fclose(fp);
    png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
    return 0;
  }

  /* Set error handling.  REQUIRED if you aren't supplying your own
   * error hadnling functions in the png_create_write_struct() call.
   */
  if (setjmp(png_ptr->jmpbuf)) {
    /* If we get here, we had a problem reading the file */
    fclose(fp);
    png_destroy_write_struct(&png_ptr,  (png_infopp)NULL);
    return 0;
  }
  /* set up the output control if you are using standard C streams */
  png_init_io(png_ptr, fp);
  
  /* Set the image information here.  Width and height are up to 2^31,
   * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
   * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
   * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
   * or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or
   * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
   * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
   */

  switch (numcomponents) {
  case 1:
    colortype = PNG_COLOR_TYPE_GRAY;
    break;
  case 2:
    colortype = PNG_COLOR_TYPE_GRAY_ALPHA;
    break;
  case 3:
    colortype = PNG_COLOR_TYPE_RGB;
    break;
  default:
  case 4:
    colortype = PNG_COLOR_TYPE_RGB_ALPHA;
    break;
  }
  png_set_IHDR(png_ptr, info_ptr, width, height, 8, colortype,
	       PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

  /* Optional gamma chunk is strongly suggested if you have any guess
   * as to the correct gamma of the image. */
  /* png_set_gAMA(png_ptr, info_ptr, gamma); */

#ifdef PNG_TEXT_SUPPORTED
  /* Optionally write comments into the image */
  text_ptr[0].key = "Title";
  text_ptr[0].text = (char*)filename;
  text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
  text_ptr[1].key = "Author";
  text_ptr[1].text = "Josh Grant";
  text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE;
  text_ptr[2].key = "Description";
  text_ptr[2].text = "Image saved using ExaminerViewerPlus.";
  text_ptr[2].compression = PNG_TEXT_COMPRESSION_zTXt;
  png_set_text(png_ptr, info_ptr, text_ptr, 3);
#endif /* PNG_TEXT_SUPPORTED */

  /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */

  /* Write the file header information.  REQUIRED */
  png_write_info(png_ptr, info_ptr);

  /* Once we write out the header, the compression type on the text
   * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
   * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
   * at the end.
   */

  /* set up the transformations you want.  Note that these are
   * all optional.  Only call them if you want them. */

  /* invert monocrome pixels */
  /* png_set_invert(png_ptr); */

  /* Shift the pixels up to a legal bit depth and fill in
   * as appropriate to correctly scale the image */
  /* png_set_shift(png_ptr, &sig_bit);*/

  /* pack pixels into bytes */
  /* png_set_packing(png_ptr); */
  /* swap location of alpha bytes from ARGB to RGBA */
  /* png_set_swap_alpha(png_ptr); */

  /* Get rid of filler (OR ALPHA) bytes, pack XRGB/RGBX/ARGB/RGBA into
   * RGB (4 channels -> 3 channels). The second parameter is not used. */
  /* png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE); */

  /* flip BGR pixels to RGB */
  /* png_set_bgr(png_ptr); */

  /* swap bytes of 16-bit files to most significant byte first */
  /* png_set_swap(png_ptr); */

  /* swap bits of 1, 2, 4 bit packed pixel formats */
  /* png_set_packswap(png_ptr); */


  /* The easiest way to write the image (you may have a different memory
   * layout, however, so choose what fits your needs best).  You need to
   * use the first method if you aren't handling interlacing yourself.
   */

  /* If you are only writing one row at a time, this works */
  
  bytesperrow = width * numcomponents;

  for (y = 0; y < height; y++) {
    png_write_row(png_ptr, (png_bytep) bytes + bytesperrow * (height-y-1));
  }
  
  /* You can write optional chunks like tEXt, zTXt, and tIME at the end
   * as well.
   */
  
  /* It is REQUIRED to call this to finish writing the rest of the file */
  png_write_end(png_ptr, info_ptr);

  /* if you allocated any text comments, free them here */

  /* clean up after the write, and free any memory allocated */
  png_destroy_write_struct(&png_ptr, (png_infopp)NULL);

  /* close the file */
  fclose(fp);

  /* that's it */
  return 1;
}
#endif

//////////////////////////////////////////////////////////////////////////////
//
//  Description:
//    Writes the current scene to file in either Inventor format or an
//    image.  The function assumes that the file being written to exists
//    or can be created.
//
//////////////////////////////////////////////////////////////////////////////
void
ExaminerViewerPlus::writeImage (const char *filename)
{
  // get the root node from the scene manager.  This node contains the camera
  // and light
  SoNode *root = getSceneManager()->getSceneGraph();

  // write the scene file
  if (!strcmp(type, "iv ASCII")) {
    writeIV(root, filename, FALSE);
    return;
  }
  else if (!strcmp(type, "iv Binary")) {
    writeIV(root, filename, TRUE);
    return;
  }

  // get the output size
  SbVec2s size;
  size[0] = atoi(XmTextFieldGetString(width));
  size[1] = atoi(XmTextFieldGetString(height));

  /*
  SbVec2s viewerSize = getSize();
  if (size[0] == viewerSize[0] &&
      size[1] == viewerSize[1]) {
    render();
    cout << "requested dimensions equal to window, grabbing glbytes" << endl;
  }
  */
  
  // Generate the viewport
  size = getViewportRegion().getWindowSize();
  SbViewportRegion myViewPort(size);

  // Create the renederer
  SoOffscreenRenderer *myRenderer = new SoOffscreenRenderer(myViewPort);

  SoGLRenderAction myglra(myViewPort);
  //myglra.setSmoothing(TRUE);
  //myglra.setNumPasses(numPasses);
  myRenderer->setGLRenderAction(&myglra);
  if (!myRenderer->render(root)) {
    fprintf(stderr, "Can't render the scene\n");
    delete myRenderer;
    return;
  } else {
    unsigned char *bytes = myRenderer->getBuffer();

    if (!strcmp(type, "ppm"))
      writePPM(filename, size[0], size[1], 3, bytes);
#ifdef HAVE_JPEGLIB
    else if (!strcmp(type, "jpg"))
      writeJPEG(filename, 90, size[0], size[1], 3, bytes);
#endif
#ifdef HAVE_PNGLIB
    else if (!strcmp(type, "png"))
      writePNG(filename, size[0], size[1], 3, bytes);
#endif
    else {
      FILE *fp = fopen(filename, "wb");

      if (!strcmp(type, "rgb"))
        myRenderer->writeToRGB(fp);
      else if (!strcmp(type, "ps"))
        myRenderer->writeToPostScript(fp);

      fclose (fp);
    }

  }

  delete myRenderer;

}

