/******************************************************************************
 *
 * Project:  SGI Image Driver
 * Purpose:  Implement SGI Image Support based on Paul Bourke's SGI Image code.
 *           http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/
 *           ftp://ftp.sgi.com/graphics/SGIIMAGESPEC
 * Authors:  Mike Mazzella (GDAL driver)
 *           Paul Bourke (original SGI format code)
 *           Frank Warmerdam (write support)
 *
 ******************************************************************************
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2008-2010, Even Rouault <even dot rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_port.h"
#include "cpl_string.h"
#include "gdal_frmts.h"
#include "gdal_pam.h"

#include <algorithm>


struct ImageRec
{
    GUInt16 imagic;
    GByte type;
    GByte bpc;
    GUInt16 dim;
    GUInt16 xsize;
    GUInt16 ysize;
    GUInt16 zsize;
    GUInt32 min;
    GUInt32 max;
    char wasteBytes[4];
    char name[80];
    GUInt32 colorMap;

    VSILFILE* file;
    std::string fileName;
    int tmpSize;
    unsigned char* tmp;
    GUInt32 rleEnd;
    int     rleTableDirty;
    GUInt32* rowStart;
    GInt32* rowSize;

    ImageRec()
            : imagic(0),
              type(0),
              bpc(1),
              dim(0),
              xsize(0),
              ysize(0),
              zsize(0),
              min(0),
              max(0),
              colorMap(0),
              file(nullptr),
              fileName(""),
              tmpSize(0),
              tmp(nullptr),
              rleEnd(0),
              rleTableDirty(FALSE),
              rowStart(nullptr),
              rowSize(nullptr)
        {
            memset(wasteBytes, 0, 4);
            memset(name, 0, 80);
        }

    void Swap()
        {
#ifdef CPL_LSB
            CPL_SWAP16PTR(&imagic);
            CPL_SWAP16PTR(&dim);
            CPL_SWAP16PTR(&xsize);
            CPL_SWAP16PTR(&ysize);
            CPL_SWAP16PTR(&zsize);
            CPL_SWAP32PTR(&min);
            CPL_SWAP32PTR(&max);
#endif
        }
};

/************************************************************************/
/*                            ConvertLong()                             */
/************************************************************************/
#ifdef CPL_LSB
static void ConvertLong(GUInt32* array, GInt32 length)
{
   GUInt32* ptr = reinterpret_cast<GUInt32*>( array );
   while(length--)
   {
     CPL_SWAP32PTR(ptr);
     ptr ++;
   }
}
#else
static void ConvertLong(GUInt32* /*array*/, GInt32 /*length */)
{
}
#endif

/************************************************************************/
/*                            ImageGetRow()                             */
/************************************************************************/
static CPLErr ImageGetRow(ImageRec* image, unsigned char* buf, int y, int z)
{
    y = image->ysize - 1 - y;

    if( static_cast<int>( image->type ) != 1)
    {
        VSIFSeekL(image->file, 512+(y*static_cast<vsi_l_offset>(image->xsize))+(z*static_cast<vsi_l_offset>(image->xsize)*static_cast<vsi_l_offset>(image->ysize)), SEEK_SET);
        if(VSIFReadL(buf, 1, image->xsize, image->file) != image->xsize)
        {
            CPLError(CE_Failure, CPLE_OpenFailed, "file read error: row (%d) of (%s)\n", y, image->fileName.empty() ? "none" : image->fileName.c_str());
            return CE_Failure;
        }
        return CE_None;
    }

    // Image type 1.

    // reads row
    if( image->rowSize[y+z*image->ysize] < 0 ||
        image->rowSize[y+z*image->ysize] > image->tmpSize )
    {
        return CE_Failure;
    }
    VSIFSeekL( image->file,
               static_cast<long>( image->rowStart[y+z*image->ysize] ),
               SEEK_SET);
    if( VSIFReadL( image->tmp, 1, static_cast<GUInt32>(
           image->rowSize[y+z*image->ysize]), image->file )
        != static_cast<GUInt32>( image->rowSize[y+z*image->ysize] ) )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "file read error: row (%d) of (%s)\n",
                  y,
                  image->fileName.empty() ? "none" : image->fileName.c_str() );
        return CE_Failure;
    }

    // expands row
    unsigned char *iPtr = image->tmp;
    unsigned char *oPtr = buf;
    int xsizeCount = 0;
    for(;;)
    {
        unsigned char pixel = *iPtr++;
        int count = static_cast<int>( pixel & 0x7F );
        if(!count)
        {
            if(xsizeCount != image->xsize)
            {
                CPLError( CE_Failure, CPLE_OpenFailed,
                          "file read error: row (%d) of (%s)\n",
                          y,
                          image->fileName.empty() ? "none" : image->fileName.c_str() );
                return CE_Failure;
            }
            else
            {
                break;
            }
        }

        if( xsizeCount + count > image->xsize )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Wrong repetition number that would overflow data "
                      "at line %d", y );
            return CE_Failure;
        }

        if(pixel & 0x80)
        {
          memcpy(oPtr, iPtr, count);
          iPtr += count;
        }
        else
        {
            pixel = *iPtr++;
            memset(oPtr, pixel, count);
        }
        oPtr += count;
        xsizeCount += count;
    }

    return CE_None;
}

/************************************************************************/
/* ==================================================================== */
/*                              SGIDataset                              */
/* ==================================================================== */
/************************************************************************/

class SGIRasterBand;

class SGIDataset final: public GDALPamDataset
{
    friend class SGIRasterBand;

    VSILFILE*  fpImage;

    int    bGeoTransformValid;
    double adfGeoTransform[6];

    ImageRec image;

public:
    SGIDataset();
    virtual ~SGIDataset();

    virtual CPLErr GetGeoTransform(double*) override;
    static GDALDataset* Open(GDALOpenInfo*);
    static GDALDataset *Create( const char * pszFilename,
                                int nXSize, int nYSize, int nBandsIn,
                                GDALDataType eType, char **papszOptions );
};

/************************************************************************/
/* ==================================================================== */
/*                            SGIRasterBand                             */
/* ==================================================================== */
/************************************************************************/

class SGIRasterBand final: public GDALPamRasterBand
{
    friend class SGIDataset;

public:
    SGIRasterBand(SGIDataset*, int);

    virtual CPLErr IReadBlock(int, int, void*) override;
    virtual CPLErr IWriteBlock(int, int, void*) override;
    virtual GDALColorInterp GetColorInterpretation() override;
};

/************************************************************************/
/*                           SGIRasterBand()                            */
/************************************************************************/

SGIRasterBand::SGIRasterBand( SGIDataset* poDSIn, int nBandIn )

{
  poDS = poDSIn;
  nBand = nBandIn;

  if(static_cast<int>( poDSIn->image.bpc ) == 1)
      eDataType = GDT_Byte;
  else
      eDataType = GDT_Int16;

  nBlockXSize = poDSIn->nRasterXSize;
  nBlockYSize = 1;
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr SGIRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
                                  int nBlockYOff,
                                  void* pImage )
{
    SGIDataset* poGDS = reinterpret_cast<SGIDataset *>( poDS );

    CPLAssert(nBlockXOff == 0);

/* -------------------------------------------------------------------- */
/*      Load the desired data into the working buffer.              */
/* -------------------------------------------------------------------- */
    return ImageGetRow( &(poGDS->image),
                        reinterpret_cast<unsigned char*>( pImage ),
                        nBlockYOff, nBand - 1 );
}

/************************************************************************/
/*                             IWritelock()                             */
/************************************************************************/

CPLErr SGIRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff,
                                  int nBlockYOff,
                                  void*  pImage)
{
    CPLAssert(nBlockXOff == 0);

    SGIDataset* poGDS = reinterpret_cast<SGIDataset *>( poDS);
    ImageRec *image = &(poGDS->image);

/* -------------------------------------------------------------------- */
/*      Handle the fairly trivial non-RLE case.                         */
/* -------------------------------------------------------------------- */
    if( image->type == 0 )
    {
        VSIFSeekL(image->file,
                  512 + (nBlockYOff*static_cast<vsi_l_offset>(image->xsize))
                  + ((nBand-1)*static_cast<vsi_l_offset>(image->xsize)*static_cast<vsi_l_offset>(image->ysize) ),
                  SEEK_SET);
        if(VSIFWriteL(pImage, 1, image->xsize, image->file) != image->xsize)
        {
            CPLError(CE_Failure, CPLE_OpenFailed,
                     "file write error: row (%d)\n", nBlockYOff );
            return CE_Failure;
        }
        return CE_None;
    }

/* -------------------------------------------------------------------- */
/*      Handle RLE case.                                                */
/* -------------------------------------------------------------------- */
    const GByte *pabyRawBuf = reinterpret_cast<const GByte *>( pImage );
    GByte *pabyRLEBuf = reinterpret_cast<GByte *>(
        CPLMalloc( image->xsize * 2 + 6 ) );

    int iX = 0;
    int nRLEBytes = 0;

    while( iX < image->xsize )
    {
        int nRepeatCount = 1;

        while( iX + nRepeatCount < image->xsize
               && nRepeatCount < 127
               && pabyRawBuf[iX + nRepeatCount] == pabyRawBuf[iX] )
            nRepeatCount++;

        if( nRepeatCount > 2
            || iX + nRepeatCount == image->xsize
            || (iX + nRepeatCount < image->xsize - 3
                && pabyRawBuf[iX + nRepeatCount + 1]
                == pabyRawBuf[iX + nRepeatCount + 2]
                && pabyRawBuf[iX + nRepeatCount + 1]
                == pabyRawBuf[iX + nRepeatCount + 3]) )
        { // encode a constant run.
            pabyRLEBuf[nRLEBytes++] = static_cast<GByte>( nRepeatCount );
            pabyRLEBuf[nRLEBytes++] = pabyRawBuf[iX];
            iX += nRepeatCount;
        }
        else
        { // copy over mixed data.
            for( nRepeatCount = 1;
                 iX + nRepeatCount < image->xsize && nRepeatCount < 127;
                 nRepeatCount++ )
            {
                if( iX + nRepeatCount + 3 >= image->xsize )
                    continue;

                // quit if the next 3 pixels match
                if( pabyRawBuf[iX + nRepeatCount]
                    == pabyRawBuf[iX + nRepeatCount+1]
                    && pabyRawBuf[iX + nRepeatCount]
                    == pabyRawBuf[iX + nRepeatCount+2] )
                    break;
            }

            pabyRLEBuf[nRLEBytes++] = static_cast<GByte>( 0x80 | nRepeatCount );
            memcpy( pabyRLEBuf + nRLEBytes,
                    pabyRawBuf + iX,
                    nRepeatCount );

            nRLEBytes += nRepeatCount;
            iX += nRepeatCount;
        }
    }

    // EOL marker.
    pabyRLEBuf[nRLEBytes++] = 0;

/* -------------------------------------------------------------------- */
/*      Write RLE Buffer at end of file.                                */
/* -------------------------------------------------------------------- */
    const int row = (image->ysize - nBlockYOff - 1) + (nBand-1) * image->ysize;

    VSIFSeekL(image->file, 0, SEEK_END );

    image->rowStart[row] = static_cast<GUInt32>( VSIFTellL( image->file ) );
    image->rowSize[row] = nRLEBytes;
    image->rleTableDirty = TRUE;

    if( static_cast<int>( VSIFWriteL(pabyRLEBuf, 1, nRLEBytes, image->file) )
        != nRLEBytes )
    {
        CPLFree( pabyRLEBuf );
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "file write error: row (%d)\n", nBlockYOff );
        return CE_Failure;
    }

    CPLFree( pabyRLEBuf );

    return CE_None;
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp SGIRasterBand::GetColorInterpretation()

{
    SGIDataset* poGDS = reinterpret_cast<SGIDataset *>( poDS );

    if(poGDS->nBands == 1)
        return GCI_GrayIndex;
    else if(poGDS->nBands == 2)
    {
        if(nBand == 1)
            return GCI_GrayIndex;
        else
            return GCI_AlphaBand;
    }
    else if(poGDS->nBands == 3)
    {
        if(nBand == 1)
            return GCI_RedBand;
        else if(nBand == 2)
            return GCI_GreenBand;
        else
            return GCI_BlueBand;
    }
    else if(poGDS->nBands == 4)
    {
        if(nBand == 1)
            return GCI_RedBand;
        else if(nBand == 2)
            return GCI_GreenBand;
        else if(nBand == 3)
            return GCI_BlueBand;
        else
            return GCI_AlphaBand;
    }
    return GCI_Undefined;
}

/************************************************************************/
/* ==================================================================== */
/*                             SGIDataset                               */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                            SGIDataset()                              */
/************************************************************************/

SGIDataset::SGIDataset() :
    fpImage(nullptr),
    bGeoTransformValid(FALSE)
{
    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;
}

/************************************************************************/
/*                           ~SGIDataset()                            */
/************************************************************************/

SGIDataset::~SGIDataset()

{
    FlushCache(true);

    // Do we need to write out rle table?
    if( image.rleTableDirty )
    {
        CPLDebug( "SGI", "Flushing RLE offset table." );
        ConvertLong( image.rowStart, image.ysize * image.zsize );
        ConvertLong( reinterpret_cast<GUInt32 *>( image.rowSize ),
                     image.ysize * image.zsize );

        VSIFSeekL( fpImage, 512, SEEK_SET );
        size_t nSize = static_cast<size_t>(image.ysize) * static_cast<size_t>(image.zsize);
        VSIFWriteL( image.rowStart, 4, nSize, fpImage );
        VSIFWriteL( image.rowSize, 4, nSize, fpImage );
        image.rleTableDirty = FALSE;
    }

    if(fpImage != nullptr)
        VSIFCloseL(fpImage);

    CPLFree(image.tmp);
    CPLFree(image.rowSize);
    CPLFree(image.rowStart);
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

CPLErr SGIDataset::GetGeoTransform(double * padfTransform)

{
    if(bGeoTransformValid)
    {
        memcpy(padfTransform, adfGeoTransform, sizeof(double)*6);
        return CE_None;
    }

    return GDALPamDataset::GetGeoTransform(padfTransform);
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset* SGIDataset::Open(GDALOpenInfo* poOpenInfo)

{
/* -------------------------------------------------------------------- */
/*      First we check to see if the file has the expected header       */
/*      bytes.                                                          */
/* -------------------------------------------------------------------- */
    if(poOpenInfo->nHeaderBytes < 12 || poOpenInfo->fpL == nullptr )
        return nullptr;

    ImageRec tmpImage;
    memcpy(&tmpImage.imagic, poOpenInfo->pabyHeader + 0, 2);
    memcpy(&tmpImage.type,   poOpenInfo->pabyHeader + 2, 1);
    memcpy(&tmpImage.bpc,    poOpenInfo->pabyHeader + 3, 1);
    memcpy(&tmpImage.dim,    poOpenInfo->pabyHeader + 4, 2);
    memcpy(&tmpImage.xsize,  poOpenInfo->pabyHeader + 6, 2);
    memcpy(&tmpImage.ysize,  poOpenInfo->pabyHeader + 8, 2);
    memcpy(&tmpImage.zsize,  poOpenInfo->pabyHeader + 10, 2);
    tmpImage.Swap();

    if(tmpImage.imagic != 474)
        return nullptr;

    if (tmpImage.type != 0 && tmpImage.type != 1)
        return nullptr;

    if (tmpImage.bpc != 1 && tmpImage.bpc != 2)
        return nullptr;

    if (tmpImage.dim != 1 && tmpImage.dim != 2 && tmpImage.dim != 3)
        return nullptr;

    if(tmpImage.bpc != 1)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "The SGI driver only supports 1 byte channel values.\n");
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    SGIDataset* poDS = new SGIDataset();
    poDS->eAccess = poOpenInfo->eAccess;
    poDS->fpImage = poOpenInfo->fpL;
    poOpenInfo->fpL = nullptr;

/* -------------------------------------------------------------------- */
/*      Read pre-image data after ensuring the file is rewound.         */
/* -------------------------------------------------------------------- */
    VSIFSeekL(poDS->fpImage, 0, SEEK_SET);
    if(VSIFReadL(reinterpret_cast<void*>( &(poDS->image) ),
                 1, 12, poDS->fpImage)
       != 12)
    {
        CPLError(CE_Failure, CPLE_OpenFailed, "file read error while reading header in sgidataset.cpp");
        delete poDS;
        return nullptr;
    }
    poDS->image.Swap();
    poDS->image.file = poDS->fpImage;
    poDS->image.fileName = poOpenInfo->pszFilename;

/* -------------------------------------------------------------------- */
/*      Capture some information from the file that is of interest.     */
/* -------------------------------------------------------------------- */
    poDS->nRasterXSize = poDS->image.xsize;
    poDS->nRasterYSize = poDS->image.ysize;
    if (poDS->nRasterXSize <= 0 || poDS->nRasterYSize <= 0)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                     "Invalid image dimensions : %d x %d", poDS->nRasterXSize, poDS->nRasterYSize);
        delete poDS;
        return nullptr;
    }
    poDS->nBands = std::max(static_cast<GUInt16>(1), poDS->image.zsize);
    if (poDS->nBands > 256)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                     "Too many bands : %d", poDS->nBands);
        delete poDS;
        return nullptr;
    }

    const int numItems
        = (static_cast<int>( poDS->image.bpc ) == 1) ? 256 : 65536;
    if( poDS->image.xsize > INT_MAX / numItems )
    {
        delete poDS;
        return nullptr;
    }
    poDS->image.tmpSize = poDS->image.xsize * numItems;
    poDS->image.tmp = (unsigned char*)VSI_CALLOC_VERBOSE(poDS->image.xsize,numItems);
    if (poDS->image.tmp == nullptr)
    {
        delete poDS;
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Read RLE Pointer tables.                                        */
/* -------------------------------------------------------------------- */
    if( static_cast<int>( poDS->image.type ) == 1 ) // RLE compressed
    {
        const size_t x = static_cast<size_t>(poDS->image.ysize) * poDS->nBands * sizeof(GUInt32);
        poDS->image.rowStart = reinterpret_cast<GUInt32*>(
            VSI_MALLOC2_VERBOSE(poDS->image.ysize, poDS->nBands * sizeof(GUInt32) ) );
        poDS->image.rowSize = reinterpret_cast<GInt32 *>(
            VSI_MALLOC2_VERBOSE(poDS->image.ysize, poDS->nBands * sizeof(GUInt32) ) );
        if (poDS->image.rowStart == nullptr || poDS->image.rowSize == nullptr)
        {
            delete poDS;
            return nullptr;
        }
        memset(poDS->image.rowStart, 0, x);
        memset(poDS->image.rowSize, 0, x);
        poDS->image.rleEnd = static_cast<GUInt32>(512 + (2 * x));
        VSIFSeekL(poDS->fpImage, 512, SEEK_SET);
        if( VSIFReadL(poDS->image.rowStart, 1, x, poDS->image.file ) != x )
        {
            delete poDS;
            CPLError(CE_Failure, CPLE_OpenFailed,
                     "file read error while reading start positions in sgidataset.cpp");
            return nullptr;
        }
        if( VSIFReadL(poDS->image.rowSize, 1, x, poDS->image.file) != x)
        {
            delete poDS;
            CPLError(CE_Failure, CPLE_OpenFailed,
                     "file read error while reading row lengths in sgidataset.cpp");
            return nullptr;
        }
        ConvertLong(poDS->image.rowStart,
                    static_cast<int>(x / static_cast<int>( sizeof(GUInt32))) );
        ConvertLong(reinterpret_cast<GUInt32 *>( poDS->image.rowSize ),
                    static_cast<int>(x / static_cast<int>( sizeof(GInt32) )) );
    }
    else // uncompressed.
    {
        poDS->image.rowStart = nullptr;
        poDS->image.rowSize = nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    for(int iBand = 0; iBand < poDS->nBands; iBand++)
        poDS->SetBand(iBand+1, new SGIRasterBand(poDS, iBand+1));

/* -------------------------------------------------------------------- */
/*      Check for world file.                                           */
/* -------------------------------------------------------------------- */
    poDS->bGeoTransformValid =
        GDALReadWorldFile(poOpenInfo->pszFilename, ".wld",
                          poDS->adfGeoTransform);

/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */
    poDS->SetDescription(poOpenInfo->pszFilename);
    poDS->TryLoadXML();

/* -------------------------------------------------------------------- */
/*      Check for overviews.                                            */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );

    return poDS;
}

/************************************************************************/
/*                               Create()                               */
/************************************************************************/

GDALDataset *SGIDataset::Create( const char * pszFilename,
                                 int nXSize,
                                 int nYSize,
                                 int nBandsIn,
                                 GDALDataType eType,
                                 CPL_UNUSED char **papszOptions )
{
    if( eType != GDT_Byte )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
              "Attempt to create SGI dataset with an illegal\n"
              "data type (%s), only Byte supported by the format.\n",
              GDALGetDataTypeName(eType) );

        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Open the file for output.                                       */
/* -------------------------------------------------------------------- */
    VSILFILE *fp = VSIFOpenL( pszFilename, "w" );
    if( fp == nullptr )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Failed to create file '%s': %s",
                  pszFilename, VSIStrerror( errno ) );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Prepare and write 512 byte header.                              */
/* -------------------------------------------------------------------- */
    GByte abyHeader[512];

    memset( abyHeader, 0, 512 );

    abyHeader[0] = 1;
    abyHeader[1] = 218;
    abyHeader[2] = 1; // RLE
    abyHeader[3] = 1;  // 8bit

    GInt16 nShortValue;
    if( nBandsIn == 1 )
        nShortValue = CPL_MSBWORD16(2);
    else
        nShortValue = CPL_MSBWORD16(3);
    memcpy( abyHeader + 4, &nShortValue, 2 );

    nShortValue = CPL_MSBWORD16(nXSize);
    memcpy( abyHeader + 6, &nShortValue, 2 );

    nShortValue = CPL_MSBWORD16(nYSize);
    memcpy( abyHeader + 8, &nShortValue, 2 );

    nShortValue = CPL_MSBWORD16(nBandsIn);
    memcpy( abyHeader + 10, &nShortValue, 2 );

    GInt32 nIntValue = CPL_MSBWORD32(0);
    memcpy( abyHeader + 12, &nIntValue, 4 );

    GUInt32 nUIntValue = CPL_MSBWORD32(255);
    memcpy( abyHeader + 16, &nUIntValue, 4 );

    VSIFWriteL( abyHeader, 1, 512, fp );

/* -------------------------------------------------------------------- */
/*      Create our RLE compressed zero-ed dummy line.                   */
/* -------------------------------------------------------------------- */
    GByte *pabyRLELine = reinterpret_cast<GByte *>(
        CPLMalloc( ( nXSize / 127 ) * 2 + 4 ) );

    int nPixelsRemaining = nXSize;
    GInt32 nRLEBytes = 0;
    while( nPixelsRemaining > 0 )
    {
        pabyRLELine[nRLEBytes] = static_cast<GByte>(
            std::min( 127, nPixelsRemaining  ) );
        pabyRLELine[nRLEBytes+1] = 0;
        nPixelsRemaining -= pabyRLELine[nRLEBytes];

        nRLEBytes += 2;
    }

/* -------------------------------------------------------------------- */
/*      Prepare and write RLE offset/size tables with everything        */
/*      zeroed indicating dummy lines.                                  */
/* -------------------------------------------------------------------- */
    const int nTableLen = nYSize * nBandsIn;
    GInt32 nDummyRLEOffset = 512 + 4 * nTableLen * 2;

    CPL_MSBPTR32( &nRLEBytes );
    CPL_MSBPTR32( &nDummyRLEOffset );

    for( int i = 0; i < nTableLen; i++ )
        VSIFWriteL( &nDummyRLEOffset, 1, 4, fp );

    for( int i = 0; i < nTableLen; i++ )
        VSIFWriteL( &nRLEBytes, 1, 4, fp );

/* -------------------------------------------------------------------- */
/*      write the dummy RLE blank line.                                 */
/* -------------------------------------------------------------------- */
    CPL_MSBPTR32( &nRLEBytes );
    if( static_cast<GInt32>( VSIFWriteL( pabyRLELine, 1, nRLEBytes, fp ) )
        != nRLEBytes )
    {
        CPLError( CE_Failure, CPLE_FileIO,
                  "Failure writing SGI file '%s'.\n%s",
                  pszFilename,
                  VSIStrerror( errno ) );
        VSIFCloseL( fp );
        CPLFree( pabyRLELine );
        return nullptr;
    }

    VSIFCloseL( fp );
    CPLFree( pabyRLELine );

    return reinterpret_cast<GDALDataset *>(
        GDALOpen( pszFilename, GA_Update ) );
}

/************************************************************************/
/*                         GDALRegister_SGI()                           */
/************************************************************************/

void GDALRegister_SGI()

{
    if( GDALGetDriverByName( "SGI" ) != nullptr )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("SGI");
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "SGI Image File Format 1.0" );
    poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "rgb" );
    poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/rgb" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/raster/sgi.html" );
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte" );
    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );

    poDriver->pfnOpen = SGIDataset::Open;
    poDriver->pfnCreate = SGIDataset::Create;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}
