/***************************************************************************
 *                                                                         *
 *   This is a QGLWidget displaying an image or portion of an image in a   *
 *   box.                                                                  *
 *                                                                         *
 *   Author:    Graeme van der Vlugt                                       *
 *   Copyright: Imetric 3D GmbH                                            *
 *   Year:      2004                                                       *
 *                                                                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *   for detail see the LICENCE text file.                                 *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"
#ifndef _PreComp_
# include <cmath>
# include <QMessageBox>
#endif

#if defined(__MINGW32__)
# include <GL/gl.h>
# include <GL/glext.h>
#endif

#include "GLImageBox.h"

using namespace ImageGui;

#if defined(Q_CC_MSVC)
#pragma warning(disable:4305) // init: truncation from const double to float
#endif

/* TRANSLATOR ImageGui::GLImageBox */

// Constructor
GLImageBox::GLImageBox(QWidget * parent, const QGLWidget * shareWidget, Qt::WFlags f)
    : QGLWidget(parent, shareWidget, f)
{
    // uses default display format for the OpenGL rendering context
    // (double buffering is enabled)

    // enable mouse tracking when moving even if no buttons are pressed
    setMouseTracking(true);

    // initialise variables
    _x0 = 0;
    _y0 = 0;
    _zoomFactor = 1.0;
    _base_x0 = 0;
    _base_y0 = 0;
    _pColorMap = 0;
    _numMapEntries = 0;
}


// Destructor
GLImageBox::~GLImageBox()
{
    try
    {
        delete [] _pColorMap;
    }
    catch(...) {}
}

// Set up the OpenGL rendering state
void GLImageBox::initializeGL()
{
  qglClearColor( Qt::black );		// Let OpenGL clear to black
}


// Update the viewport
void GLImageBox::resizeGL( int w, int h )
{
    glViewport( 0, 0, (GLint)w, (GLint)h );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluOrtho2D(0, width() - 1, height() - 1, 0);
    glMatrixMode(GL_MODELVIEW);
}

// Redraw (current image)
void GLImageBox::redraw()
{
    glDraw();
}


// Paint the box
void GLImageBox::paintGL()
{
    // clear background (in back buffer)
    glDrawBuffer(GL_BACK);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw the image
    drawImage();

    // Emit a signal for owners to draw any graphics that is needed.
    if (_image.hasValidData() == true)
        drawGraphics();

    // flush the OpenGL graphical pipeline
    glFinish();

    // Double buffering is used so we need to swap the buffers
    // There is no need to explicitly call this function because it is 
    // done automatically after each widget repaint, i.e. each time after paintGL() has been executed
    // swapBuffers();
}

// Draw the image
void GLImageBox::drawImage()
{
    if (_image.hasValidData() == false)
        return;

    // Gets the size of the diplayed image area using the current display settings 
    // (in units of image pixels)
    int dx, dy;
    getDisplayedImageAreaSize(dx, dy);

   // Draw the visible image region with the correct position and zoom
    if ((dx > 0) && (dy > 0))
    {
        // Get top left image pixel to display
        int tlx = std::max<int>(0, _x0);
        int tly = std::max<int>(0, _y0);

        // Get pointer to first pixel in source image rectangle
        unsigned char* pPix = (unsigned char *)(_image.getPixelDataPtr());
        pPix += (unsigned long)(_image.getNumBytesPerPixel()) * (tly * _image.getWidth() + tlx);

        // Draw in the back buffer, using the following parameters
        glDrawBuffer(GL_BACK);
        glPixelStorei(GL_UNPACK_ROW_LENGTH, _image.getWidth()); // defines number of pixels in a row
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // defines byte alignment of rows
        glPixelZoom(_zoomFactor, -_zoomFactor); // defines the zoom factors to draw at

        // set current raster position to coincide with top left image pixel to display
        // the first pixel is always displayed in full when zoomed in
         // round to nearest widget pixel that coincides with top left corner of top left image pixel to display
        int xx = (int)floor(ICToWC_X(tlx - 0.5) + 0.5);
        int yy = (int)floor(ICToWC_Y(tly - 0.5) + 0.5);
        glRasterPos2f(xx, yy);

        // Compute scale to stretch number of significant bits to full range
        // e.g. stretch 12 significant bits to 16-bit range: 0-4095 -> 0-65535, therefore scale = 65535/4095
        double scale = (pow(2.0, _image.getNumBitsPerSample()) - 1.0) / (pow(2.0, _image.getNumSigBitsPerSample()) - 1.0);
        glPixelTransferf(GL_RED_SCALE, (float)scale);
        glPixelTransferf(GL_GREEN_SCALE, (float)scale);
        glPixelTransferf(GL_BLUE_SCALE, (float)scale);

        // Load the color map if present
        if (_pColorMap != 0)
        {
            glPixelTransferf(GL_MAP_COLOR, 1.0);
            glPixelMapfv(GL_PIXEL_MAP_R_TO_R, _numMapEntries, _pColorMap);
            glPixelMapfv(GL_PIXEL_MAP_G_TO_G, _numMapEntries, _pColorMap + _numMapEntries);
            glPixelMapfv(GL_PIXEL_MAP_B_TO_B, _numMapEntries, _pColorMap + _numMapEntries * 2);
            glPixelMapfv(GL_PIXEL_MAP_A_TO_A, _numMapEntries, _pColorMap + _numMapEntries * 3);
        }
        else
        {
            glPixelTransferf(GL_MAP_COLOR, 0.0);
            float zero = 0.0;
            float one = 1.0;
            glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 1, &zero);
            glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 1, &zero);
            glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 1, &zero);
            glPixelMapfv(GL_PIXEL_MAP_A_TO_A, 1, &one);
        }

        // Get the pixel format
        GLenum pixFormat;
        GLenum pixType;
        getPixFormat(pixFormat, pixType);

        // Draw the defined source rectangle
        glDrawPixels(dx, dy, pixFormat, pixType, (GLvoid *)pPix);
        glFlush();
    }
}

// Gets the size of the diplayed image area using the current display settings 
// (in units of image pixels)
void GLImageBox::getDisplayedImageAreaSize(int &dx, int &dy)
{
    if (_image.hasValidData() == false)
    {
        dx = 0;
        dy = 0;
    }
    else
    {
        // Make sure drawing position and zoom factor are valid
        limitCurrPos();
        limitZoomFactor();

        // Image coordinates of top left widget pixel = (_x0, _y0)
        // Get image coordinates of bottom right widget pixel
        int brx = (int)ceil(WCToIC_X(width() - 1));
        int bry = (int)ceil(WCToIC_Y(height() - 1));

        // Find the outer coordinates of the displayed image area
        int itlx = std::max<int>(_x0, 0);
        int itly = std::max<int>(_y0, 0);
        int ibrx = std::min<int>(brx, (int)(_image.getWidth()) - 1);
        int ibry = std::min<int>(bry, (int)(_image.getHeight()) - 1);
        if ((itlx >= (int)(_image.getWidth())) || 
            (itly >= (int)(_image.getHeight())) || 
            (ibrx < 0) || 
            (ibry < 0))
        {
            dx = 0;
            dy = 0;
        }
        dx = ibrx - itlx + 1;
        dy = ibry - itly + 1;
    }
}

// Gets the value of an image sample at the given image pixel position
// Returns 0 for valid value or -1 if coordinates or sample index are out of range or 
// if there is no image data
int GLImageBox::getImageSample(int x, int y, unsigned short sampleIndex, double &value)
{
    return (_image.getSample(x, y, sampleIndex, value));
}

// Gets the number of samples per pixel for the image
unsigned short GLImageBox::getImageNumSamplesPerPix()
{
    return (_image.getNumSamples());
}

// Gets the format (color space format) of the image
int GLImageBox::getImageFormat()
{
    return (_image.getFormat());
}


// Get the OpenGL pixel format and pixel type from the image properties
void GLImageBox::getPixFormat(GLenum &pixFormat, GLenum &pixType)
{
    switch(_image.getFormat())
    {
        case IB_CF_GREY8:
            pixFormat = GL_LUMINANCE;
            pixType = GL_UNSIGNED_BYTE;
            break;
        case IB_CF_GREY16:
            pixFormat = GL_LUMINANCE;
            pixType = GL_UNSIGNED_SHORT;
            break;
        case IB_CF_GREY32:
            pixFormat = GL_LUMINANCE;
            pixType = GL_UNSIGNED_INT;
            break;
        case IB_CF_RGB24:
            pixFormat = GL_RGB;
            pixType = GL_UNSIGNED_BYTE;
            break;
#ifndef FC_OS_CYGWIN
        case IB_CF_BGR24:
            pixFormat = GL_BGR_EXT;
            pixType = GL_UNSIGNED_BYTE;
            break;
        case IB_CF_RGB48:
            pixFormat = GL_RGB;
            pixType = GL_UNSIGNED_SHORT;
            break;
        case IB_CF_BGR48:
            pixFormat = GL_BGR_EXT;
            pixType = GL_UNSIGNED_SHORT;
            break;
#endif
        case IB_CF_RGBA32:
            pixFormat = GL_RGBA;
            pixType = GL_UNSIGNED_BYTE;
            break;
        case IB_CF_RGBA64:
            pixFormat = GL_RGBA;
            pixType = GL_UNSIGNED_SHORT;
            break;
#ifndef FC_OS_CYGWIN
        case IB_CF_BGRA32:
            pixFormat = GL_BGRA_EXT;
            pixType = GL_UNSIGNED_BYTE;
            break;
        case IB_CF_BGRA64:
            pixFormat = GL_BGRA_EXT;
            pixType = GL_UNSIGNED_SHORT;
            break;
#endif
        default:
            // Should never happen
            pixFormat = GL_LUMINANCE;
            pixType = GL_UNSIGNED_BYTE;
            QMessageBox::warning((QWidget *)this, tr("Image pixel format"),
                tr("Undefined type of colour space for image viewing"));
            return;
    }
}

// Limits the current position (centre of top left image pixel)
// Currently we don't limit it!
void GLImageBox::limitCurrPos()
{
    if (_image.hasValidData() == false)
        return;

    /*
    if (_x0 < 0)
        _x0 = 0;
    else if (_x0 >= (int)(_image.getWidth()))
        _x0 = _image.getWidth() - 1;
    if (_y0 < 0)
        _y0 = 0;
    else if (_y0 >= (int)(_image.getHeight()))
        _y0 = _image.getHeight() - 1;
    */
}

// Limits the current zoom factor from 1:64 to 64:1
void GLImageBox::limitZoomFactor()
{
    if (_zoomFactor > 64.0)
        _zoomFactor = 64.0;
    else if (_zoomFactor < (1.0 / 64.0))
        _zoomFactor = 1.0 / 64.0;
}

// Set the current position (centre of top left image pixel coordinates)
// This function does not redraw (call redraw afterwards)
void GLImageBox::setCurrPos(int x0, int y0)
{
    _x0 = x0;
    _y0 = y0;
    limitCurrPos();
}

// Fixes a base position at the current position
void GLImageBox::fixBasePosCurr()
{
    if (_image.hasValidData() == false)
    {
        _base_x0 = 0;
        _base_y0 = 0;
    }
    else
    {
        _base_x0 = _x0;
        _base_y0 = _y0;
    }
}

// Set the current zoom factor
// Option to centre the zoom at a given image point or not
// This function does not redraw (call redraw afterwards)
void GLImageBox::setZoomFactor(double zoomFactor, bool useCentrePt, int ICx, int ICy)
{
    if ((useCentrePt == false) || (_image.hasValidData() == false))
    {
        _zoomFactor = zoomFactor;
        limitZoomFactor();
    }
    else
    {
        // Set new zoom factor
        _zoomFactor = zoomFactor;
        limitZoomFactor();

        // get centre position of widget in image coordinates
        int ix, iy;
        getCentrePoint(ix, iy);

        // try to shift the current position so that defined centre point is in the middle of the widget
        // (this can be modified by the limitCurrPos function)
        setCurrPos(_x0 - ix + ICx, _y0 - iy + ICy);
    }
}

// Stretch or shrink the image to fit the view (although the zoom factor is limited so a 
// very small or very big image may not fit completely (depending on the size of the view)
// This function redraws
void GLImageBox::stretchToFit()
{
    if (_image.hasValidData() == false)
        return;

    setToFit();
    glDraw();
}

// Sets the settings needed to fit the image into the view (although the zoom factor is limited so a 
// very small or very big image may not fit completely (depending on the size of the view)
// This function does not redraw (call redraw afterwards)
void GLImageBox::setToFit()
{
    if (_image.hasValidData() == false)
        return;

    // Compute ideal zoom factor to fit the image
    double zoomX = (double)width() / (double)(_image.getWidth());
    double zoomY = (double)height() / (double)(_image.getHeight());
    if (zoomX > zoomY)
        _zoomFactor = zoomY;
    else
        _zoomFactor = zoomX;
    limitZoomFactor();

    // set current position to top left image pixel
    setCurrPos(0, 0);
}

// Sets the normal viewing position and zoom = 1
// If the image is smaller than the widget then the image is centred 
// otherwise we view the top left part of the image
// This function does not redraw (call redraw afterwards)
void GLImageBox::setNormal()
{
    if (_image.hasValidData() == false)
        return;

    if (((int)(_image.getWidth()) < width()) && ((int)(_image.getHeight()) < height()))
    {
        setZoomFactor(1.0, true, _image.getWidth() / 2, _image.getHeight() / 2);
    }
    else
    {
        _zoomFactor = 1;
        setCurrPos(0, 0);
    }
}

// Gets the image coordinates of the centre point of the widget 
void GLImageBox::getCentrePoint(int &ICx, int &ICy)
{
    ICx = (int)floor(WCToIC_X((double)(width() - 1) / 2.0) + 0.5);
    ICy = (int)floor(WCToIC_Y((double)(height() - 1) / 2.0) + 0.5);
}

// Moves the image by a relative amount (in widget pixel units) from the base position
// First use fixBasePosCurr() to fix the base position at a position
void GLImageBox::relMoveWC(int WCdx, int WCdy)
{
    double ICdx = WCdx / _zoomFactor;
    double ICdy = WCdy / _zoomFactor;
    setCurrPos(_base_x0 - (int)floor(ICdx + 0.5), _base_y0 - (int)floor(ICdy + 0.5));
    glDraw();
}

// Computes an image x-coordinate from the widget x-coordinate
// Note: (_x0,_y0) is the centre of the image pixel displayed at the top left of the widget
// therefore (_x0 - 0.5, _y0 - 0.5) is the top left coordinate of this pixel which will 
// theoretically coincide with widget coordinate (-0.5,-0.5)
// Zoom = 4:    Widget(0,0) = Image(_x0 - 0.375,_y0 - 0.375)
// Zoom = 2:    Widget(0,0) = Image(_x0 - 0.250,_y0 - 0.250)
// Zoom = 1:    Widget(0,0) = Image(_x0,_y0)
// Zoom = 0.5:  Widget(0,0) = Image(_x0 + 0.500,_y0 + 0.500)
// Zoom = 0.25: Widget(0,0) = Image(_x0 + 1.500,_y0 + 1.500)
double GLImageBox::WCToIC_X(double WidgetX)
{
    return ((double)_x0 - 0.5 + (WidgetX + 0.5) / _zoomFactor);
}

// Computes an image y-coordinate from the widget y-coordinate
// Note: (_x0,_y0) is the centre of the image pixel displayed at the top left of the widget
// therefore (_x0 - 0.5, _y0 - 0.5) is the top left coordinate of this pixel which will 
// theoretically coincide with widget coordinate (-0.5,-0.5)
// Zoom = 4:    Widget(0,0) = Image(_x0 - 0.375,_y0 - 0.375)
// Zoom = 2:    Widget(0,0) = Image(_x0 - 0.250,_y0 - 0.250)
// Zoom = 1:    Widget(0,0) = Image(_x0,_y0)
// Zoom = 0.5:  Widget(0,0) = Image(_x0 + 0.500,_y0 + 0.500)
// Zoom = 0.25: Widget(0,0) = Image(_x0 + 1.500,_y0 + 1.500)
double GLImageBox::WCToIC_Y(double WidgetY)
{
    return ((double)_y0 - 0.5 + (WidgetY + 0.5) / _zoomFactor);
}

// Computes a widget x-coordinate from an image x-coordinate
// Note: (_x0,_y0) is the centre of the image pixel displayed at the top left of the widget
// therefore (_x0 - 0.5, _y0 - 0.5) is the top left coordinate of this pixel which will 
// theoretically coincide with widget coordinate (-0.5,-0.5)
// Zoom = 4:    Widget(0,0) = Image(_x0 - 0.375,_y0 - 0.375)
// Zoom = 2:    Widget(0,0) = Image(_x0 - 0.250,_y0 - 0.250)
// Zoom = 1:    Widget(0,0) = Image(_x0,_y0)
// Zoom = 0.5:  Widget(0,0) = Image(_x0 + 0.500,_y0 + 0.500)
// Zoom = 0.25: Widget(0,0) = Image(_x0 + 1.500,_y0 + 1.500)
double GLImageBox::ICToWC_X(double ImageX)
{
    return ((ImageX - (double)_x0 + 0.5) * _zoomFactor - 0.5);
}

// Computes a widget y-coordinate from an image y-coordinate
// Note: (_x0,_y0) is the centre of the image pixel displayed at the top left of the widget
// therefore (_x0 - 0.5, _y0 - 0.5) is the top left coordinate of this pixel which will 
// theoretically coincide with widget coordinate (-0.5,-0.5)
// Zoom = 4:    Widget(0,0) = Image(_x0 - 0.375,_y0 - 0.375)
// Zoom = 2:    Widget(0,0) = Image(_x0 - 0.250,_y0 - 0.250)
// Zoom = 1:    Widget(0,0) = Image(_x0,_y0)
// Zoom = 0.5:  Widget(0,0) = Image(_x0 + 0.500,_y0 + 0.500)
// Zoom = 0.25: Widget(0,0) = Image(_x0 + 1.500,_y0 + 1.500)
double GLImageBox::ICToWC_Y(double ImageY)
{
    return ((ImageY - (double)_y0 + 0.5) * _zoomFactor - 0.5);
}


// Clears the image data
void GLImageBox::clearImage()
{
    _image.clear();
    resetDisplay();
}

// Load image by copying the pixel data
// The image object will take ownership of the copied pixel data
// (the source image is still controlled by the caller)
// If numSigBitsPerSample = 0 then the full range is assumed to be significant
// displayMode ... controls the initial display of the image, one of:
//                      IV_DISPLAY_NOCHANGE  ... no change to view settings when displaying a new image
//                      IV_DISPLAY_FITIMAGE  ... fit-image when displaying a new image (other settings remain the same)
//                      IV_DISPLAY_RESET     ... reset settings when displaying a new image (image will be displayed at 1:1 scale with no color map)
// This function does not redraw (call redraw afterwards)
// Returns:
//		 0 for OK
//		-1 for invalid color format
//		-2 for memory allocation error
int GLImageBox::createImageCopy(void* pSrcPixelData, unsigned long width, unsigned long height, int format, unsigned short numSigBitsPerSample, int displayMode)
{
    // Copy image
    int ret = _image.createCopy(pSrcPixelData, width, height, format, numSigBitsPerSample);

    // Set display settings depending on mode
    if (displayMode == IV_DISPLAY_RESET)
    {
        // reset drawing settings (position, scale, colour mapping) if requested
        resetDisplay();
    }
    else if (displayMode == IV_DISPLAY_FITIMAGE)
    {
        // compute stretch to fit settings
        setToFit();
    }
    else // if (displayMode == IV_DISPLAY_NOCHANGE)
    {
        // use same settings
        limitCurrPos();
        limitZoomFactor();
    }
    return ret;
}

// Make the image object point to another image source
// If takeOwnership is false then:
//      This object will not own (control) or copy the pixel data
//      (the source image is still controlled by the caller)
// Else if takeOwnership is true then:
//      This object will take ownership (control) of the pixel data
//      (the source image is not (should not be) controlled by the caller anymore)
//      In this case the memory must have been allocated with the new operator (because this class will use the delete operator)
// If numSigBitsPerSample = 0 then the full range is assumed to be significant
// displayMode ... controls the initial display of the image, one of:
//                      IV_DISPLAY_NOCHANGE  ... no change to view settings when displaying a new image
//                      IV_DISPLAY_FITIMAGE  ... fit-image when displaying a new image (other settings remain the same)
//                      IV_DISPLAY_RESET     ... reset settings when displaying a new image (image will be displayed at 1:1 scale with no color map)
// This function does not redraw (call redraw afterwards)
// Returns:
//		 0 for OK
//		-1 for invalid color format
int GLImageBox::pointImageTo(void* pSrcPixelData, unsigned long width, unsigned long height, int format, unsigned short numSigBitsPerSample, bool takeOwnership, int displayMode)
{
    // Point to image
    int ret = _image.pointTo(pSrcPixelData, width, height, format, numSigBitsPerSample, takeOwnership);

    // Set display settings depending on mode
    if (displayMode == IV_DISPLAY_RESET)
    {
        // reset drawing settings (position, scale, colour mapping) if requested
        resetDisplay();
    }
    else if (displayMode == IV_DISPLAY_FITIMAGE)
    {
        // compute stretch to fit settings
        setToFit();
    }
    else // if (displayMode == IV_DISPLAY_NOCHANGE)
    {
        // use same settings
        limitCurrPos();
        limitZoomFactor();
    }
    return ret;
}

// Reset display settings
void GLImageBox::resetDisplay()
{
    clearColorMap();
    setNormal(); // re-draws as well
}

// Clears the color map
void GLImageBox::clearColorMap()
{
    delete [] _pColorMap;
    _pColorMap = 0;
    _numMapEntries = 0;
}

// Calculate the number of color map entries to use
int GLImageBox::calcNumColorMapEntries()
{
    // Get the maximum number of map entries that the system supports
    // Get the number of bits per sample for the image if it exists and compute the number of pixel values
    // Return the fewer amount of entries
    GLint maxMapEntries;
    glGetIntegerv(GL_MAX_PIXEL_MAP_TABLE, &maxMapEntries);
    int NumEntries = maxMapEntries;
    if (_image.hasValidData() == true)
        NumEntries = (int)std::min<double>(pow(2.0, (double)(_image.getNumSigBitsPerSample())), (double)maxMapEntries);
    return NumEntries;
}

// Creates a color map (All red entries come first, then green, then blue, then alpha)
// returns 0 for OK, -1 for memory allocation error
// numRequestedEntries ... requested number of map entries (used if not greater than maximum possible or number of intensity values)
// Initialise ... flag to initialise the map to a linear scale or not
int GLImageBox::createColorMap(int numEntriesReq, bool Initialise)
{
    // Get the number of map entries to use
    int maxNumEntries = calcNumColorMapEntries();
    int numEntries;
    if (numEntriesReq <= 0)
        numEntries = maxNumEntries;
    else
        numEntries = std::min<int>(numEntriesReq, maxNumEntries);

    // Clear and re-create the color map if it's not the desired size
    if (numEntries != _numMapEntries)
    {
        clearColorMap();
        _numMapEntries = numEntries;

        // Create the color map (RGBA)
        try
        {
            _pColorMap = new float[4 * _numMapEntries];
        }
        catch(...)
        {
            clearColorMap();
            return -1;
        }
    }

    // Initialise the color map if requested
    // (All red entries come first, then green, then blue, then alpha)
    if (Initialise == true)
    {
        // For each RGB channel
        int arrayIndex = 0;
        for (int chan = 0; chan < 3; chan++)
        {
            for (int in = 0; in < _numMapEntries; in++)
            {
                _pColorMap[arrayIndex] = (float)in / (float)(_numMapEntries - 1);
                arrayIndex++;
            }
        }
        // For alpha channel
        for (int in = 0; in < _numMapEntries; in++)
        {
            _pColorMap[arrayIndex] = 1.0;
            arrayIndex++;
        }
    }

    return 0;
}

// Sets a color map RGBA value
// (All red entries come first, then green, then blue, then alpha)
// index ... index of color map RGBA entry
// red ... intensity value for this red entry (range 0 to 1)
// green ... intensity value for this green entry (range 0 to 1)
// blue ... intensity value for this blue entry (range 0 to 1)
// alpha ... value for this alpha entry (range 0 to 1)
int GLImageBox::setColorMapRGBAValue(int index, float red, float green, float blue, float alpha)
{
    if ((index < 0) || (index >= _numMapEntries) || 
        (red < 0.0) || (red > 1.0) || 
        (green < 0.0) || (green > 1.0) || 
        (blue < 0.0) || (blue > 1.0) || 
        (alpha < 0.0) || (alpha > 1.0))
        return -1;

    _pColorMap[index] = red;
    _pColorMap[_numMapEntries + index] = green;
    _pColorMap[_numMapEntries * 2 + index] = blue;
    _pColorMap[_numMapEntries * 3 + index] = alpha;
    return 0;
}

// Sets a color map red value
// (All red entries come first, then green, then blue, then alpha)
// index ... index of color map red entry
// value ... intensity value for this red entry (range 0 to 1)
int GLImageBox::setColorMapRedValue(int index, float value)
{
    if ((index < 0) || (index >= _numMapEntries) || (value < 0.0) || (value > 1.0))
        return -1;

    _pColorMap[index] = value;
    return 0;
}

// Sets a color map green value
// (All red entries come first, then green, then blue, then alpha)
// index ... index of color map green entry
// value ... intensity value for this green entry (range 0 to 1)
int GLImageBox::setColorMapGreenValue(int index, float value)
{
    if ((index < 0) || (index >= _numMapEntries) || (value < 0.0) || (value > 1.0))
        return -1;

    _pColorMap[_numMapEntries + index] = value;
    return 0;
}

// Sets a color map blue value
// (All red entries come first, then green, then blue, then alpha)
// index ... index of color map blue entry
// value ... intensity value for this blue entry (range 0 to 1)
int GLImageBox::setColorMapBlueValue(int index, float value)
{
    if ((index < 0) || (index >= _numMapEntries) || (value < 0.0) || (value > 1.0))
        return -1;

    _pColorMap[_numMapEntries * 2 + index] = value;
    return 0;
}

// Sets a color map alpha value
// (All red entries come first, then green, then blue, then alpha)
// index ... index of color map alpha entry
// value ... value for this alpha entry (range 0 to 1)
int GLImageBox::setColorMapAlphaValue(int index, float value)
{
    if ((index < 0) || (index >= _numMapEntries) || (value < 0.0) || (value > 1.0))
        return -1;

    _pColorMap[_numMapEntries * 3 + index] = value;
    return 0;
}

// Helper function to convert a pixel's value (of a sample) to the color map index (i.e. the map index that will be used for that pixel value)
unsigned int GLImageBox::pixValToMapIndex(double PixVal)
{
    if (_pColorMap != NULL)
    {
        double MaxVal = pow(2.0, _image.getNumBitsPerSample()) - 1.0;
        double Scale = (pow(2.0, _image.getNumBitsPerSample()) - 1.0) / (pow(2.0, _image.getNumSigBitsPerSample()) - 1.0);
        double PixVal01 = Scale * PixVal / MaxVal;
        int numMapEntries = getNumColorMapEntries();
        unsigned int MapIndex = (unsigned int)floor(0.5 + PixVal01 * (double)(numMapEntries - 1));
        return MapIndex;
    }
    else
    {
        return 0;
    }
}


#include "moc_GLImageBox.cpp"
