#include "stdafx.h"
#include "ImageFileFormat.h"
#include "FITSHeader.h"

int IoAImageFileFormat::FITSBlankBytes( int DataBytes )
{
    // Round up data size to multiple of 36 * 80 = 2880
    int Overspill = DataBytes % ( 36 * 80 );

    int BlankBytes = Overspill ? ( 36 * 80 ) - Overspill : 0;

    return BlankBytes;
}

int IoAImageFileFormat::FITSFileBytes( const IoAFrameBuffer& FrameBuffer )
{
    int FITSFileBytes = IoAFITSHeader::Size( FITSFormat ) + FITSDataBytes( FrameBuffer );

    // Round up file size to multiple of 36 * 80 = 2880
    return ( ( FITSFileBytes + 2880 - 1 ) / 2880 ) * 2880;
}

int IoAImageFileFormat::FITSDataBytes( const IoAFrameBuffer& FrameBuffer )
{
    return FrameBuffer.FrameBytes( );
}

int IoAImageFileFormat::FITSFileFormat( const IoAFrameBuffer& FrameBuffer, IoAFileBuffer& FileBuffer, int Append )
{
    if ( ! FrameBuffer.IsAllocated( ) || ! FrameBuffer.IsFrameValid( ) || FrameBuffer.BytesPerPixel( ) != 2 ) return FALSE;

    if ( ! FileBuffer.IsAllocated( ) ) return FALSE;

    // Determine buffer start position
    int Pointer = Append ? FileBuffer.DataSize( ) : 0;

    if ( ! FileBuffer.SetPointer( Pointer ) ) return FALSE;

    // Determine total amount of data
    int FileBytes = FITSFileBytes( FrameBuffer );

    // See if it will fit in buffer
    if ( Pointer + FileBytes > FileBuffer.BufferSize( ) ) return FALSE;

    // Construct FITS header
    IoAFITSHeader FITSHeader;

//    FITSHeader.Set( "NAXIS1",   FrameBuffer.XSize( ) );
//    FITSHeader.Set( "NAXIS2",   FrameBuffer.YSize( ) );
//    FITSHeader.Set( "CAMERAID", FrameBuffer.CameraNumber( ) );
//    FITSHeader.Set( "FRAMENUM", FrameBuffer.FrameNumber( ) );
//    FITSHeader.Set( "FRAMETIM", GetTickCount( ) );
//    FITSHeader.Set( "CPUCLKHI", FrameBuffer.CPUClock( ).HighPart );
//    FITSHeader.Set( "CPUCLKLO", FrameBuffer.CPUClock( ).LowPart );
//    FITSHeader.Set( "DMABUFID", FrameBuffer.DMABufferID( ) );

    FITSHeader.m_NAXIS1   = FrameBuffer.XSize( );
    FITSHeader.m_NAXIS2   = FrameBuffer.YSize( );
    FITSHeader.m_CAMERAID = FrameBuffer.CameraNumber( );
    FITSHeader.m_FRAMENUM = FrameBuffer.FrameNumber( );
    FITSHeader.m_FRAMETIM = GetTickCount( );
    FITSHeader.m_CPUCLKHI = FrameBuffer.CPUClock( ).HighPart;
    FITSHeader.m_CPUCLKLO = FrameBuffer.CPUClock( ).LowPart;
    FITSHeader.m_DMABUFID = FrameBuffer.DMABufferID( );

    // Copy header to file buffer
    int FITSHeaderBytes = FITSHeader.Encode( (char *) FileBuffer.ByteAddress( ) + FileStart, FITSFormat );

    BufferOffset += FITSHeaderBytes;

    // Set up pixel pointers
    unsigned short *pSrc = FrameBuffer.ShortAddress( );
    unsigned short *pDst = (unsigned short *) ( FileBuffer.ByteAddress( ) + BufferOffset );

    // Copy byte-swapped pixels to file buffer
    int PixelCount = FrameBuffer.FramePixels( );

    for ( int i = 0; i < PixelCount; i++ )
    {
        unsigned short Pixel = pSrc[ i ];

        pDst[ i ] = ( Pixel << 8 ) | ( Pixel >> 8 );
    }

    BufferOffset += PixelCount * 2;

    // Pad buffer with blanks to round up size to multiple of 36 * 80 = 2880 bytes
    int BlankBytes = FileBytes - ( FITSHeaderBytes + PixelCount * 2 );

    if ( BlankBytes )
    {
        ::FillMemory( FileBuffer.ByteAddress( ) + FileStart + FITSHeaderBytes + DataBytes, BlankBytes, ' ' );
    }

    return TRUE;
}

int IoAImageFileFormat::FITSFileFormat( const IoAFileBuffer& FileBuffer, IoAFrameBuffer& FrameBuffer )
{
    IoAFITSHeader FITSHeader;

    if ( ! FITSHeader.Decode( (char *) FileBuffer.Address( ) ) ) return FALSE;

    int XSize = FITSHeader.m_NAXIS1;
    int YSize = FITSHeader.m_NAXIS2;

    int BytePerPixel = 2;  // Decode( ) functions checks that BITPIX = 16

    if ( FileBuffer.DataSize( ) < FITSHeader.Size( FITSFormat ) + XSize * YSize * BytePerPixel ) return FALSE;

    if ( ! FrameBuffer.SetFrame( XSize, YSize, BytePerPixel ) ) return FALSE;

    if ( ! FrameBuffer.SetCameraNumber( FITSHeader.m_CAMERAID ) ) return FALSE;

    if ( ! FrameBuffer.SetFrameNumber( FITSHeader.m_FRAMENUM ) ) return FALSE;

    // Set up pixel pointers
    unsigned short *pSrc = (unsigned short *) ( FileBuffer.ByteAddress( ) + FITSHeader.Size( FITSFormat ) );
    unsigned short *pDst = FrameBuffer.ShortAddress( );

    // Copy byte-swapped pixels to frame buffer
    for ( int i = 0; i < FrameBuffer.FramePixels( ); i++ )
    {
        unsigned short Pixel = pSrc[ i ];

        pDst[ i ] = ( Pixel << 8 ) | ( Pixel >> 8 );
    }

    return TRUE;
}

int IoAImageFileFormat::LuckyFileBytes( const IoAFrameBuffer& FrameBuffer )
{
    int DataBytes = LuckyDataBytes( FrameBuffer );

    return IoAFITSHeader::Size( LuckyFormat ) + DataBytes + FITSBlankBytes( DataBytes );
}

int IoAImageFileFormat::LuckyDataBytes( const IoAFrameBuffer& FrameBuffer )
{
    if ( FrameBuffer.IsHistogramValid( ) && FrameBuffer.NonEmptyBinCount( ) > 0 )
    {
        // Fetch pointer to array of histogram counts
        unsigned int *pHistogram = FrameBuffer.Histogram( );

        // Determine maximum pixel value = index to last non-zero histogram count
        int maxPixelValue;

        for ( maxPixelValue = FrameBuffer.HistogramSize( ) - 1; maxPixelValue >= 0; maxPixelValue-- )
        {
            if ( pHistogram[ maxPixelValue ] ) break;
        }

        // Determine number of bits to encode maximum pixel value => number of bits to encode each uncompressed value
        int maxPixelBits;

        for ( maxPixelBits = 0; maxPixelValue; maxPixelBits++ )
        {
            maxPixelValue >>= 1;
        }

        // All uncompressed values occupy this number of bits
        int minUncompressedBits = maxPixelBits;

        // Find optimum number of bits for compressed data (number of bits which yields most compressed result)
        int minCompressedBits       = 0;  // all pixels encode with minUncompressedBits 
        int minCompressedPixels     = 0;
        int minCompressedBitCount   = FrameBuffer.FramePixels( ) * minUncompressedBits;
        int minCompressedPixelStart = 0;

        for ( int CompressedBits = 1; ; CompressedBits++ )
        {
            int CompressedValues = 0x1 << CompressedBits;  // CompressedValues = pow( 2, CompressedBits )

            CompressedValues -= 1;  // lose 1 for uncompressed flag = 0

            if ( CompressedValues > FrameBuffer.HistogramSize( ) ) break;

            int PixelCount = 0;

            for ( int PixelIndex = 0; PixelIndex < CompressedValues; PixelIndex++ )
            {
                PixelCount += pHistogram[ PixelIndex ];
            }

            int maxPixelCount = PixelCount;
            int maxPixelIndex = 0;

            // Find position for compression window which contains the largest number fo pixels
            for ( PixelIndex = 1; PixelIndex <= FrameBuffer.HistogramSize( ) - CompressedValues; PixelIndex++ )
            {
                PixelCount -= pHistogram[ PixelIndex                    - 1 ];
                PixelCount += pHistogram[ PixelIndex + CompressedValues - 1 ];

                if ( PixelCount > maxPixelCount )
                {
                    maxPixelCount = PixelCount;
                    maxPixelIndex = PixelIndex;
                }
            }

            int CompressedPixels     = maxPixelCount;
            int CompressedPixelStart = maxPixelIndex;

            int UncompressedPixels = FrameBuffer.FramePixels( ) - CompressedPixels;

            int CompressedBitCount = FrameBuffer.FramePixels( ) * CompressedBits + UncompressedPixels * minUncompressedBits;

            if ( CompressedBitCount < minCompressedBitCount )
            {
                minCompressedBits       = CompressedBits;
                minCompressedPixels     = CompressedPixels;
                minCompressedBitCount   = CompressedBitCount;
                minCompressedPixelStart = CompressedPixelStart;
            }
        }

        // Determine compression ratio
        double CR = (double) FrameBuffer.FramePixels( ) * 16.0 / (double) minCompressedBitCount;

        // Determine size of data (whole number of 32-bit words to encode total number of bits)
        int DataWords = ( minCompressedBitCount + 31 ) / 32;

        int DataBytes = 4 * DataWords;

        return DataBytes;
    }

    return -1;  // couldn't establish data byte count
}

int IoAImageFileFormat::LuckyFileFormat( const IoAFrameBuffer& FrameBuffer, IoAFileBuffer& FileBuffer, int Append )
{
    if ( ! FrameBuffer.IsAllocated( ) || ! FrameBuffer.IsFrameValid( ) || FrameBuffer.BytesPerPixel( ) != 2 ) return FALSE;

    if ( ! FrameBuffer.IsHistogramValid( ) || FrameBuffer.NonEmptyBinCount( ) == 0 ) return FALSE;

    if ( ! FileBuffer.IsAllocated( ) ) return FALSE;

    // If not appending data, reset buffer pointer
    if ( ! Append ) FileBuffer.Reset( );

    // Determine total amount of data
    int FileBytes = LuckyDataBytes( FrameBuffer );

    // See if it will fit in buffer
    if ( ! FileBuffer.SetDataSize( FileBytes ) ) return FALSE;

    // Fetch pointer to array of histogram counts
    unsigned int *pHistogram = FrameBuffer.Histogram( );

    // Determine maximum pixel value = index to last non-zero histogram count
    for ( int maxPixelValue = FrameBuffer.HistogramSize( ) - 1; maxPixelValue >= 0; maxPixelValue-- )
    {
        if ( pHistogram[ maxPixelValue ] ) break;
    }

    // Determine number of bits to encode maximum pixel value => number of bits to encode each uncompressed value
    for ( int maxPixelBits = 0; maxPixelValue; maxPixelBits++ )
    {
        maxPixelValue >>= 1;
    }

    // All uncompressed values occupy this number of bits
    int minUncompressedBits = maxPixelBits;

    // Find optimum number of bits for compressed data (number of bits which yields most compressed result)
    int minCompressedBits       = 0;  // all pixels encode with minUncompressedBits 
    int minCompressedPixels     = 0;
    int minCompressedBitCount   = FrameBuffer.FramePixels( ) * minUncompressedBits;
    int minCompressedPixelStart = 0;

    for ( int CompressedBits = 1; ; CompressedBits++ )
    {
        int CompressedValues = 0x1 << CompressedBits;  // CompressedValues = pow( 2, CompressedBits )

        CompressedValues -= 1;  // lose 1 for uncompressed flag = 0

        if ( CompressedValues > FrameBuffer.HistogramSize( ) ) break;

        int PixelCount = 0;

        for ( int PixelIndex = 0; PixelIndex < CompressedValues; PixelIndex++ )
        {
            PixelCount += pHistogram[ PixelIndex ];
        }

        int maxPixelCount = PixelCount;
        int maxPixelIndex = 0;

        // Find position for compression window which contains the largest number fo pixels
        for ( PixelIndex = 1; PixelIndex <= FrameBuffer.HistogramSize( ) - CompressedValues; PixelIndex++ )
        {
            PixelCount -= pHistogram[ PixelIndex                    - 1 ];
            PixelCount += pHistogram[ PixelIndex + CompressedValues - 1 ];

            if ( PixelCount > maxPixelCount )
            {
                maxPixelCount = PixelCount;
                maxPixelIndex = PixelIndex;
            }
        }

        int CompressedPixels     = maxPixelCount;
        int CompressedPixelStart = maxPixelIndex;

        int UncompressedPixels = FrameBuffer.FramePixels( ) - CompressedPixels;

        int CompressedBitCount = FrameBuffer.FramePixels( ) * CompressedBits + UncompressedPixels * minUncompressedBits;

        if ( CompressedBitCount < minCompressedBitCount )
        {
            minCompressedBits       = CompressedBits;
            minCompressedPixels     = CompressedPixels;
            minCompressedBitCount   = CompressedBitCount;
            minCompressedPixelStart = CompressedPixelStart;
        }
    }

    double CR = (double) FrameBuffer.FramePixels( ) * 16.0 / (double) minCompressedBitCount;

    // FileFormat = 0, original, byte compressed format
    //            = 1, bit-compressed format within simple compression window
    //            = 2, bit-compressed format with LUT to support optimised compression window

    int FileFormat = 1;

    // Construct FITS header
    IoAFITSHeader FITSHeader;

//    FITSHeader.Set( "NAXIS1",   FrameBuffer.XSize( ) );
//    FITSHeader.Set( "NAXIS2",   FrameBuffer.YSize( ) );
//    FITSHeader.Set( "CAMERAID", FrameBuffer.CameraNumber( ) );
//    FITSHeader.Set( "FRAMENUM", FrameBuffer.FrameNumber( ) );
//    FITSHeader.Set( "FRAMETIM", GetTickCount( ) );
//    FITSHeader.Set( "LC_FORMT", FileFormat );
//    FITSHeader.Set( "LC_CBITS", minCompressedBits );
//    FITSHeader.Set( "LC_UBITS", minUncompressedBits );
//    FITSHeader.Set( "LC_OFFST", minCompressedPixelStart - 1 );
//    FITSHeader.Set( "CPUCLKHI", FrameBuffer.CPUClock( ).HighPart );
//    FITSHeader.Set( "CPUCLKLO", FrameBuffer.CPUClock( ).LowPart );
//    FITSHeader.Set( "DMABUFID", FrameBuffer.DMABufferID( ) );

    FITSHeader.m_NAXIS1   = FrameBuffer.XSize( );
    FITSHeader.m_NAXIS2   = FrameBuffer.YSize( );
    FITSHeader.m_CAMERAID = FrameBuffer.CameraNumber( );
    FITSHeader.m_FRAMENUM = FrameBuffer.FrameNumber( );
    FITSHeader.m_FRAMETIM = GetTickCount( );
    FITSHeader.m_LC_FORMT = FileFormat;
    FITSHeader.m_LC_CBITS = minCompressedBits;
    FITSHeader.m_LC_UBITS = minUncompressedBits;
    FITSHeader.m_LC_OFFST = minCompressedPixelStart - 1;
    FITSHeader.m_CPUCLKHI = FrameBuffer.CPUClock( ).HighPart;
    FITSHeader.m_CPUCLKLO = FrameBuffer.CPUClock( ).LowPart;
    FITSHeader.m_DMABUFID = FrameBuffer.DMABufferID( );

    // Copy header to file buffer
    int FITSHeaderBytes = FITSHeader.Encode( (char *) FileBuffer.ByteAddress( ), LuckyFormat );

    // These are the Microsoft-defined data-types and 64-bit shift function
    // that are used to provide efficient bit-stream handling

//    typedef unsigned int DWORD;
//
//    typedef unsigned __int64 ULONGLONG;
//
//    typedef union _ULARGE_INTEGER
//    { 
//        struct
//        {
//            DWORD LowPart; 
//            DWORD HighPart; 
//        };
//
//        ULONGLONG QuadPart;
//    }
//    ULARGE_INTEGER;
//
//    ULONGLONG Int64ShrlMod32( ULONGLONG Value,     // unsigned 64-bit integer to shift right logically
//                              DWORD ShiftCount );  // a shift count in the range 0 to 31

    // Set up pixel pointers
    unsigned short *pSrc = FrameBuffer.ShortAddress( );
    DWORD          *pDst = (DWORD *) ( FileBuffer.ByteAddress( ) + FITSHeaderBytes );

    int WordCount = 0;

    ULARGE_INTEGER ShiftRegister = { 0, 0 };

    DWORD BitShift = 0;

    DWORD minCompressedPixel = minCompressedPixelStart;
    DWORD maxCompressedPixel = minCompressedPixelStart + ( 0x1 << minCompressedBits ) - 2;

    // Copy compressed pixels to file buffer
    for ( int i = 0; i < FrameBuffer.FramePixels( ); i++ )
    {
        DWORD Pixel = pSrc[ i ];

        DWORD BitCount;

        if ( minCompressedBits )
        {
            BitCount = minCompressedBits;

            // If uncompressed pixel value, output compressed value = 0
            if ( Pixel < minCompressedPixel || Pixel > maxCompressedPixel )
            {
                ShiftRegister.HighPart = 0;

                // If too many bits to shift in one go (register would overflow), split register shift into two
                if ( BitShift + BitCount > 32 )
                {
                    BitShift = 32 - BitShift;

                    // ShiftRegister.QuadPart >>= BitShift;
                    ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitShift );  // first part of shift

                    BitCount -= BitShift;
                    BitShift  = 0;

                    pDst[ WordCount++ ] = ShiftRegister.LowPart;  // store next 32-bit word
                }

                // // ShiftRegister.QuadPart >>= BitCount;
                ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitCount );  // complete the shift

                BitShift += BitCount;
                BitCount  = minUncompressedBits;
            }
            else
            {
                Pixel = 1 + ( Pixel - minCompressedPixel );  // compressed value
            }
        }
        else
        {
            BitCount = minUncompressedBits;  // all pixels output as uncompressed value
        }

        ShiftRegister.HighPart = Pixel;  // move pixel value into shift register

        // If too many bits to shift in one go (register would overflow), split register shift into two
        if ( BitShift + BitCount > 32 )
        {
            BitShift = 32 - BitShift;

            // // ShiftRegister.QuadPart >>= BitShift;
            ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitShift );  // first part of shift

            BitCount -= BitShift;
            BitShift  = 0;

            pDst[ WordCount++ ] = ShiftRegister.LowPart;  // store next 32-bit word
        }

        // // ShiftRegister.QuadPart >>= BitCount;
        ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitCount );  // complete the shift

        BitShift += BitCount;
    }

    // Flush out any bits remaining in the shift register
    if ( BitShift )
    {
        BitShift = 32 - BitShift;

        // ShiftRegister.QuadPart >>= BitShift;
        ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitShift );

        pDst[ WordCount++ ] = ShiftRegister.LowPart;
    }

//    ASSERT( WordCount == DataWords );  // just checking

    // Pad buffer with blanks to round up size to multiple of 36 * 80 = 2880 bytes
    if ( BlankBytes )
    {
        ::FillMemory( FileBuffer.ByteAddress( ) + FITSHeaderBytes + DataBytes, BlankBytes, ' ' );
    }

    return TRUE;
}

int IoAImageFileFormat::LuckyFileFormat( const IoAFileBuffer& FileBuffer, IoAFrameBuffer& FrameBuffer )
{
    IoAFITSHeader FITSHeader;

    if ( ! FITSHeader.Decode( (char *) FileBuffer.Address( ) ) ) return FALSE;

    if ( FITSHeader.m_LCL_ZERO < 0 ) return FALSE;

    int XSize = FITSHeader.m_NAXIS1;
    int YSize = FITSHeader.m_NAXIS2;

    int BytePerPixel = 2;  // Decode( ) function has already checked that BITPIX = 16

    if ( ! FrameBuffer.SetFrame( XSize, YSize, BytePerPixel ) ) return FALSE;

    if ( ! FrameBuffer.SetCameraNumber( FITSHeader.m_CAMERAID ) ) return FALSE;

    if ( ! FrameBuffer.SetFrameNumber( FITSHeader.m_FRAMENUM ) ) return FALSE;

    int FileFormat = FITSHeader.m_LC_FORMT;

    // New LCC format (LC_FORMT keyword appears in the FITS header) - FileFormat = 1 only, for now
    if ( FileFormat )
    {
        int CompressedBits   = FITSHeader.m_LC_CBITS;
        int UncompressedBits = FITSHeader.m_LC_UBITS;

        DWORD CompressedPixelMask   = (DWORD) 0xffffffff >> ( 32 - CompressedBits   );
        DWORD UncompressedPixelMask = (DWORD) 0xffffffff >> ( 32 - UncompressedBits );

        unsigned short LevelOffset = (unsigned short) FITSHeader.m_LC_OFFST;

        // Set up pixel pointers
        DWORD          *pSrc = (DWORD *) ( FileBuffer.ByteAddress( ) + FITSHeader.Size( LuckyFormat ) );
        unsigned short *pDst = FrameBuffer.ShortAddress( );

        int WordCount = 0;

        ULARGE_INTEGER ShiftRegister = { 0, 0 };

        // Load shift register with first 64-bits of data
        ShiftRegister.LowPart  = pSrc[ WordCount++ ];
        ShiftRegister.HighPart = pSrc[ WordCount++ ];

        DWORD BitShift = 0;

        // Decompress and copy pixels to frame buffer
        for ( int i = 0; i < FrameBuffer.FramePixels( ); i++ )
        {
            DWORD BitCount;

            if ( CompressedBits )
            {
                BitCount = CompressedBits;

                // Fetch compressed datum
                DWORD Pixel = ShiftRegister.LowPart & CompressedPixelMask;

                if ( Pixel )  // compressed pixel value
                {
                    pDst[ i ] = LevelOffset + (unsigned short) Pixel;  // compressed pixel value
                }
                else  // uncompressed flag followed by uncompressed pixel value
                {
                    // If too many bits to shift in one go (register would overflow), split register shift into two
                    if ( BitShift + BitCount > 32 )
                    {
                        BitShift = 32 - BitShift;

                        // ShiftRegister.QuadPart >>= BitShift;
                        ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitShift );  // first part of shift

                        BitCount -= BitShift;
                        BitShift  = 0;

                        ShiftRegister.HighPart = pSrc[ WordCount++ ];  // fetch next 32-bit word
                    }

                    // ShiftRegister.QuadPart >>= BitCount;
                    ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitCount );  // complete the shift

                    BitShift += BitCount;
                    BitCount  = UncompressedBits;

                    pDst[ i ] = (unsigned short) ( ShiftRegister.LowPart & UncompressedPixelMask );  // uncompressed pixel value
                }
            }
            else
            {
                BitCount = UncompressedBits;  // all pixels are uncompressed

                pDst[ i ] = (unsigned short) ( ShiftRegister.LowPart & UncompressedPixelMask );  // uncompressed pixel value
            }

            // If too many bits to shift in one go (register would overflow), split register shift into two
            if ( BitShift + BitCount > 32 )
            {
                BitShift = 32 - BitShift;

                // ShiftRegister.QuadPart >>= BitShift;
                ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitShift );  // first part of shift

                BitCount -= BitShift;
                BitShift  = 0;

                ShiftRegister.HighPart = pSrc[ WordCount++ ];  // fetch next 32-bit word
            }

            // ShiftRegister.QuadPart >>= BitCount;
            ShiftRegister.QuadPart = Int64ShrlMod32( ShiftRegister.QuadPart, BitCount );  // complete the shift

            BitShift += BitCount;
        }
    }

    // Old LCC format
    else
    {
        int ZeroLevel = FITSHeader.m_LCL_ZERO;

        int BitsPerPixel = FrameBuffer.BitsPerPixel( );

        if ( BitsPerPixel == 0 ) return FALSE;

        // Set up pixel pointers
        signed   char  *pSrc = (signed char *) ( FileBuffer.ByteAddress( ) + FITSHeader.Size( LuckyFormat ) );
        unsigned short *pDst = FrameBuffer.ShortAddress( );

        int minPixel = 0;
        int maxPixel = (unsigned int) 0xffff >> ( 16 - BitsPerPixel );

        int ByteCount = 0;

        // Decompress and copy pixels to frame buffer
        for ( int i = 0; i < FrameBuffer.FramePixels( ); i++ )
        {
            signed char Byte = pSrc[ ByteCount++ ];

            int Pixel;

            if ( Byte == -128 )
            {
                unsigned short LSByte = (unsigned char) pSrc[ ByteCount++ ];
                unsigned short MSByte = (unsigned char) pSrc[ ByteCount++ ];

                Pixel = ZeroLevel + (signed short) ( ( MSByte << 8 ) | LSByte );  // uncompressed pixel value (16-bits)
            }
            else
            {
                Pixel = ZeroLevel + Byte;  // compressed pixel value (8-bits)
            }

            // Truncate pixel value to range supported by frame buffer
            if ( Pixel < minPixel ) Pixel = minPixel;
            if ( Pixel > maxPixel ) Pixel = maxPixel;

            pDst[ i ] = Pixel;
        }
    }

    return TRUE;
}
