mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-19 14:15:21 -06:00
1553 lines
56 KiB
C++
1553 lines
56 KiB
C++
/******************************************************************************
|
|
* $Id: bmpdataset.cpp 28234 2014-12-27 14:22:54Z rouault $
|
|
*
|
|
* Project: Microsoft Windows Bitmap
|
|
* Purpose: Read/write MS Windows Device Independent Bitmap (DIB) files
|
|
* and OS/2 Presentation Manager bitmaps v. 1.x and v. 2.x
|
|
* Author: Andrey Kiselev, dron@remotesensing.org
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2002, Andrey Kiselev <dron@remotesensing.org>
|
|
* Copyright (c) 2007-2010, Even Rouault <even dot rouault at mines-paris dot org>
|
|
*
|
|
* 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 "gdal_pam.h"
|
|
#include "cpl_string.h"
|
|
|
|
CPL_CVSID("$Id: bmpdataset.cpp 28234 2014-12-27 14:22:54Z rouault $");
|
|
|
|
CPL_C_START
|
|
void GDALRegister_BMP(void);
|
|
CPL_C_END
|
|
|
|
// Enable if you want to see lots of BMP debugging output.
|
|
// #define BMP_DEBUG
|
|
|
|
enum BMPType
|
|
{
|
|
BMPT_WIN4, // BMP used in Windows 3.0/NT 3.51/95
|
|
BMPT_WIN5, // BMP used in Windows NT 4.0/98/Me/2000/XP
|
|
BMPT_OS21, // BMP used in OS/2 PM 1.x
|
|
BMPT_OS22 // BMP used in OS/2 PM 2.x
|
|
};
|
|
|
|
// Bitmap file consists of a BMPFileHeader structure followed by a
|
|
// BMPInfoHeader structure. An array of BMPColorEntry structures (also called
|
|
// a colour table) follows the bitmap information header structure. The colour
|
|
// table is followed by a second array of indexes into the colour table (the
|
|
// actual bitmap data). Data may be comressed, for 4-bpp and 8-bpp used RLE
|
|
// compression.
|
|
//
|
|
// +---------------------+
|
|
// | BMPFileHeader |
|
|
// +---------------------+
|
|
// | BMPInfoHeader |
|
|
// +---------------------+
|
|
// | BMPColorEntry array |
|
|
// +---------------------+
|
|
// | Colour-index array |
|
|
// +---------------------+
|
|
//
|
|
// All numbers stored in Intel order with least significant byte first.
|
|
|
|
enum BMPComprMethod
|
|
{
|
|
BMPC_RGB = 0L, // Uncompressed
|
|
BMPC_RLE8 = 1L, // RLE for 8 bpp images
|
|
BMPC_RLE4 = 2L, // RLE for 4 bpp images
|
|
BMPC_BITFIELDS = 3L, // Bitmap is not compressed and the colour table
|
|
// consists of three DWORD color masks that specify
|
|
// the red, green, and blue components of each pixel.
|
|
// This is valid when used with 16- and 32-bpp bitmaps.
|
|
BMPC_JPEG = 4L, // Indicates that the image is a JPEG image.
|
|
BMPC_PNG = 5L // Indicates that the image is a PNG image.
|
|
};
|
|
|
|
enum BMPLCSType // Type of logical color space.
|
|
{
|
|
BMPLT_CALIBRATED_RGB = 0, // This value indicates that endpoints and gamma
|
|
// values are given in the appropriate fields.
|
|
BMPLT_DEVICE_RGB = 1,
|
|
BMPLT_DEVICE_CMYK = 2
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
GInt32 iCIEX;
|
|
GInt32 iCIEY;
|
|
GInt32 iCIEZ;
|
|
} BMPCIEXYZ;
|
|
|
|
typedef struct // This structure contains the x, y, and z
|
|
{ // coordinates of the three colors that correspond
|
|
BMPCIEXYZ iCIERed; // to the red, green, and blue endpoints for
|
|
BMPCIEXYZ iCIEGreen; // a specified logical color space.
|
|
BMPCIEXYZ iCIEBlue;
|
|
} BMPCIEXYZTriple;
|
|
|
|
typedef struct
|
|
{
|
|
GByte bType[2]; // Signature "BM"
|
|
GUInt32 iSize; // Size in bytes of the bitmap file. Should always
|
|
// be ignored while reading because of error
|
|
// in Windows 3.0 SDK's description of this field
|
|
GUInt16 iReserved1; // Reserved, set as 0
|
|
GUInt16 iReserved2; // Reserved, set as 0
|
|
GUInt32 iOffBits; // Offset of the image from file start in bytes
|
|
} BMPFileHeader;
|
|
|
|
// File header size in bytes:
|
|
const int BFH_SIZE = 14;
|
|
|
|
typedef struct
|
|
{
|
|
GUInt32 iSize; // Size of BMPInfoHeader structure in bytes.
|
|
// Should be used to determine start of the
|
|
// colour table
|
|
GInt32 iWidth; // Image width
|
|
GInt32 iHeight; // Image height. If positive, image has bottom left
|
|
// origin, if negative --- top left.
|
|
GUInt16 iPlanes; // Number of image planes (must be set to 1)
|
|
GUInt16 iBitCount; // Number of bits per pixel (1, 4, 8, 16, 24 or 32).
|
|
// If 0 then the number of bits per pixel is
|
|
// specified or is implied by the JPEG or PNG format.
|
|
BMPComprMethod iCompression; // Compression method
|
|
GUInt32 iSizeImage; // Size of uncomressed image in bytes. May be 0
|
|
// for BMPC_RGB bitmaps. If iCompression is BI_JPEG
|
|
// or BI_PNG, iSizeImage indicates the size
|
|
// of the JPEG or PNG image buffer.
|
|
GInt32 iXPelsPerMeter; // X resolution, pixels per meter (0 if not used)
|
|
GInt32 iYPelsPerMeter; // Y resolution, pixels per meter (0 if not used)
|
|
GUInt32 iClrUsed; // Size of colour table. If 0, iBitCount should
|
|
// be used to calculate this value (1<<iBitCount)
|
|
GUInt32 iClrImportant; // Number of important colours. If 0, all
|
|
// colours are required
|
|
|
|
// Fields above should be used for bitmaps, compatible with Windows NT 3.51
|
|
// and earlier. Windows 98/Me, Windows 2000/XP introduces additional fields:
|
|
|
|
GUInt32 iRedMask; // Colour mask that specifies the red component
|
|
// of each pixel, valid only if iCompression
|
|
// is set to BI_BITFIELDS.
|
|
GUInt32 iGreenMask; // The same for green component
|
|
GUInt32 iBlueMask; // The same for blue component
|
|
GUInt32 iAlphaMask; // Colour mask that specifies the alpha
|
|
// component of each pixel.
|
|
BMPLCSType iCSType; // Colour space of the DIB.
|
|
BMPCIEXYZTriple sEndpoints; // This member is ignored unless the iCSType member
|
|
// specifies BMPLT_CALIBRATED_RGB.
|
|
GUInt32 iGammaRed; // Toned response curve for red. This member
|
|
// is ignored unless color values are calibrated
|
|
// RGB values and iCSType is set to
|
|
// BMPLT_CALIBRATED_RGB. Specified in 16^16 format.
|
|
GUInt32 iGammaGreen; // Toned response curve for green.
|
|
GUInt32 iGammaBlue; // Toned response curve for blue.
|
|
} BMPInfoHeader;
|
|
|
|
// Info header size in bytes:
|
|
const unsigned int BIH_WIN4SIZE = 40; // for BMPT_WIN4
|
|
#if 0 /* Unused */
|
|
const unsigned int BIH_WIN5SIZE = 57; // for BMPT_WIN5
|
|
#endif
|
|
const unsigned int BIH_OS21SIZE = 12; // for BMPT_OS21
|
|
const unsigned int BIH_OS22SIZE = 64; // for BMPT_OS22
|
|
|
|
// We will use plain byte array instead of this structure, but declaration
|
|
// provided for reference
|
|
typedef struct
|
|
{
|
|
GByte bBlue;
|
|
GByte bGreen;
|
|
GByte bRed;
|
|
GByte bReserved; // Must be 0
|
|
} BMPColorEntry;
|
|
|
|
/*****************************************************************/
|
|
|
|
int countonbits(GUInt32 dw)
|
|
{
|
|
int r = 0;
|
|
for(int x = 0; x < 32; x++)
|
|
{
|
|
if((dw & (1 << x)) != 0)
|
|
r++;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
|
|
int findfirstonbit(GUInt32 n)
|
|
{
|
|
for(int x = 0; x < 32; x++)
|
|
{
|
|
if((n & (1 << x)) != 0)
|
|
return x;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* BMPDataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class BMPDataset : public GDALPamDataset
|
|
{
|
|
friend class BMPRasterBand;
|
|
friend class BMPComprRasterBand;
|
|
|
|
BMPFileHeader sFileHeader;
|
|
BMPInfoHeader sInfoHeader;
|
|
int nColorTableSize, nColorElems;
|
|
GByte *pabyColorTable;
|
|
GDALColorTable *poColorTable;
|
|
double adfGeoTransform[6];
|
|
int bGeoTransformValid;
|
|
|
|
char *pszFilename;
|
|
VSILFILE *fp;
|
|
|
|
protected:
|
|
virtual CPLErr IRasterIO( GDALRWFlag, int, int, int, int,
|
|
void *, int, int, GDALDataType,
|
|
int, int *,
|
|
GSpacing nPixelSpace, GSpacing nLineSpace,
|
|
GSpacing nBandSpace,
|
|
GDALRasterIOExtraArg* psExtraArg );
|
|
|
|
public:
|
|
BMPDataset();
|
|
~BMPDataset();
|
|
|
|
static int Identify( GDALOpenInfo * );
|
|
static GDALDataset *Open( GDALOpenInfo * );
|
|
static GDALDataset *Create( const char * pszFilename,
|
|
int nXSize, int nYSize, int nBands,
|
|
GDALDataType eType, char ** papszParmList );
|
|
|
|
CPLErr GetGeoTransform( double * padfTransform );
|
|
virtual CPLErr SetGeoTransform( double * );
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* BMPRasterBand */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class BMPRasterBand : public GDALPamRasterBand
|
|
{
|
|
friend class BMPDataset;
|
|
|
|
protected:
|
|
|
|
GUInt32 nScanSize;
|
|
unsigned int iBytesPerPixel;
|
|
GByte *pabyScan;
|
|
|
|
public:
|
|
|
|
BMPRasterBand( BMPDataset *, int );
|
|
~BMPRasterBand();
|
|
|
|
virtual CPLErr IReadBlock( int, int, void * );
|
|
virtual CPLErr IWriteBlock( int, int, void * );
|
|
virtual GDALColorInterp GetColorInterpretation();
|
|
virtual GDALColorTable *GetColorTable();
|
|
CPLErr SetColorTable( GDALColorTable * );
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* BMPRasterBand() */
|
|
/************************************************************************/
|
|
|
|
BMPRasterBand::BMPRasterBand( BMPDataset *poDS, int nBand )
|
|
{
|
|
this->poDS = poDS;
|
|
this->nBand = nBand;
|
|
eDataType = GDT_Byte;
|
|
iBytesPerPixel = poDS->sInfoHeader.iBitCount / 8;
|
|
|
|
// We will read one scanline per time. Scanlines in BMP aligned at 4-byte
|
|
// boundary
|
|
nBlockXSize = poDS->GetRasterXSize();
|
|
|
|
if (nBlockXSize < (INT_MAX - 31) / poDS->sInfoHeader.iBitCount)
|
|
nScanSize =
|
|
((poDS->GetRasterXSize() * poDS->sInfoHeader.iBitCount + 31) & ~31) / 8;
|
|
else
|
|
{
|
|
pabyScan = NULL;
|
|
return;
|
|
}
|
|
nBlockYSize = 1;
|
|
|
|
#ifdef BMP_DEBUG
|
|
CPLDebug( "BMP",
|
|
"Band %d: set nBlockXSize=%d, nBlockYSize=%d, nScanSize=%d",
|
|
nBand, nBlockXSize, nBlockYSize, nScanSize );
|
|
#endif
|
|
|
|
pabyScan = (GByte *) VSIMalloc( nScanSize );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~BMPRasterBand() */
|
|
/************************************************************************/
|
|
|
|
BMPRasterBand::~BMPRasterBand()
|
|
{
|
|
CPLFree( pabyScan );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr BMPRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
|
|
int nBlockYOff,
|
|
void * pImage )
|
|
{
|
|
BMPDataset *poGDS = (BMPDataset *) poDS;
|
|
GUInt32 iScanOffset;
|
|
int i;
|
|
|
|
if ( poGDS->sInfoHeader.iHeight > 0 )
|
|
iScanOffset = poGDS->sFileHeader.iOffBits +
|
|
( poGDS->GetRasterYSize() - nBlockYOff - 1 ) * nScanSize;
|
|
else
|
|
iScanOffset = poGDS->sFileHeader.iOffBits + nBlockYOff * nScanSize;
|
|
|
|
if ( VSIFSeekL( poGDS->fp, iScanOffset, SEEK_SET ) < 0 )
|
|
{
|
|
// XXX: We will not report error here, because file just may be
|
|
// in update state and data for this block will be available later
|
|
if( poGDS->eAccess == GA_Update )
|
|
{
|
|
memset( pImage, 0, nBlockXSize );
|
|
return CE_None;
|
|
}
|
|
else
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Can't seek to offset %ld in input file to read data.",
|
|
(long) iScanOffset );
|
|
return CE_Failure;
|
|
}
|
|
}
|
|
if ( VSIFReadL( pabyScan, 1, nScanSize, poGDS->fp ) < nScanSize )
|
|
{
|
|
// XXX
|
|
if( poGDS->eAccess == GA_Update )
|
|
{
|
|
memset( pImage, 0, nBlockXSize );
|
|
return CE_None;
|
|
}
|
|
else
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Can't read from offset %ld in input file.",
|
|
(long) iScanOffset );
|
|
return CE_Failure;
|
|
}
|
|
}
|
|
|
|
if ( poGDS->sInfoHeader.iBitCount == 24 ||
|
|
poGDS->sInfoHeader.iBitCount == 32 )
|
|
{
|
|
GByte *pabyTemp = pabyScan + 3 - nBand;
|
|
|
|
for ( i = 0; i < nBlockXSize; i++ )
|
|
{
|
|
// Colour triplets in BMP file organized in reverse order:
|
|
// blue, green, red. When we have 32-bit BMP the forth byte
|
|
// in quadriplet should be discarded as it has no meaning.
|
|
// That is why we always use 3 byte count in the following
|
|
// pabyTemp index.
|
|
((GByte *) pImage)[i] = *pabyTemp;
|
|
pabyTemp += iBytesPerPixel;
|
|
}
|
|
}
|
|
else if ( poGDS->sInfoHeader.iBitCount == 8 )
|
|
{
|
|
memcpy( pImage, pabyScan, nBlockXSize );
|
|
}
|
|
else if ( poGDS->sInfoHeader.iBitCount == 16 )
|
|
{
|
|
// rcg, oct 7/06: Byteswap if necessary, use int16
|
|
// references to file pixels, expand samples to
|
|
// 8-bit, support BMPC_BITFIELDS channel mask indicators,
|
|
// and generalize band handling.
|
|
|
|
GUInt16* pScan16 = (GUInt16*)pabyScan;
|
|
#ifdef CPL_MSB
|
|
GDALSwapWords( pScan16, sizeof(GUInt16), nBlockXSize, 0);
|
|
#endif
|
|
|
|
// todo: make these band members and precompute.
|
|
int mask[3], shift[3], size[3];
|
|
float fTo8bit[3];
|
|
|
|
if(poGDS->sInfoHeader.iCompression == BMPC_RGB)
|
|
{
|
|
mask[0] = 0x7c00;
|
|
mask[1] = 0x03e0;
|
|
mask[2] = 0x001f;
|
|
}
|
|
else if(poGDS->sInfoHeader.iCompression == BMPC_BITFIELDS)
|
|
{
|
|
mask[0] = poGDS->sInfoHeader.iRedMask;
|
|
mask[1] = poGDS->sInfoHeader.iGreenMask;
|
|
mask[2] = poGDS->sInfoHeader.iBlueMask;
|
|
}
|
|
else
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Unknown 16-bit compression %d.",
|
|
poGDS->sInfoHeader.iCompression);
|
|
return CE_Failure;
|
|
}
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
shift[i] = findfirstonbit(mask[i]);
|
|
size[i] = countonbits(mask[i]);
|
|
if(size[i] > 14 || size[i] == 0)
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Bad 16-bit channel mask %8x.",
|
|
mask[i]);
|
|
return CE_Failure;
|
|
}
|
|
fTo8bit[i] = 255.0f / ((1 << size[i])-1);
|
|
}
|
|
|
|
for ( i = 0; i < nBlockXSize; i++ )
|
|
{
|
|
((GByte *) pImage)[i] = (GByte)
|
|
(0.5f + fTo8bit[nBand-1] *
|
|
((pScan16[i] & mask[nBand-1]) >> shift[nBand-1]));
|
|
#if 0
|
|
// original code
|
|
switch ( nBand )
|
|
{
|
|
case 1: // Red
|
|
((GByte *) pImage)[i] = pabyScan[i + 1] & 0x1F;
|
|
break;
|
|
|
|
case 2: // Green
|
|
((GByte *) pImage)[i] =
|
|
((pabyScan[i] & 0x03) << 3) |
|
|
((pabyScan[i + 1] & 0xE0) >> 5);
|
|
break;
|
|
|
|
case 3: // Blue
|
|
((GByte *) pImage)[i] = (pabyScan[i] & 0x7c) >> 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
#endif // 0
|
|
}
|
|
}
|
|
else if ( poGDS->sInfoHeader.iBitCount == 4 )
|
|
{
|
|
GByte *pabyTemp = pabyScan;
|
|
|
|
for ( i = 0; i < nBlockXSize; i++ )
|
|
{
|
|
// Most significant part of the byte represents leftmost pixel
|
|
if ( i & 0x01 )
|
|
((GByte *) pImage)[i] = *pabyTemp++ & 0x0F;
|
|
else
|
|
((GByte *) pImage)[i] = (*pabyTemp & 0xF0) >> 4;
|
|
}
|
|
}
|
|
else if ( poGDS->sInfoHeader.iBitCount == 1 )
|
|
{
|
|
GByte *pabyTemp = pabyScan;
|
|
|
|
for ( i = 0; i < nBlockXSize; i++ )
|
|
{
|
|
switch ( i & 0x7 )
|
|
{
|
|
case 0:
|
|
((GByte *) pImage)[i] = (*pabyTemp & 0x80) >> 7;
|
|
break;
|
|
case 1:
|
|
((GByte *) pImage)[i] = (*pabyTemp & 0x40) >> 6;
|
|
break;
|
|
case 2:
|
|
((GByte *) pImage)[i] = (*pabyTemp & 0x20) >> 5;
|
|
break;
|
|
case 3:
|
|
((GByte *) pImage)[i] = (*pabyTemp & 0x10) >> 4;
|
|
break;
|
|
case 4:
|
|
((GByte *) pImage)[i] = (*pabyTemp & 0x08) >> 3;
|
|
break;
|
|
case 5:
|
|
((GByte *) pImage)[i] = (*pabyTemp & 0x04) >> 2;
|
|
break;
|
|
case 6:
|
|
((GByte *) pImage)[i] = (*pabyTemp & 0x02) >> 1;
|
|
break;
|
|
case 7:
|
|
((GByte *) pImage)[i] = *pabyTemp++ & 0x01;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IWriteBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr BMPRasterBand::IWriteBlock( int nBlockXOff, int nBlockYOff,
|
|
void * pImage )
|
|
{
|
|
BMPDataset *poGDS = (BMPDataset *)poDS;
|
|
int iInPixel, iOutPixel;
|
|
GUInt32 iScanOffset;
|
|
|
|
CPLAssert( poGDS != NULL
|
|
&& nBlockXOff >= 0
|
|
&& nBlockYOff >= 0
|
|
&& pImage != NULL );
|
|
|
|
iScanOffset = poGDS->sFileHeader.iOffBits +
|
|
( poGDS->GetRasterYSize() - nBlockYOff - 1 ) * nScanSize;
|
|
if ( VSIFSeekL( poGDS->fp, iScanOffset, SEEK_SET ) < 0 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Can't seek to offset %ld in output file to write data.\n%s",
|
|
(long) iScanOffset, VSIStrerror( errno ) );
|
|
return CE_Failure;
|
|
}
|
|
|
|
if( poGDS->nBands != 1 )
|
|
{
|
|
memset( pabyScan, 0, nScanSize );
|
|
VSIFReadL( pabyScan, 1, nScanSize, poGDS->fp );
|
|
VSIFSeekL( poGDS->fp, iScanOffset, SEEK_SET );
|
|
}
|
|
|
|
for ( iInPixel = 0, iOutPixel = iBytesPerPixel - nBand;
|
|
iInPixel < nBlockXSize; iInPixel++, iOutPixel += poGDS->nBands )
|
|
{
|
|
pabyScan[iOutPixel] = ((GByte *) pImage)[iInPixel];
|
|
}
|
|
|
|
if ( VSIFWriteL( pabyScan, 1, nScanSize, poGDS->fp ) < nScanSize )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Can't write block with X offset %d and Y offset %d.\n%s",
|
|
nBlockXOff, nBlockYOff,
|
|
VSIStrerror( errno ) );
|
|
return CE_Failure;
|
|
}
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetColorTable() */
|
|
/************************************************************************/
|
|
|
|
GDALColorTable *BMPRasterBand::GetColorTable()
|
|
{
|
|
BMPDataset *poGDS = (BMPDataset *) poDS;
|
|
|
|
return poGDS->poColorTable;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetColorTable() */
|
|
/************************************************************************/
|
|
|
|
CPLErr BMPRasterBand::SetColorTable( GDALColorTable *poColorTable )
|
|
{
|
|
BMPDataset *poGDS = (BMPDataset *) poDS;
|
|
|
|
if ( poColorTable )
|
|
{
|
|
GDALColorEntry oEntry;
|
|
GUInt32 iULong;
|
|
unsigned int i;
|
|
|
|
poGDS->sInfoHeader.iClrUsed = poColorTable->GetColorEntryCount();
|
|
if ( poGDS->sInfoHeader.iClrUsed < 1 ||
|
|
poGDS->sInfoHeader.iClrUsed > (1U << poGDS->sInfoHeader.iBitCount) )
|
|
return CE_Failure;
|
|
|
|
VSIFSeekL( poGDS->fp, BFH_SIZE + 32, SEEK_SET );
|
|
|
|
iULong = CPL_LSBWORD32( poGDS->sInfoHeader.iClrUsed );
|
|
VSIFWriteL( &iULong, 4, 1, poGDS->fp );
|
|
poGDS->pabyColorTable = (GByte *) CPLRealloc( poGDS->pabyColorTable,
|
|
poGDS->nColorElems * poGDS->sInfoHeader.iClrUsed );
|
|
if ( !poGDS->pabyColorTable )
|
|
return CE_Failure;
|
|
|
|
for( i = 0; i < poGDS->sInfoHeader.iClrUsed; i++ )
|
|
{
|
|
poColorTable->GetColorEntryAsRGB( i, &oEntry );
|
|
poGDS->pabyColorTable[i * poGDS->nColorElems + 3] = 0;
|
|
poGDS->pabyColorTable[i * poGDS->nColorElems + 2] =
|
|
(GByte) oEntry.c1; // Red
|
|
poGDS->pabyColorTable[i * poGDS->nColorElems + 1] =
|
|
(GByte) oEntry.c2; // Green
|
|
poGDS->pabyColorTable[i * poGDS->nColorElems] =
|
|
(GByte) oEntry.c3; // Blue
|
|
}
|
|
|
|
VSIFSeekL( poGDS->fp, BFH_SIZE + poGDS->sInfoHeader.iSize, SEEK_SET );
|
|
if ( VSIFWriteL( poGDS->pabyColorTable, 1,
|
|
poGDS->nColorElems * poGDS->sInfoHeader.iClrUsed, poGDS->fp ) <
|
|
poGDS->nColorElems * (GUInt32) poGDS->sInfoHeader.iClrUsed )
|
|
{
|
|
return CE_Failure;
|
|
}
|
|
}
|
|
else
|
|
return CE_Failure;
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetColorInterpretation() */
|
|
/************************************************************************/
|
|
|
|
GDALColorInterp BMPRasterBand::GetColorInterpretation()
|
|
{
|
|
BMPDataset *poGDS = (BMPDataset *) poDS;
|
|
|
|
if( poGDS->sInfoHeader.iBitCount == 24 ||
|
|
poGDS->sInfoHeader.iBitCount == 32 ||
|
|
poGDS->sInfoHeader.iBitCount == 16 )
|
|
{
|
|
if( nBand == 1 )
|
|
return GCI_RedBand;
|
|
else if( nBand == 2 )
|
|
return GCI_GreenBand;
|
|
else if( nBand == 3 )
|
|
return GCI_BlueBand;
|
|
else
|
|
return GCI_Undefined;
|
|
}
|
|
else
|
|
{
|
|
return GCI_PaletteIndex;
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* BMPComprRasterBand */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class BMPComprRasterBand : public BMPRasterBand
|
|
{
|
|
friend class BMPDataset;
|
|
|
|
GByte *pabyComprBuf;
|
|
GByte *pabyUncomprBuf;
|
|
|
|
public:
|
|
|
|
BMPComprRasterBand( BMPDataset *, int );
|
|
~BMPComprRasterBand();
|
|
|
|
virtual CPLErr IReadBlock( int, int, void * );
|
|
// virtual CPLErr IWriteBlock( int, int, void * );
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* BMPComprRasterBand() */
|
|
/************************************************************************/
|
|
|
|
BMPComprRasterBand::BMPComprRasterBand( BMPDataset *poDS, int nBand )
|
|
: BMPRasterBand( poDS, nBand )
|
|
{
|
|
unsigned int i, j, k, iLength = 0;
|
|
GUInt32 iComprSize, iUncomprSize;
|
|
|
|
iComprSize = poDS->sFileHeader.iSize - poDS->sFileHeader.iOffBits;
|
|
iUncomprSize = poDS->GetRasterXSize() * poDS->GetRasterYSize();
|
|
|
|
#ifdef DEBUG
|
|
CPLDebug( "BMP", "RLE compression detected." );
|
|
CPLDebug ( "BMP", "Size of compressed buffer %ld bytes,"
|
|
" size of uncompressed buffer %ld bytes.",
|
|
(long) iComprSize, (long) iUncomprSize );
|
|
#endif
|
|
/* TODO: it might be interesting to avoid uncompressing the whole data */
|
|
/* in a single pass, especially if nXSize * nYSize is big */
|
|
/* We could read incrementally one row at a time */
|
|
if (poDS->GetRasterXSize() > INT_MAX / poDS->GetRasterYSize())
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported, "Too big dimensions : %d x %d",
|
|
poDS->GetRasterXSize(), poDS->GetRasterYSize());
|
|
pabyComprBuf = NULL;
|
|
pabyUncomprBuf = NULL;
|
|
return;
|
|
}
|
|
pabyComprBuf = (GByte *) VSIMalloc( iComprSize );
|
|
pabyUncomprBuf = (GByte *) VSIMalloc( iUncomprSize );
|
|
if (pabyComprBuf == NULL ||
|
|
pabyUncomprBuf == NULL)
|
|
{
|
|
CPLFree(pabyComprBuf);
|
|
pabyComprBuf = NULL;
|
|
CPLFree(pabyUncomprBuf);
|
|
pabyUncomprBuf = NULL;
|
|
return;
|
|
}
|
|
|
|
VSIFSeekL( poDS->fp, poDS->sFileHeader.iOffBits, SEEK_SET );
|
|
VSIFReadL( pabyComprBuf, 1, iComprSize, poDS->fp );
|
|
i = 0;
|
|
j = 0;
|
|
if ( poDS->sInfoHeader.iBitCount == 8 ) // RLE8
|
|
{
|
|
while( j < iUncomprSize && i < iComprSize )
|
|
{
|
|
if ( pabyComprBuf[i] )
|
|
{
|
|
iLength = pabyComprBuf[i++];
|
|
while( iLength > 0 && j < iUncomprSize && i < iComprSize )
|
|
{
|
|
pabyUncomprBuf[j++] = pabyComprBuf[i];
|
|
iLength--;
|
|
}
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
if ( pabyComprBuf[i] == 0 ) // Next scanline
|
|
{
|
|
i++;
|
|
}
|
|
else if ( pabyComprBuf[i] == 1 ) // End of image
|
|
{
|
|
break;
|
|
}
|
|
else if ( pabyComprBuf[i] == 2 ) // Move to...
|
|
{
|
|
i++;
|
|
if ( i < iComprSize - 1 )
|
|
{
|
|
j += pabyComprBuf[i] +
|
|
pabyComprBuf[i+1] * poDS->GetRasterXSize();
|
|
i += 2;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else // Absolute mode
|
|
{
|
|
if (i < iComprSize)
|
|
iLength = pabyComprBuf[i++];
|
|
for ( k = 0; k < iLength && j < iUncomprSize && i < iComprSize; k++ )
|
|
pabyUncomprBuf[j++] = pabyComprBuf[i++];
|
|
if ( i & 0x01 )
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // RLE4
|
|
{
|
|
while( j < iUncomprSize && i < iComprSize )
|
|
{
|
|
if ( pabyComprBuf[i] )
|
|
{
|
|
iLength = pabyComprBuf[i++];
|
|
while( iLength > 0 && j < iUncomprSize && i < iComprSize )
|
|
{
|
|
if ( iLength & 0x01 )
|
|
pabyUncomprBuf[j++] = (pabyComprBuf[i] & 0xF0) >> 4;
|
|
else
|
|
pabyUncomprBuf[j++] = pabyComprBuf[i] & 0x0F;
|
|
iLength--;
|
|
}
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
if ( pabyComprBuf[i] == 0 ) // Next scanline
|
|
{
|
|
i++;
|
|
}
|
|
else if ( pabyComprBuf[i] == 1 ) // End of image
|
|
{
|
|
break;
|
|
}
|
|
else if ( pabyComprBuf[i] == 2 ) // Move to...
|
|
{
|
|
i++;
|
|
if ( i < iComprSize - 1 )
|
|
{
|
|
j += pabyComprBuf[i] +
|
|
pabyComprBuf[i+1] * poDS->GetRasterXSize();
|
|
i += 2;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else // Absolute mode
|
|
{
|
|
if (i < iComprSize)
|
|
iLength = pabyComprBuf[i++];
|
|
for ( k = 0; k < iLength && j < iUncomprSize && i < iComprSize; k++ )
|
|
{
|
|
if ( k & 0x01 )
|
|
pabyUncomprBuf[j++] = pabyComprBuf[i++] & 0x0F;
|
|
else
|
|
pabyUncomprBuf[j++] = (pabyComprBuf[i] & 0xF0) >> 4;
|
|
}
|
|
if ( i & 0x01 )
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// rcg, release compressed buffer here.
|
|
if ( pabyComprBuf )
|
|
CPLFree( pabyComprBuf );
|
|
pabyComprBuf = NULL;
|
|
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~BMPComprRasterBand() */
|
|
/************************************************************************/
|
|
|
|
BMPComprRasterBand::~BMPComprRasterBand()
|
|
{
|
|
if ( pabyComprBuf )
|
|
CPLFree( pabyComprBuf );
|
|
if ( pabyUncomprBuf )
|
|
CPLFree( pabyUncomprBuf );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr BMPComprRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
|
|
int nBlockYOff,
|
|
void * pImage )
|
|
{
|
|
memcpy( pImage, pabyUncomprBuf +
|
|
(poDS->GetRasterYSize() - nBlockYOff - 1) * poDS->GetRasterXSize(),
|
|
nBlockXSize );
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* BMPDataset() */
|
|
/************************************************************************/
|
|
|
|
BMPDataset::BMPDataset()
|
|
{
|
|
pszFilename = NULL;
|
|
fp = NULL;
|
|
nBands = 0;
|
|
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;
|
|
pabyColorTable = NULL;
|
|
poColorTable = NULL;
|
|
memset( &sFileHeader, 0, sizeof(sFileHeader) );
|
|
memset( &sInfoHeader, 0, sizeof(sInfoHeader) );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~BMPDataset() */
|
|
/************************************************************************/
|
|
|
|
BMPDataset::~BMPDataset()
|
|
{
|
|
FlushCache();
|
|
|
|
if ( pabyColorTable )
|
|
CPLFree( pabyColorTable );
|
|
if ( poColorTable != NULL )
|
|
delete poColorTable;
|
|
if( fp != NULL )
|
|
VSIFCloseL( fp );
|
|
CPLFree( pszFilename );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr BMPDataset::GetGeoTransform( double * padfTransform )
|
|
{
|
|
if( bGeoTransformValid )
|
|
{
|
|
memcpy( padfTransform, adfGeoTransform, sizeof(adfGeoTransform[0])*6 );
|
|
return CE_None;
|
|
}
|
|
|
|
if( GDALPamDataset::GetGeoTransform( padfTransform ) == CE_None)
|
|
return CE_None;
|
|
|
|
#ifdef notdef
|
|
// See http://trac.osgeo.org/gdal/ticket/3578
|
|
if (sInfoHeader.iXPelsPerMeter > 0 && sInfoHeader.iYPelsPerMeter > 0)
|
|
{
|
|
padfTransform[1] = sInfoHeader.iXPelsPerMeter;
|
|
padfTransform[5] = -sInfoHeader.iYPelsPerMeter;
|
|
padfTransform[0] = -0.5*padfTransform[1];
|
|
padfTransform[3] = -0.5*padfTransform[5];
|
|
return CE_None;
|
|
}
|
|
#endif
|
|
|
|
return CE_Failure;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr BMPDataset::SetGeoTransform( double * padfTransform )
|
|
{
|
|
CPLErr eErr = CE_None;
|
|
|
|
if ( pszFilename && bGeoTransformValid )
|
|
{
|
|
memcpy( adfGeoTransform, padfTransform, sizeof(double) * 6 );
|
|
|
|
if ( GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform )
|
|
== FALSE )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO, "Can't write world file." );
|
|
eErr = CE_Failure;
|
|
}
|
|
return eErr;
|
|
}
|
|
else
|
|
return GDALPamDataset::SetGeoTransform( padfTransform );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IRasterIO() */
|
|
/* */
|
|
/* Multi-band raster io handler. We will use block based */
|
|
/* loading is used for multiband BMPs. That is because they */
|
|
/* are effectively pixel interleaved, so processing all bands */
|
|
/* for a given block together avoid extra seeks. */
|
|
/************************************************************************/
|
|
|
|
CPLErr BMPDataset::IRasterIO( GDALRWFlag eRWFlag,
|
|
int nXOff, int nYOff, int nXSize, int nYSize,
|
|
void *pData, int nBufXSize, int nBufYSize,
|
|
GDALDataType eBufType,
|
|
int nBandCount, int *panBandMap,
|
|
GSpacing nPixelSpace, GSpacing nLineSpace,
|
|
GSpacing nBandSpace,
|
|
GDALRasterIOExtraArg* psExtraArg )
|
|
|
|
{
|
|
if( nBandCount > 1 )
|
|
return GDALDataset::BlockBasedRasterIO(
|
|
eRWFlag, nXOff, nYOff, nXSize, nYSize,
|
|
pData, nBufXSize, nBufYSize, eBufType,
|
|
nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace, psExtraArg );
|
|
else
|
|
return
|
|
GDALDataset::IRasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize,
|
|
pData, nBufXSize, nBufYSize, eBufType,
|
|
nBandCount, panBandMap,
|
|
nPixelSpace, nLineSpace, nBandSpace, psExtraArg );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Identify() */
|
|
/************************************************************************/
|
|
|
|
int BMPDataset::Identify( GDALOpenInfo *poOpenInfo )
|
|
|
|
{
|
|
if( poOpenInfo->nHeaderBytes < 2
|
|
|| poOpenInfo->pabyHeader[0] != 'B'
|
|
|| poOpenInfo->pabyHeader[1] != 'M' )
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Open() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *BMPDataset::Open( GDALOpenInfo * poOpenInfo )
|
|
{
|
|
if( !Identify( poOpenInfo ) )
|
|
return NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create a corresponding GDALDataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
BMPDataset *poDS;
|
|
VSIStatBufL sStat;
|
|
|
|
poDS = new BMPDataset();
|
|
poDS->eAccess = poOpenInfo->eAccess;
|
|
|
|
if( poOpenInfo->eAccess == GA_ReadOnly )
|
|
poDS->fp = VSIFOpenL( poOpenInfo->pszFilename, "rb" );
|
|
else
|
|
poDS->fp = VSIFOpenL( poOpenInfo->pszFilename, "r+b" );
|
|
if ( !poDS->fp )
|
|
{
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
VSIStatL(poOpenInfo->pszFilename, &sStat);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read the BMPFileHeader. We need iOffBits value only */
|
|
/* -------------------------------------------------------------------- */
|
|
VSIFSeekL( poDS->fp, 10, SEEK_SET );
|
|
VSIFReadL( &poDS->sFileHeader.iOffBits, 1, 4, poDS->fp );
|
|
#ifdef CPL_MSB
|
|
CPL_SWAP32PTR( &poDS->sFileHeader.iOffBits );
|
|
#endif
|
|
poDS->sFileHeader.iSize = (GUInt32) sStat.st_size;
|
|
|
|
#ifdef BMP_DEBUG
|
|
CPLDebug( "BMP", "File size %d bytes.", poDS->sFileHeader.iSize );
|
|
CPLDebug( "BMP", "Image offset 0x%x bytes from file start.",
|
|
poDS->sFileHeader.iOffBits );
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read the BMPInfoHeader. */
|
|
/* -------------------------------------------------------------------- */
|
|
BMPType eBMPType;
|
|
|
|
VSIFSeekL( poDS->fp, BFH_SIZE, SEEK_SET );
|
|
VSIFReadL( &poDS->sInfoHeader.iSize, 1, 4, poDS->fp );
|
|
#ifdef CPL_MSB
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iSize );
|
|
#endif
|
|
|
|
if ( poDS->sInfoHeader.iSize == BIH_WIN4SIZE )
|
|
eBMPType = BMPT_WIN4;
|
|
else if ( poDS->sInfoHeader.iSize == BIH_OS21SIZE )
|
|
eBMPType = BMPT_OS21;
|
|
else if ( poDS->sInfoHeader.iSize == BIH_OS22SIZE ||
|
|
poDS->sInfoHeader.iSize == 16 )
|
|
eBMPType = BMPT_OS22;
|
|
else
|
|
eBMPType = BMPT_WIN5;
|
|
|
|
if ( eBMPType == BMPT_WIN4 || eBMPType == BMPT_WIN5 || eBMPType == BMPT_OS22 )
|
|
{
|
|
VSIFReadL( &poDS->sInfoHeader.iWidth, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iHeight, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iPlanes, 1, 2, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iBitCount, 1, 2, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iCompression, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iSizeImage, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iXPelsPerMeter, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iYPelsPerMeter, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iClrUsed, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iClrImportant, 1, 4, poDS->fp );
|
|
|
|
// rcg, read win4/5 fields. If we're reading a
|
|
// legacy header that ends at iClrImportant, it turns
|
|
// out that the three DWORD color table entries used
|
|
// by the channel masks start here anyway.
|
|
if(poDS->sInfoHeader.iCompression == BMPC_BITFIELDS)
|
|
{
|
|
VSIFReadL( &poDS->sInfoHeader.iRedMask, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iGreenMask, 1, 4, poDS->fp );
|
|
VSIFReadL( &poDS->sInfoHeader.iBlueMask, 1, 4, poDS->fp );
|
|
}
|
|
#ifdef CPL_MSB
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iWidth );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iHeight );
|
|
CPL_SWAP16PTR( &poDS->sInfoHeader.iPlanes );
|
|
CPL_SWAP16PTR( &poDS->sInfoHeader.iBitCount );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iCompression );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iSizeImage );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iXPelsPerMeter );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iYPelsPerMeter );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iClrUsed );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iClrImportant );
|
|
// rcg, swap win4/5 fields.
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iRedMask );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iGreenMask );
|
|
CPL_SWAP32PTR( &poDS->sInfoHeader.iBlueMask );
|
|
#endif
|
|
poDS->nColorElems = 4;
|
|
}
|
|
|
|
if ( eBMPType == BMPT_OS22 )
|
|
{
|
|
poDS->nColorElems = 3; // FIXME: different info in different documents regarding this!
|
|
}
|
|
|
|
if ( eBMPType == BMPT_OS21 )
|
|
{
|
|
GInt16 iShort;
|
|
|
|
VSIFReadL( &iShort, 1, 2, poDS->fp );
|
|
poDS->sInfoHeader.iWidth = CPL_LSBWORD16( iShort );
|
|
VSIFReadL( &iShort, 1, 2, poDS->fp );
|
|
poDS->sInfoHeader.iHeight = CPL_LSBWORD16( iShort );
|
|
VSIFReadL( &iShort, 1, 2, poDS->fp );
|
|
poDS->sInfoHeader.iPlanes = CPL_LSBWORD16( iShort );
|
|
VSIFReadL( &iShort, 1, 2, poDS->fp );
|
|
poDS->sInfoHeader.iBitCount = CPL_LSBWORD16( iShort );
|
|
poDS->sInfoHeader.iCompression = BMPC_RGB;
|
|
poDS->nColorElems = 3;
|
|
}
|
|
|
|
if ( poDS->sInfoHeader.iBitCount != 1 &&
|
|
poDS->sInfoHeader.iBitCount != 4 &&
|
|
poDS->sInfoHeader.iBitCount != 8 &&
|
|
poDS->sInfoHeader.iBitCount != 16 &&
|
|
poDS->sInfoHeader.iBitCount != 24 &&
|
|
poDS->sInfoHeader.iBitCount != 32 )
|
|
{
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef BMP_DEBUG
|
|
CPLDebug( "BMP", "Windows Device Independent Bitmap parameters:\n"
|
|
" info header size: %d bytes\n"
|
|
" width: %d\n height: %d\n planes: %d\n bpp: %d\n"
|
|
" compression: %d\n image size: %d bytes\n X resolution: %d\n"
|
|
" Y resolution: %d\n colours used: %d\n colours important: %d",
|
|
poDS->sInfoHeader.iSize,
|
|
poDS->sInfoHeader.iWidth, poDS->sInfoHeader.iHeight,
|
|
poDS->sInfoHeader.iPlanes, poDS->sInfoHeader.iBitCount,
|
|
poDS->sInfoHeader.iCompression, poDS->sInfoHeader.iSizeImage,
|
|
poDS->sInfoHeader.iXPelsPerMeter, poDS->sInfoHeader.iYPelsPerMeter,
|
|
poDS->sInfoHeader.iClrUsed, poDS->sInfoHeader.iClrImportant );
|
|
#endif
|
|
|
|
poDS->nRasterXSize = poDS->sInfoHeader.iWidth;
|
|
poDS->nRasterYSize = (poDS->sInfoHeader.iHeight > 0)?
|
|
poDS->sInfoHeader.iHeight:-poDS->sInfoHeader.iHeight;
|
|
|
|
if (poDS->nRasterXSize <= 0 || poDS->nRasterYSize <= 0)
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Invalid dimensions : %d x %d",
|
|
poDS->nRasterXSize, poDS->nRasterYSize);
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
switch ( poDS->sInfoHeader.iBitCount )
|
|
{
|
|
case 1:
|
|
case 4:
|
|
case 8:
|
|
{
|
|
int i;
|
|
|
|
poDS->nBands = 1;
|
|
// Allocate memory for colour table and read it
|
|
if ( poDS->sInfoHeader.iClrUsed )
|
|
poDS->nColorTableSize = poDS->sInfoHeader.iClrUsed;
|
|
else
|
|
poDS->nColorTableSize = 1 << poDS->sInfoHeader.iBitCount;
|
|
poDS->pabyColorTable =
|
|
(GByte *)VSIMalloc2( poDS->nColorElems, poDS->nColorTableSize );
|
|
if (poDS->pabyColorTable == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_OutOfMemory, "Color palette will be ignored");
|
|
poDS->nColorTableSize = 0;
|
|
break;
|
|
}
|
|
|
|
VSIFSeekL( poDS->fp, BFH_SIZE + poDS->sInfoHeader.iSize, SEEK_SET );
|
|
VSIFReadL( poDS->pabyColorTable, poDS->nColorElems,
|
|
poDS->nColorTableSize, poDS->fp );
|
|
|
|
GDALColorEntry oEntry;
|
|
poDS->poColorTable = new GDALColorTable();
|
|
for( i = 0; i < poDS->nColorTableSize; i++ )
|
|
{
|
|
oEntry.c1 = poDS->pabyColorTable[i * poDS->nColorElems + 2]; // Red
|
|
oEntry.c2 = poDS->pabyColorTable[i * poDS->nColorElems + 1]; // Green
|
|
oEntry.c3 = poDS->pabyColorTable[i * poDS->nColorElems]; // Blue
|
|
oEntry.c4 = 255;
|
|
|
|
poDS->poColorTable->SetColorEntry( i, &oEntry );
|
|
}
|
|
}
|
|
break;
|
|
case 16:
|
|
case 24:
|
|
case 32:
|
|
poDS->nBands = 3;
|
|
break;
|
|
default:
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create band information objects. */
|
|
/* -------------------------------------------------------------------- */
|
|
int iBand;
|
|
|
|
if ( poDS->sInfoHeader.iCompression == BMPC_RGB
|
|
|| poDS->sInfoHeader.iCompression == BMPC_BITFIELDS )
|
|
{
|
|
for( iBand = 1; iBand <= poDS->nBands; iBand++ )
|
|
{
|
|
BMPRasterBand* band = new BMPRasterBand( poDS, iBand );
|
|
poDS->SetBand( iBand, band );
|
|
if (band->pabyScan == NULL)
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"The BMP file is probably corrupted or too large. Image width = %d", poDS->nRasterXSize);
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
else if ( poDS->sInfoHeader.iCompression == BMPC_RLE8
|
|
|| poDS->sInfoHeader.iCompression == BMPC_RLE4 )
|
|
{
|
|
for( iBand = 1; iBand <= poDS->nBands; iBand++ )
|
|
{
|
|
BMPComprRasterBand* band = new BMPComprRasterBand( poDS, iBand );
|
|
poDS->SetBand( iBand, band);
|
|
if (band->pabyUncomprBuf == NULL)
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"The BMP file is probably corrupted or too large. Image width = %d", poDS->nRasterXSize);
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for world file. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->bGeoTransformValid =
|
|
GDALReadWorldFile( poOpenInfo->pszFilename, NULL,
|
|
poDS->adfGeoTransform );
|
|
|
|
if( !poDS->bGeoTransformValid )
|
|
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 *BMPDataset::Create( const char * pszFilename,
|
|
int nXSize, int nYSize, int nBands,
|
|
GDALDataType eType, char **papszOptions )
|
|
|
|
{
|
|
if( eType != GDT_Byte )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Attempt to create BMP dataset with an illegal\n"
|
|
"data type (%s), only Byte supported by the format.\n",
|
|
GDALGetDataTypeName(eType) );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if( nBands != 1 && nBands != 3 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"BMP driver doesn't support %d bands. Must be 1 or 3.\n",
|
|
nBands );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create the dataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
BMPDataset *poDS;
|
|
|
|
poDS = new BMPDataset();
|
|
|
|
poDS->fp = VSIFOpenL( pszFilename, "wb+" );
|
|
if( poDS->fp == NULL )
|
|
{
|
|
CPLError( CE_Failure, CPLE_OpenFailed,
|
|
"Unable to create file %s.\n",
|
|
pszFilename );
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
poDS->pszFilename = CPLStrdup(pszFilename);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Fill the BMPInfoHeader */
|
|
/* -------------------------------------------------------------------- */
|
|
GUInt32 nScanSize;
|
|
|
|
poDS->sInfoHeader.iSize = 40;
|
|
poDS->sInfoHeader.iWidth = nXSize;
|
|
poDS->sInfoHeader.iHeight = nYSize;
|
|
poDS->sInfoHeader.iPlanes = 1;
|
|
poDS->sInfoHeader.iBitCount = ( nBands == 3 )?24:8;
|
|
poDS->sInfoHeader.iCompression = BMPC_RGB;
|
|
|
|
/* XXX: Avoid integer overflow. We can calculate size in one
|
|
* step using
|
|
*
|
|
* nScanSize = ((poDS->sInfoHeader.iWidth *
|
|
* poDS->sInfoHeader.iBitCount + 31) & ~31) / 8
|
|
*
|
|
* formulae, but we should check for overflow conditions
|
|
* during calculation.
|
|
*/
|
|
nScanSize =
|
|
(GUInt32)poDS->sInfoHeader.iWidth * poDS->sInfoHeader.iBitCount + 31;
|
|
if ( !poDS->sInfoHeader.iWidth
|
|
|| !poDS->sInfoHeader.iBitCount
|
|
|| (nScanSize - 31) / poDS->sInfoHeader.iBitCount
|
|
!= (GUInt32)poDS->sInfoHeader.iWidth )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Wrong image parameters; "
|
|
"can't allocate space for scanline buffer" );
|
|
delete poDS;
|
|
|
|
return NULL;
|
|
}
|
|
nScanSize = (nScanSize & ~31) / 8;
|
|
|
|
poDS->sInfoHeader.iSizeImage = nScanSize * poDS->sInfoHeader.iHeight;
|
|
poDS->sInfoHeader.iXPelsPerMeter = 0;
|
|
poDS->sInfoHeader.iYPelsPerMeter = 0;
|
|
poDS->nColorElems = 4;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do we need colour table? */
|
|
/* -------------------------------------------------------------------- */
|
|
unsigned int i;
|
|
|
|
if ( nBands == 1 )
|
|
{
|
|
poDS->sInfoHeader.iClrUsed = 1 << poDS->sInfoHeader.iBitCount;
|
|
poDS->pabyColorTable =
|
|
(GByte *) CPLMalloc( poDS->nColorElems * poDS->sInfoHeader.iClrUsed );
|
|
for ( i = 0; i < poDS->sInfoHeader.iClrUsed; i++ )
|
|
{
|
|
poDS->pabyColorTable[i * poDS->nColorElems] =
|
|
poDS->pabyColorTable[i * poDS->nColorElems + 1] =
|
|
poDS->pabyColorTable[i * poDS->nColorElems + 2] =
|
|
poDS->pabyColorTable[i * poDS->nColorElems + 3] = (GByte) i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
poDS->sInfoHeader.iClrUsed = 0;
|
|
}
|
|
poDS->sInfoHeader.iClrImportant = 0;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Fill the BMPFileHeader */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->sFileHeader.bType[0] = 'B';
|
|
poDS->sFileHeader.bType[1] = 'M';
|
|
poDS->sFileHeader.iSize = BFH_SIZE + poDS->sInfoHeader.iSize +
|
|
poDS->sInfoHeader.iClrUsed * poDS->nColorElems +
|
|
poDS->sInfoHeader.iSizeImage;
|
|
poDS->sFileHeader.iReserved1 = 0;
|
|
poDS->sFileHeader.iReserved2 = 0;
|
|
poDS->sFileHeader.iOffBits = BFH_SIZE + poDS->sInfoHeader.iSize +
|
|
poDS->sInfoHeader.iClrUsed * poDS->nColorElems;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Write all structures to the file */
|
|
/* -------------------------------------------------------------------- */
|
|
if( VSIFWriteL( &poDS->sFileHeader.bType, 1, 2, poDS->fp ) != 2 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Write of first 2 bytes to BMP file %s failed.\n"
|
|
"Is file system full?",
|
|
pszFilename );
|
|
delete poDS;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
GInt32 iLong;
|
|
GUInt32 iULong;
|
|
GUInt16 iUShort;
|
|
|
|
iULong = CPL_LSBWORD32( poDS->sFileHeader.iSize );
|
|
VSIFWriteL( &iULong, 4, 1, poDS->fp );
|
|
iUShort = CPL_LSBWORD16( poDS->sFileHeader.iReserved1 );
|
|
VSIFWriteL( &iUShort, 2, 1, poDS->fp );
|
|
iUShort = CPL_LSBWORD16( poDS->sFileHeader.iReserved2 );
|
|
VSIFWriteL( &iUShort, 2, 1, poDS->fp );
|
|
iULong = CPL_LSBWORD32( poDS->sFileHeader.iOffBits );
|
|
VSIFWriteL( &iULong, 4, 1, poDS->fp );
|
|
|
|
iULong = CPL_LSBWORD32( poDS->sInfoHeader.iSize );
|
|
VSIFWriteL( &iULong, 4, 1, poDS->fp );
|
|
iLong = CPL_LSBWORD32( poDS->sInfoHeader.iWidth );
|
|
VSIFWriteL( &iLong, 4, 1, poDS->fp );
|
|
iLong = CPL_LSBWORD32( poDS->sInfoHeader.iHeight );
|
|
VSIFWriteL( &iLong, 4, 1, poDS->fp );
|
|
iUShort = CPL_LSBWORD16( poDS->sInfoHeader.iPlanes );
|
|
VSIFWriteL( &iUShort, 2, 1, poDS->fp );
|
|
iUShort = CPL_LSBWORD16( poDS->sInfoHeader.iBitCount );
|
|
VSIFWriteL( &iUShort, 2, 1, poDS->fp );
|
|
iULong = CPL_LSBWORD32( poDS->sInfoHeader.iCompression );
|
|
VSIFWriteL( &iULong, 4, 1, poDS->fp );
|
|
iULong = CPL_LSBWORD32( poDS->sInfoHeader.iSizeImage );
|
|
VSIFWriteL( &iULong, 4, 1, poDS->fp );
|
|
iLong = CPL_LSBWORD32( poDS->sInfoHeader.iXPelsPerMeter );
|
|
VSIFWriteL( &iLong, 4, 1, poDS->fp );
|
|
iLong = CPL_LSBWORD32( poDS->sInfoHeader.iYPelsPerMeter );
|
|
VSIFWriteL( &iLong, 4, 1, poDS->fp );
|
|
iULong = CPL_LSBWORD32( poDS->sInfoHeader.iClrUsed );
|
|
VSIFWriteL( &iULong, 4, 1, poDS->fp );
|
|
iULong = CPL_LSBWORD32( poDS->sInfoHeader.iClrImportant );
|
|
VSIFWriteL( &iULong, 4, 1, poDS->fp );
|
|
|
|
if ( poDS->sInfoHeader.iClrUsed )
|
|
{
|
|
if( VSIFWriteL( poDS->pabyColorTable, 1,
|
|
poDS->nColorElems * poDS->sInfoHeader.iClrUsed, poDS->fp )
|
|
!= poDS->nColorElems * poDS->sInfoHeader.iClrUsed )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Error writing color table. Is disk full?" );
|
|
delete poDS;
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
poDS->nRasterXSize = nXSize;
|
|
poDS->nRasterYSize = nYSize;
|
|
poDS->eAccess = GA_Update;
|
|
poDS->nBands = nBands;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create band information objects. */
|
|
/* -------------------------------------------------------------------- */
|
|
int iBand;
|
|
|
|
for( iBand = 1; iBand <= poDS->nBands; iBand++ )
|
|
{
|
|
poDS->SetBand( iBand, new BMPRasterBand( poDS, iBand ) );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do we need a world file? */
|
|
/* -------------------------------------------------------------------- */
|
|
if( CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) )
|
|
poDS->bGeoTransformValid = TRUE;
|
|
|
|
return (GDALDataset *) poDS;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALRegister_BMP() */
|
|
/************************************************************************/
|
|
|
|
void GDALRegister_BMP()
|
|
|
|
{
|
|
GDALDriver *poDriver;
|
|
|
|
if( GDALGetDriverByName( "BMP" ) == NULL )
|
|
{
|
|
poDriver = new GDALDriver();
|
|
|
|
poDriver->SetDescription( "BMP" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
|
|
"MS Windows Device Independent Bitmap" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
|
|
"frmt_bmp.html" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "bmp" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
|
|
"<CreationOptionList>"
|
|
" <Option name='WORLDFILE' type='boolean' description='Write out world file'/>"
|
|
"</CreationOptionList>" );
|
|
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
|
|
|
|
poDriver->pfnOpen = BMPDataset::Open;
|
|
poDriver->pfnCreate = BMPDataset::Create;
|
|
poDriver->pfnIdentify = BMPDataset::Identify;
|
|
|
|
GetGDALDriverManager()->RegisterDriver( poDriver );
|
|
}
|
|
}
|