MacGamma.cpp

/*
    File:       MacGamma.cpp
 
    Contains:   Functions to enable Mac OS device gamma adjustments using 3 channel 256 element 8 bit gamma ramps
 
    Written by: Geoff Stahl (ggs)
 
    Copyright:  Copyright © 1999 Apple Computer, Inc., All Rights Reserved
 
    Change History (most recent first):
 
         <7>     8/25/99    ggs     Added usage notes
         <6>     8/25/99    ggs     Fixed handling of 1 -> 3 channel gamma conversions in get and set
         <5>     7/14/99    ggs     Added sample code notice
         <4>     5/20/99    ggs     Added handling for gamma tables with different data widths,
                                    number of entries, and channels.  Forced updates to 3 channels
                                    (poss. could break on rare card, but very unlikely).  Added
                                    quick update with BlockMove for 3x256x8 tables. Updated function
                                    names.
         <3>     5/20/99    ggs     Cleaned up and commented
         <2>     5/20/99    ggs     Added system wide get and restore gamma functions to enable
                                    restoration of original for all devices.  Modified functionality
                                    to return pointers vice squirreling away the memory.
         <1>     5/20/99    ggs     Initial Add
 
    Disclaimer: You may incorporate this sample code into your applications without
                restriction, though the sample code has been provided "AS IS" and the
                responsibility for its operation is 100% yours.  However, what you are
                not permitted to do is to redistribute the source as "DSC Sample Code"
                after having made changes. If you're going to re-distribute the source,
                we require that you make it clear in the source that the code was
                descended from Apple Sample Code, but that you've made changes.
 
*/
 
// Usage notes: 
 
// SetDeviceGammaRampXX may not return until the VBL following their call, depending on the display driver,
// this is due to the handling of the Control call by the driver.
 
// Startup and Shutdown:
 
// GetSystemGammas (...) must be called to save the system gammas prior to using MacGamma
// RestoreSystemGamms (...)  must be called before exiting to restore original gamma
// DisposeSystemGammas (...) can then be used to dispose of the system gammas
 
// Suspend and Resume:
 
// RestoreSystemGamms (...); DisposeSystemGammas (...); should also be called prior to suspend and
// GetSystemGammas (...) should be called when resuming
// this prevents users from saving display settings with your gammas
 
 
// system includes ----------------------------------------------------------
 
#include <Devices.h>
#include <Files.h>
#include <MacTypes.h>
#include <QDOffscreen.h>
#include <Quickdraw.h>
#include <video.h>
 
 
// project includes ---------------------------------------------------------
 
#include "MacGamma.h"
 
 
// structures (internal/private) -----------------------------------------------
    
struct recDeviceGamma                                           // storage for device handle and gamma table
{
    GDHandle hGD;                                               // handle to device
    GammaTblPtr pDeviceGamma;                                   // pointer to device gamma table
};
typedef recDeviceGamma * precDeviceGamma;
 
struct recSystemGamma                                           // storage for system devices and gamma tables
{
    short numDevices;                                           // number of devices
    precDeviceGamma * devGamma;                                 // array of pointers to device gamma records
};
typedef recSystemGamma * precSystemGamma;
 
 
// prototypes (internal/private) --------------------------------------------
 
// 5/20/99: (ggs) changed functional specification
 
OSErr GetGammaTable(GDHandle gd, GammaTblPtr * ppTableGammaOut);        // Returns the device gamma table pointer in ppDeviceTable
Ptr CreateEmptyGammaTable (short channels, short entries, short bits);  // creates an empty gamma table of a given size, assume no formula data will be used
Ptr CopyGammaTable (GammaTblPtr pTableGammaIn);                         // given a pointer toa device gamma table properly iterates and copies
void DisposeGammaTable (Ptr pGamma);                                    // disposes gamma table returned from GetGammaTable, GetDeviceGamma, or CopyGammaTable 
Ptr GetDeviceGamma (GDHandle hGD);                                      // returns pointer to copy of orginal device gamma table in native format
void RestoreDeviceGamma (GDHandle hGD, Ptr pGammaTable);                // sets device to saved table
 
 
// functions (internal/private) ---------------------------------------------
 
// GetGammaTable
 
// Returns the device gamma table pointer in ppTableGammaOut
 
OSErr GetGammaTable (GDHandle hGD, GammaTblPtr * ppTableGammaOut)
{
    VDGammaRecord   DeviceGammaRec;
    CntrlParam      cParam;
    OSErr           err;
    
    cParam.ioCompletion = NULL;                                     // set up control params
    cParam.ioNamePtr = NULL;
    cParam.ioVRefNum = 0;
    cParam.ioCRefNum = (**hGD).gdRefNum;
    cParam.csCode = cscGetGamma;                                    // Get Gamma commnd to device
    *(Ptr *)cParam.csParam = (Ptr) &DeviceGammaRec;                 // record for gamma
 
    err = PBStatus( (ParmBlkPtr)&cParam, 0 );                       // get gamma
    
    *ppTableGammaOut = (GammaTblPtr)(DeviceGammaRec.csGTable);      // pull table out of record
    
    return err; 
}
 
// --------------------------------------------------------------------------
 
// CreateEmptyGammaTable
 
// creates an empty gamma table of a given size, assume no formula data will be used
 
Ptr CreateEmptyGammaTable (short channels, short entries, short bits)
{
    GammaTblPtr     pTableGammaOut = NULL;
    short           tableSize, dataWidth;
 
    dataWidth = (bits + 7) / 8;                                     // number of bytes per entry
    tableSize = sizeof (GammaTbl) + (channels * entries * dataWidth);
    pTableGammaOut = (GammaTblPtr) NewPtrClear (tableSize);         // allocate new tabel
 
    if (pTableGammaOut)                                             // if we successfully allocated
    {
        pTableGammaOut->gVersion = 0;                               // set parameters based on input
        pTableGammaOut->gType = 0;
        pTableGammaOut->gFormulaSize = 0;
        pTableGammaOut->gChanCnt = channels;
        pTableGammaOut->gDataCnt = entries;
        pTableGammaOut->gDataWidth = bits;
    }
    return (Ptr)pTableGammaOut;                                     // return whatever we allocated
}
 
// --------------------------------------------------------------------------
 
// CopyGammaTable
 
// given a pointer toa device gamma table properly iterates and copies
 
Ptr CopyGammaTable (GammaTblPtr pTableGammaIn)
{
    GammaTblPtr     pTableGammaOut = NULL;
    short           tableSize, dataWidth;
 
    if (pTableGammaIn)                                              // if there is a table to copy 
    {
        dataWidth = (pTableGammaIn->gDataWidth + 7) / 8;            // number of bytes per entry
        tableSize = sizeof (GammaTbl) + pTableGammaIn->gFormulaSize +
                    (pTableGammaIn->gChanCnt * pTableGammaIn->gDataCnt * dataWidth);
        pTableGammaOut = (GammaTblPtr) NewPtr (tableSize);          // allocate new table
        if (pTableGammaOut)                                         
            BlockMove( (Ptr)pTableGammaIn, (Ptr)pTableGammaOut, tableSize); // move everything
    }
    return (Ptr)pTableGammaOut;                                     // return whatever we allocated, could be NULL
}
 
// --------------------------------------------------------------------------
 
// DisposeGammaTable
 
// disposes gamma table returned from GetGammaTable, GetDeviceGamma, or CopyGammaTable 
// 5/20/99: (ggs) added
 
void DisposeGammaTable (Ptr pGamma)
{
    if (pGamma)
        DisposePtr((Ptr) pGamma);                                   // get rid of it
}
 
// --------------------------------------------------------------------------
 
// GetDeviceGamma
 
// returns pointer to copy of orginal device gamma table in native format (allocates memory for gamma table, call DisposeDeviceGamma to delete)
// 5/20/99: (ggs) change spec to return the allocated pointer vice storing internally
 
Ptr GetDeviceGamma (GDHandle hGD)
{
    GammaTblPtr     pTableGammaDevice = NULL;
    GammaTblPtr     pTableGammaReturn = NULL;   
    OSErr           err;
    
    err = GetGammaTable (hGD, &pTableGammaDevice);                  // get a pointer to the devices table
    if ((noErr == err) && pTableGammaDevice)                        // if succesful
        pTableGammaReturn = (GammaTblPtr) CopyGammaTable (pTableGammaDevice); // copy to global
 
    return (Ptr) pTableGammaReturn;
}
 
// --------------------------------------------------------------------------
 
// RestoreDeviceGamma
 
// sets device to saved table
// 5/20/99: (ggs) now does not delete table, avoids confusion
 
void RestoreDeviceGamma (GDHandle hGD, Ptr pGammaTable)
{
    VDSetEntryRecord setEntriesRec;
    VDGammaRecord   gameRecRestore;
    CTabHandle      hCTabDeviceColors;
    Ptr             csPtr;
    OSErr           err = noErr;
    
    if (pGammaTable)                                                // if we have a table to restore                                
    {
        gameRecRestore.csGTable = pGammaTable;                      // setup restore record
        csPtr = (Ptr) &gameRecRestore;
        err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr); // restore gamma
 
        if ((noErr == err) && (8 == (**(**hGD).gdPMap).pixelSize))  // if successful and on an 8 bit device
        {
            hCTabDeviceColors = (**(**hGD).gdPMap).pmTable;         // do SetEntries to force CLUT update
            setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable;
            setEntriesRec.csStart = 0;
            setEntriesRec.csCount = (**hCTabDeviceColors).ctSize;
            csPtr = (Ptr) &setEntriesRec;
            
            err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr); // SetEntries in CLUT
        }
    }
}
 
 
// functions (external/public) ----------------------------------------------
 
// GetSystemGammas
 
// returns a pointer to a set of all current device gammas in native format (returns NULL on failure, which means reseting gamma will not be possible)
// 5/20/99: (ggs) added
 
Ptr GetSystemGammas (void)                                      
{
    precSystemGamma pSysGammaOut;                                   // return pointer to system device gamma info
    short devCount = 0;                                             // number of devices attached
    Boolean fail = false;
 
    pSysGammaOut = (precSystemGamma) NewPtr (sizeof (recSystemGamma)); // allocate for structure
    
    GDHandle hGDevice = GetDeviceList ();                           // top of device list
    do                                                              // iterate
    {
        devCount++;                                                 // count devices                    
        hGDevice = GetNextDevice (hGDevice);                        // next device
    } while (hGDevice);
    
    pSysGammaOut->devGamma = (precDeviceGamma *) NewPtr (sizeof (precDeviceGamma) * devCount); // allocate for array of pointers to device records
    if (pSysGammaOut)
    {
        pSysGammaOut->numDevices = devCount;                        // stuff count
        
        devCount = 0;                                               // reset iteration
        hGDevice = GetDeviceList ();
        do
        {
            pSysGammaOut->devGamma [devCount] = (precDeviceGamma) NewPtr (sizeof (recDeviceGamma));   // new device record
            if (pSysGammaOut->devGamma [devCount])                  // if we actually allocated memory
            {
                pSysGammaOut->devGamma [devCount]->hGD = hGDevice;                                        // stuff handle
                pSysGammaOut->devGamma [devCount]->pDeviceGamma = (GammaTblPtr)GetDeviceGamma (hGDevice); // copy gamma table
            }
            else                                                    // otherwise dump record on exit
             fail = true;
            devCount++;                                             // next device
            hGDevice = GetNextDevice (hGDevice);                        
        } while (hGDevice);
    }
    if (!fail)                                                      // if we did not fail
        return (Ptr) pSysGammaOut;                                  // return pointer to structure
    else
    {
        DisposeSystemGammas (&(Ptr)pSysGammaOut);                   // otherwise dump the current structures (dispose does error checking)
        return NULL;                                                // could not complete
    }
}
 
// --------------------------------------------------------------------------
 
// RestoreSystemGammas
 
// restores all system devices to saved gamma setting
// 5/20/99: (ggs) added
 
void RestoreSystemGammas (Ptr pSystemGammas)
{
    precSystemGamma pSysGammaIn = (precSystemGamma) pSystemGammas;
    if (pSysGammaIn)
        for (short i = 0; i < pSysGammaIn->numDevices; i++)         // for all devices
            RestoreDeviceGamma (pSysGammaIn->devGamma [i]->hGD, (Ptr) pSysGammaIn->devGamma [i]->pDeviceGamma); // restore gamma
}
 
// --------------------------------------------------------------------------
 
// DisposeSystemGammas
 
// iterates through and deletes stored gamma settings
// 5/20/99: (ggs) added
 
void DisposeSystemGammas (Ptr* ppSystemGammas)
{
    precSystemGamma pSysGammaIn;
    if (ppSystemGammas)
    {
        pSysGammaIn = (precSystemGamma) *ppSystemGammas;
        if (pSysGammaIn)
        {
            for (short i = 0; i < pSysGammaIn->numDevices; i++)     // for all devices
                if (pSysGammaIn->devGamma [i])                      // if pointer is valid
                {
                    DisposeGammaTable ((Ptr) pSysGammaIn->devGamma [i]->pDeviceGamma); // dump gamma table
                    DisposePtr ((Ptr) pSysGammaIn->devGamma [i]);                      // dump device info
                }
            DisposePtr ((Ptr) pSysGammaIn->devGamma);               // dump device pointer array        
            DisposePtr ((Ptr) pSysGammaIn);                         // dump system structure
            *ppSystemGammas = NULL;
        }   
    }
}
 
// --------------------------------------------------------------------------
 
// GetDeviceGammaRampGD
 
// retrieves the gamma ramp from a graphics device (pRamp: 3 arrays of 256 elements each)
 
Boolean GetDeviceGammaRampGD (GDHandle hGD, Ptr pRamp)
{
    GammaTblPtr     pTableGammaTemp = NULL;
    long            indexChan, indexEntry;
    OSErr           err;
    
    if (pRamp)                                                          // ensure pRamp is allocated
    {
        err = GetGammaTable (hGD, &pTableGammaTemp);                    // get a pointer to the current gamma
        if ((noErr == err) && pTableGammaTemp)                          // if successful
        {                                                           
            // fill ramp
            unsigned char * pEntry = (unsigned char *) &pTableGammaTemp->gFormulaData + pTableGammaTemp->gFormulaSize; // base of table
            short bytesPerEntry = (pTableGammaTemp->gDataWidth + 7) / 8; // size, in bytes, of the device table entries
            short shiftRightValue = pTableGammaTemp->gDataWidth - 8;     // number of right shifts device -> ramp
            short channels = pTableGammaTemp->gChanCnt; 
            short entries = pTableGammaTemp->gDataCnt;                                  
            if (3 == channels)                                          // RGB format
            {                                                           // note, this will create runs of entries if dest. is bigger (not linear interpolate)
                for (indexChan = 0; indexChan < channels; indexChan++)
                    for (indexEntry = 0; indexEntry < 256; indexEntry++)
                        *((unsigned char *) pRamp + (indexChan * 256) + indexEntry) = 
                          *(pEntry + indexChan * entries * bytesPerEntry + indexEntry * entries * bytesPerEntry / 256) >> shiftRightValue;
            }
            else                                                        // single channel format
            {
                for (indexChan = 0; indexChan < 768; indexChan += 256)  // repeat for all 3 channels (step by ramp size)
                    for (indexEntry = 0; indexEntry < 256; indexEntry++) // for all entries set vramp value
                        *((unsigned char *) pRamp + indexChan + indexEntry) = 
                          *(pEntry + indexEntry * entries * bytesPerEntry / 256) >> shiftRightValue;
            }
            return true;
        }
    }
    return false;
}
 
// --------------------------------------------------------------------------
 
// GetDeviceGammaRampGW
 
// retrieves the gamma ramp from a graphics device associated with a GWorld pointer (pRamp: 3 arrays of 256 elements each)
 
Boolean GetDeviceGammaRampGW (GWorldPtr pGW, Ptr pRamp)
{
    GDHandle hGD = GetGWorldDevice (pGW);
    return GetDeviceGammaRampGD (hGD, pRamp);
}
 
// --------------------------------------------------------------------------
 
// GetDeviceGammaRampCGP
 
// retrieves the gamma ramp from a graphics device associated with a CGraf pointer (pRamp: 3 arrays of 256 elements each)
 
Boolean GetDeviceGammaRampCGP (CGrafPtr pGraf, Ptr pRamp)
{
    CGrafPtr pGrafSave;
    GDHandle hGDSave;
    GetGWorld (&pGrafSave, &hGDSave);
    SetGWorld (pGraf, NULL);
    GDHandle hGD = GetGDevice ();
    Boolean fResult = GetDeviceGammaRampGD (hGD, pRamp);
    SetGWorld (pGrafSave, hGDSave);
    return fResult;
}
 
// --------------------------------------------------------------------------
 
// SetDeviceGammaRampGD
 
// sets the gamma ramp for a graphics device (pRamp: 3 arrays of 256 elements each (R,G,B))
 
Boolean SetDeviceGammaRampGD (GDHandle hGD, Ptr pRamp)
{
    VDSetEntryRecord setEntriesRec;
    VDGammaRecord   gameRecRestore;
    GammaTblPtr     pTableGammaNew;
    GammaTblPtr     pTableGammaCurrent = NULL;
    CTabHandle      hCTabDeviceColors;
    Ptr             csPtr;
    OSErr           err;
    short           dataBits, entries, channels = 3;                        // force three channels in the gamma table
    
    if (pRamp)                                                              // ensure pRamp is allocated
    {
        err= GetGammaTable (hGD, &pTableGammaCurrent);                      // get pointer to current table
        if ((noErr == err) && pTableGammaCurrent)
        {
            dataBits = pTableGammaCurrent->gDataWidth;                      // table must have same data width
            entries = pTableGammaCurrent->gDataCnt;                         // table must be same size
            pTableGammaNew = (GammaTblPtr) CreateEmptyGammaTable (channels, entries, dataBits); // our new table
            if (pTableGammaNew)                                             // if successful fill table
            {   
                unsigned char * pGammaBase = (unsigned char *) &pTableGammaNew->gFormulaData + pTableGammaNew->gFormulaSize; // base of table
                if ((256 == entries) && (8 == dataBits))                        // simple case: direct mapping
                    BlockMove ((Ptr)pRamp, (Ptr)pGammaBase, channels * entries); // move everything
                else                                                        // tough case handle entry, channel and data size disparities
                {
                    short bytesPerEntry = (dataBits + 7) / 8;               // size, in bytes, of the device table entries
                    short shiftRightValue = 8 - dataBits;                   // number of right shifts ramp -> device
                    shiftRightValue += ((bytesPerEntry - 1) * 8);           // multibyte entries and the need to map a byte at a time most sig. to least sig.
                    for (short indexChan = 0; indexChan < channels; indexChan++) // for all the channels
                        for (short indexEntry = 0; indexEntry < entries; indexEntry++) // for all the entries
                        {
                            short currentShift = shiftRightValue;           // reset current bit shift
                            long temp = *((unsigned char *)pRamp + (indexChan << 8) + (indexEntry << 8) / entries); // get data from ramp
                            for (short indexByte = 0; indexByte < bytesPerEntry; indexByte++) // for all bytes
                            {
                                if (currentShift < 0)                       // shift data correctly for current byte
                                    *(pGammaBase++) = temp << -currentShift;
                                else
                                    *(pGammaBase++) = temp >> currentShift;
                                currentShift -= 8;                          // increment shift to align to next less sig. byte
                            }
                        }
                }
                
                // set gamma
                gameRecRestore.csGTable = (Ptr) pTableGammaNew;             // setup restore record
                csPtr = (Ptr) &gameRecRestore;
                err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr); // restore gamma (note, display drivers may delay returning from this until VBL)
                
                if ((8 == (**(**hGD).gdPMap).pixelSize) && (noErr == err))  // if successful and on an 8 bit device
                {
                    hCTabDeviceColors = (**(**hGD).gdPMap).pmTable;         // do SetEntries to force CLUT update
                    setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable;
                    setEntriesRec.csStart = 0;
                    setEntriesRec.csCount = (**hCTabDeviceColors).ctSize;
                    csPtr = (Ptr) &setEntriesRec;
                    err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr);   // SetEntries in CLUT
                }
                DisposeGammaTable ((Ptr) pTableGammaNew);                   // dump table
                if (noErr == err)
                    return true;
            }
        }
    }
    else                                                                    // set NULL gamma -> results in linear map
    {
        gameRecRestore.csGTable = (Ptr) NULL;                               // setup restore record
        csPtr = (Ptr) &gameRecRestore;
        err = Control((**hGD).gdRefNum, cscSetGamma, (Ptr) &csPtr);         // restore gamma
        
        if ((8 == (**(**hGD).gdPMap).pixelSize) && (noErr == err))          // if successful and on an 8 bit device
        {
            hCTabDeviceColors = (**(**hGD).gdPMap).pmTable;                 // do SetEntries to force CLUT update
            setEntriesRec.csTable = (ColorSpec *) &(**hCTabDeviceColors).ctTable;
            setEntriesRec.csStart = 0;
            setEntriesRec.csCount = (**hCTabDeviceColors).ctSize;
            csPtr = (Ptr) &setEntriesRec;
            err = Control((**hGD).gdRefNum, cscSetEntries, (Ptr) &csPtr);   // SetEntries in CLUT
        }
        if (noErr == err)
            return true;
    }
    return false;                                                           // memory allocation or device control failed if we get here
}
 
// --------------------------------------------------------------------------
 
// SetDeviceGammaRampGW
 
// sets the gamma ramp for a graphics device associated with a GWorld pointer (pRamp: 3 arrays of 256 elements each (R,G,B))
 
Boolean SetDeviceGammaRampGW (GWorldPtr pGW, Ptr pRamp)
{
    GDHandle hGD = GetGWorldDevice (pGW);
    return SetDeviceGammaRampGD (hGD, pRamp);
}
 
// --------------------------------------------------------------------------
 
// SetDeviceGammaRampCGP
 
// sets the gamma ramp for a graphics device associated with a CGraf pointer (pRamp: 3 arrays of 256 elements each (R,G,B))
 
Boolean SetDeviceGammaRampCGP (CGrafPtr pGraf, Ptr pRamp)
{
    CGrafPtr pGrafSave;
    GDHandle hGDSave;
    GetGWorld (&pGrafSave, &hGDSave);
    SetGWorld (pGraf, NULL);
    GDHandle hGD = GetGDevice ();
    Boolean fResult = SetDeviceGammaRampGD (hGD, pRamp);
    SetGWorld (pGrafSave, hGDSave);
    return fResult;
}