ultimatepp/bazaar/plugin/gdal/frmts/jpeg/jpgdataset.cpp
cxl 23ff1e7e82 .gdal moved to bazaar
git-svn-id: svn://ultimatepp.org/upp/trunk@9273 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2015-12-07 13:36:24 +00:00

3740 lines
128 KiB
C++

/******************************************************************************
* $Id: jpgdataset.cpp 29267 2015-05-29 20:47:00Z rouault $
*
* Project: JPEG JFIF Driver
* Purpose: Implement GDAL JPEG Support based on IJG libjpeg.
* Author: Frank Warmerdam, warmerdam@pobox.com
*
******************************************************************************
* Copyright (c) 2000, Frank Warmerdam
* Copyright (c) 2007-2014, Even Rouault <even dot rouault at mines-paris dot org>
*
* Portions Copyright (c) Her majesty the Queen in right of Canada as
* represented by the Minister of National Defence, 2006.
*
* 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"
#include "gdalexif.h"
#include "memdataset.h"
#include <setjmp.h>
#define TIFF_VERSION 42
#define TIFF_BIGENDIAN 0x4d4d
#define TIFF_LITTLEENDIAN 0x4949
#define JPEG_TIFF_IMAGEWIDTH 0x100
#define JPEG_TIFF_IMAGEHEIGHT 0x101
#define JPEG_TIFF_COMPRESSION 0x103
#define JPEG_EXIF_JPEGIFOFSET 0x201
#define JPEG_EXIF_JPEGIFBYTECOUNT 0x202
/*
* TIFF header.
*/
typedef struct {
GUInt16 tiff_magic; /* magic number (defines byte order) */
GUInt16 tiff_version; /* TIFF version number */
GUInt32 tiff_diroff; /* byte offset to first directory */
} TIFFHeader;
CPL_CVSID("$Id: jpgdataset.cpp 29267 2015-05-29 20:47:00Z rouault $");
CPL_C_START
#ifdef LIBJPEG_12_PATH
# include LIBJPEG_12_PATH
#else
# include "jpeglib.h"
#endif
CPL_C_END
// we believe it is ok to use setjmp() in this situation.
#ifdef _MSC_VER
# pragma warning(disable:4611)
#endif
#if defined(JPEG_DUAL_MODE_8_12) && !defined(JPGDataset)
GDALDataset* JPEGDataset12Open(const char* pszFilename,
VSILFILE* fpLin,
char** papszSiblingFiles,
int nScaleFactor,
int bDoPAMInitialize);
GDALDataset* JPEGDataset12CreateCopy( const char * pszFilename,
GDALDataset *poSrcDS,
int bStrict, char ** papszOptions,
GDALProgressFunc pfnProgress,
void * pProgressData );
#endif
CPL_C_START
void GDALRegister_JPEG(void);
CPL_C_END
#include "vsidataio.h"
/*
* Do we want to do special processing suitable for when JSAMPLE is a
* 16bit value?
*/
#if defined(JPEG_LIB_MK1)
# define JPEG_LIB_MK1_OR_12BIT 1
#elif BITS_IN_JSAMPLE == 12
# define JPEG_LIB_MK1_OR_12BIT 1
#endif
class JPGDatasetCommon;
GDALRasterBand* JPGCreateBand(JPGDatasetCommon* poDS, int nBand);
CPLErr JPGAppendMask( const char *pszJPGFilename, GDALRasterBand *poMask,
GDALProgressFunc pfnProgress, void * pProgressData );
void JPGAddEXIFOverview( GDALDataType eWorkDT,
GDALDataset* poSrcDS, char** papszOptions,
j_compress_ptr cinfo,
void (*p_jpeg_write_m_header) (j_compress_ptr cinfo, int marker, unsigned int datalen),
void (*p_jpeg_write_m_byte) (j_compress_ptr cinfo, int val),
GDALDataset *(pCreateCopy)( const char *, GDALDataset *,
int, char **,
GDALProgressFunc pfnProgress,
void * pProgressData ) );
void JPGAddICCProfile( struct jpeg_compress_struct *pInfo,
const char *pszICCProfile,
void (*p_jpeg_write_m_header) (j_compress_ptr cinfo, int marker, unsigned int datalen),
void (*p_jpeg_write_m_byte) (j_compress_ptr cinfo, int val));
typedef struct
{
jmp_buf setjmp_buffer;
int bNonFatalErrorEncountered;
void (*p_previous_emit_message)(j_common_ptr cinfo, int msg_level);
} GDALJPEGErrorStruct;
/************************************************************************/
/* ==================================================================== */
/* JPGDatasetCommon */
/* ==================================================================== */
/************************************************************************/
class JPGRasterBand;
class JPGMaskBand;
class JPGDatasetCommon : public GDALPamDataset
{
protected:
friend class JPGRasterBand;
friend class JPGMaskBand;
GDALJPEGErrorStruct sErrorStruct;
int ErrorOutOnNonFatalError();
int nScaleFactor;
int bHasInitInternalOverviews;
int nInternalOverviewsCurrent;
int nInternalOverviewsToFree;
GDALDataset** papoInternalOverviews;
void InitInternalOverviews();
GDALDataset* InitEXIFOverview();
char *pszProjection;
int bGeoTransformValid;
double adfGeoTransform[6];
int nGCPCount;
GDAL_GCP *pasGCPList;
VSILFILE *fpImage;
GUIntBig nSubfileOffset;
int nLoadedScanline;
GByte *pabyScanline;
int bHasReadEXIFMetadata;
int bHasReadXMPMetadata;
int bHasReadICCMetadata;
char **papszMetadata;
char **papszSubDatasets;
int bigendian;
int nExifOffset;
int nInterOffset;
int nGPSOffset;
int bSwabflag;
int nTiffDirStart;
int nTIFFHEADER;
int bHasDoneJpegCreateDecompress;
int bHasDoneJpegStartDecompress;
virtual CPLErr LoadScanline(int) = 0;
virtual CPLErr Restart() = 0;
virtual int GetDataPrecision() = 0;
virtual int GetOutColorSpace() = 0;
int EXIFInit(VSILFILE *);
void ReadICCProfile();
void CheckForMask();
void DecompressMask();
void ReadEXIFMetadata();
void ReadXMPMetadata();
int bHasCheckedForMask;
JPGMaskBand *poMaskBand;
GByte *pabyBitMask;
int bMaskLSBOrder;
GByte *pabyCMask;
int nCMaskSize;
J_COLOR_SPACE eGDALColorSpace; /* color space exposed by GDAL. Not necessarily the in_color_space nor */
/* the out_color_space of JPEG library */
int bIsSubfile;
int bHasTriedLoadWorldFileOrTab;
void LoadWorldFileOrTab();
CPLString osWldFilename;
virtual int CloseDependentDatasets();
virtual CPLErr IBuildOverviews( const char *, int, int *,
int, int *, GDALProgressFunc, void * );
public:
JPGDatasetCommon();
~JPGDatasetCommon();
virtual CPLErr IRasterIO( GDALRWFlag, int, int, int, int,
void *, int, int, GDALDataType,
int, int *,
GSpacing nPixelSpace, GSpacing nLineSpace,
GSpacing nBandSpace,
GDALRasterIOExtraArg* psExtraArg );
virtual CPLErr GetGeoTransform( double * );
virtual int GetGCPCount();
virtual const char *GetGCPProjection();
virtual const GDAL_GCP *GetGCPs();
virtual char **GetMetadataDomainList();
virtual char **GetMetadata( const char * pszDomain = "" );
virtual const char *GetMetadataItem( const char * pszName,
const char * pszDomain = "" );
virtual char **GetFileList(void);
virtual void FlushCache(void);
static int Identify( GDALOpenInfo * );
static GDALDataset *Open( GDALOpenInfo * );
static void EmitMessage(j_common_ptr cinfo, int msg_level);
};
/************************************************************************/
/* ==================================================================== */
/* JPGDataset */
/* ==================================================================== */
/************************************************************************/
class JPGDataset : public JPGDatasetCommon
{
struct jpeg_decompress_struct sDInfo;
struct jpeg_error_mgr sJErr;
virtual CPLErr LoadScanline(int);
virtual CPLErr Restart();
virtual int GetDataPrecision() { return sDInfo.data_precision; }
virtual int GetOutColorSpace() { return sDInfo.out_color_space; }
int nQLevel;
#if !defined(JPGDataset)
void LoadDefaultTables(int);
#endif
void SetScaleNumAndDenom();
public:
JPGDataset();
~JPGDataset();
static GDALDataset *Open( const char* pszFilename,
VSILFILE* fpLin,
char** papszSiblingFiles,
int nScaleFactor,
int bDoPAMInitialize );
static GDALDataset* CreateCopy( const char * pszFilename,
GDALDataset *poSrcDS,
int bStrict, char ** papszOptions,
GDALProgressFunc pfnProgress,
void * pProgressData );
static void ErrorExit(j_common_ptr cinfo);
};
/************************************************************************/
/* ==================================================================== */
/* JPGRasterBand */
/* ==================================================================== */
/************************************************************************/
class JPGRasterBand : public GDALPamRasterBand
{
friend class JPGDatasetCommon;
/* We have to keep a pointer to the JPGDataset that this JPGRasterBand
belongs to. In some case, we may have this->poGDS != this->poDS
For example for a JPGRasterBand that is set to a NITFDataset...
In other words, this->poDS doesn't necessary point to a JPGDataset
See ticket #1807.
*/
JPGDatasetCommon *poGDS;
public:
JPGRasterBand( JPGDatasetCommon *, int );
virtual CPLErr IReadBlock( int, int, void * );
virtual GDALColorInterp GetColorInterpretation();
virtual GDALRasterBand *GetMaskBand();
virtual int GetMaskFlags();
virtual GDALRasterBand *GetOverview(int i);
virtual int GetOverviewCount();
};
#if !defined(JPGDataset)
/************************************************************************/
/* ==================================================================== */
/* JPGMaskBand */
/* ==================================================================== */
/************************************************************************/
class JPGMaskBand : public GDALRasterBand
{
protected:
virtual CPLErr IReadBlock( int, int, void * );
public:
JPGMaskBand( JPGDataset *poDS );
};
/************************************************************************/
/* ReadEXIFMetadata() */
/************************************************************************/
void JPGDatasetCommon::ReadEXIFMetadata()
{
if (bHasReadEXIFMetadata)
return;
CPLAssert(papszMetadata == NULL);
/* Save current position to avoid disturbing JPEG stream decoding */
vsi_l_offset nCurOffset = VSIFTellL(fpImage);
if( EXIFInit(fpImage) )
{
EXIFExtractMetadata(papszMetadata,
fpImage, nTiffDirStart,
bSwabflag, nTIFFHEADER,
nExifOffset, nInterOffset, nGPSOffset);
if(nExifOffset > 0){
EXIFExtractMetadata(papszMetadata,
fpImage, nExifOffset,
bSwabflag, nTIFFHEADER,
nExifOffset, nInterOffset, nGPSOffset);
}
if(nInterOffset > 0) {
EXIFExtractMetadata(papszMetadata,
fpImage, nInterOffset,
bSwabflag, nTIFFHEADER,
nExifOffset, nInterOffset, nGPSOffset);
}
if(nGPSOffset > 0) {
EXIFExtractMetadata(papszMetadata,
fpImage, nGPSOffset,
bSwabflag, nTIFFHEADER,
nExifOffset, nInterOffset, nGPSOffset);
}
/* Avoid setting the PAM dirty bit just for that */
int nOldPamFlags = nPamFlags;
/* Append metadata from PAM after EXIF metadata */
papszMetadata = CSLMerge(papszMetadata, GDALPamDataset::GetMetadata());
SetMetadata( papszMetadata );
nPamFlags = nOldPamFlags;
}
VSIFSeekL( fpImage, nCurOffset, SEEK_SET );
bHasReadEXIFMetadata = TRUE;
}
/************************************************************************/
/* ReadXMPMetadata() */
/************************************************************************/
/* See §2.1.3 of http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart3.pdf */
void JPGDatasetCommon::ReadXMPMetadata()
{
if (bHasReadXMPMetadata)
return;
/* Save current position to avoid disturbing JPEG stream decoding */
vsi_l_offset nCurOffset = VSIFTellL(fpImage);
/* -------------------------------------------------------------------- */
/* Search for APP1 chunk. */
/* -------------------------------------------------------------------- */
GByte abyChunkHeader[2+2+29];
int nChunkLoc = 2;
int bFoundXMP = FALSE;
for( ; TRUE; )
{
if( VSIFSeekL( fpImage, nChunkLoc, SEEK_SET ) != 0 )
break;
if( VSIFReadL( abyChunkHeader, sizeof(abyChunkHeader), 1, fpImage ) != 1 )
break;
nChunkLoc += 2 + abyChunkHeader[2] * 256 + abyChunkHeader[3];
// COM marker
if( abyChunkHeader[0] == 0xFF && abyChunkHeader[1] == 0xFE )
continue;
if( abyChunkHeader[0] != 0xFF
|| (abyChunkHeader[1] & 0xf0) != 0xe0 )
break; // Not an APP chunk.
if( abyChunkHeader[1] == 0xe1
&& strncmp((const char *) abyChunkHeader + 4,"http://ns.adobe.com/xap/1.0/",28) == 0 )
{
bFoundXMP = TRUE;
break; // APP1 - XMP
}
}
if (bFoundXMP)
{
int nXMPLength = abyChunkHeader[2] * 256 + abyChunkHeader[3];
if (nXMPLength > 2 + 29)
{
char* pszXMP = (char*)VSIMalloc(nXMPLength - 2 - 29 + 1);
if (pszXMP)
{
if (VSIFReadL( pszXMP, nXMPLength - 2 - 29, 1, fpImage ) == 1)
{
pszXMP[nXMPLength - 2 - 29] = '\0';
/* Avoid setting the PAM dirty bit just for that */
int nOldPamFlags = nPamFlags;
char *apszMDList[2];
apszMDList[0] = pszXMP;
apszMDList[1] = NULL;
SetMetadata(apszMDList, "xml:XMP");
nPamFlags = nOldPamFlags;
}
VSIFree(pszXMP);
}
}
}
VSIFSeekL( fpImage, nCurOffset, SEEK_SET );
bHasReadXMPMetadata = TRUE;
}
/************************************************************************/
/* GetMetadataDomainList() */
/************************************************************************/
char **JPGDatasetCommon::GetMetadataDomainList()
{
return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
TRUE,
"xml:XMP", "COLOR_PROFILE", NULL);
}
/************************************************************************/
/* GetMetadata() */
/************************************************************************/
char **JPGDatasetCommon::GetMetadata( const char * pszDomain )
{
if (fpImage == NULL)
return NULL;
if (eAccess == GA_ReadOnly && !bHasReadEXIFMetadata &&
(pszDomain == NULL || EQUAL(pszDomain, "")))
ReadEXIFMetadata();
if (eAccess == GA_ReadOnly && !bHasReadXMPMetadata &&
pszDomain != NULL && EQUAL(pszDomain, "xml:XMP"))
ReadXMPMetadata();
if (eAccess == GA_ReadOnly && !bHasReadICCMetadata &&
pszDomain != NULL && EQUAL(pszDomain, "COLOR_PROFILE"))
ReadICCProfile();
return GDALPamDataset::GetMetadata(pszDomain);
}
/************************************************************************/
/* GetMetadataItem() */
/************************************************************************/
const char *JPGDatasetCommon::GetMetadataItem( const char * pszName,
const char * pszDomain )
{
if (fpImage == NULL)
return NULL;
if (eAccess == GA_ReadOnly && !bHasReadEXIFMetadata &&
(pszDomain == NULL || EQUAL(pszDomain, "")) &&
pszName != NULL &&
(EQUAL(pszName, "COMMENT") || EQUALN(pszName, "EXIF_", 5)))
ReadEXIFMetadata();
if (eAccess == GA_ReadOnly && !bHasReadICCMetadata &&
pszDomain != NULL && EQUAL(pszDomain, "COLOR_PROFILE"))
ReadICCProfile();
return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
}
/************************************************************************/
/* ReadICCProfile() */
/* */
/* Read ICC Profile from APP2 data */
/************************************************************************/
void JPGDatasetCommon::ReadICCProfile()
{
if (bHasReadICCMetadata)
return;
bHasReadICCMetadata = TRUE;
vsi_l_offset nCurOffset = VSIFTellL(fpImage);
int nTotalSize = 0;
int nChunkCount = -1;
int anChunkSize[256];
char *apChunk[256];
memset(anChunkSize, 0, 256 * sizeof(int));
memset(apChunk, 0, 256 * sizeof(char*));
/* -------------------------------------------------------------------- */
/* Search for APP2 chunk. */
/* -------------------------------------------------------------------- */
GByte abyChunkHeader[18];
int nChunkLoc = 2;
bool bOk = true;
for( ; TRUE; )
{
if( VSIFSeekL( fpImage, nChunkLoc, SEEK_SET ) != 0 )
break;
if( VSIFReadL( abyChunkHeader, sizeof(abyChunkHeader), 1, fpImage ) != 1 )
break;
if( abyChunkHeader[0] != 0xFF )
break; // Not a valid tag
if (abyChunkHeader[1] == 0xD9)
break; // End of image
if ((abyChunkHeader[1] >= 0xD0) && (abyChunkHeader[1] <= 0xD8))
{
// Restart tags have no length
nChunkLoc += 2;
continue;
}
int nChunkLength = abyChunkHeader[2] * 256 + abyChunkHeader[3];
if( abyChunkHeader[1] == 0xe2
&& memcmp((const char *) abyChunkHeader + 4,"ICC_PROFILE\0",12) == 0 )
{
/* Get length and segment ID */
/* Header: */
/* APP2 tag: 2 bytes */
/* App Length: 2 bytes */
/* ICC_PROFILE\0 tag: 12 bytes */
/* Segment index: 1 bytes */
/* Total segments: 1 bytes */
int nICCChunkLength = nChunkLength - 16;
int nICCChunkID = abyChunkHeader[16];
int nICCMaxChunkID = abyChunkHeader[17];
if (nChunkCount == -1)
nChunkCount = nICCMaxChunkID;
/* Check that all max segment counts are the same */
if (nICCMaxChunkID != nChunkCount)
{
bOk = false;
break;
}
/* Check that no segment ID is larger than the total segment count */
if ((nICCChunkID > nChunkCount) || (nICCChunkID == 0) || (nChunkCount == 0))
{
bOk = false;
break;
}
/* Check if ICC segment already loaded */
if (apChunk[nICCChunkID-1] != NULL)
{
bOk = false;
break;
}
/* Load it */
apChunk[nICCChunkID-1] = (char*)VSIMalloc(nICCChunkLength);
anChunkSize[nICCChunkID-1] = nICCChunkLength;
if( VSIFReadL( apChunk[nICCChunkID-1], nICCChunkLength, 1, fpImage ) != 1 )
{
bOk = false;
break;
}
}
nChunkLoc += 2 + nChunkLength;
}
/* Get total size and verify that there are no missing segments */
if (bOk)
{
for(int i = 0; i < nChunkCount; i++)
{
if (apChunk[i] == NULL)
{
/* Missing segment... abort */
bOk = false;
break;
}
nTotalSize += anChunkSize[i];
}
}
/* Merge all segments together and set metadata */
if (bOk && nChunkCount > 0)
{
char *pBuffer = (char*)VSIMalloc(nTotalSize);
char *pBufferPtr = pBuffer;
for(int i = 0; i < nChunkCount; i++)
{
memcpy(pBufferPtr, apChunk[i], anChunkSize[i]);
pBufferPtr += anChunkSize[i];
}
/* Escape the profile */
char *pszBase64Profile = CPLBase64Encode(nTotalSize, (const GByte*)pBuffer);
/* Avoid setting the PAM dirty bit just for that */
int nOldPamFlags = nPamFlags;
/* Set ICC profile metadata */
SetMetadataItem( "SOURCE_ICC_PROFILE", pszBase64Profile, "COLOR_PROFILE" );
nPamFlags = nOldPamFlags;
VSIFree(pBuffer);
CPLFree(pszBase64Profile);
}
/* Cleanup */
for(int i = 0; i < nChunkCount; i++)
{
if (apChunk[i] != NULL)
VSIFree(apChunk[i]);
}
VSIFSeekL( fpImage, nCurOffset, SEEK_SET );
}
/************************************************************************/
/* EXIFInit() */
/* */
/* Create Metadata from Information file directory APP1 */
/************************************************************************/
int JPGDatasetCommon::EXIFInit(VSILFILE *fp)
{
int one = 1;
TIFFHeader hdr;
if( nTiffDirStart == 0 )
return FALSE;
else if( nTiffDirStart > 0 )
return TRUE;
nTiffDirStart = 0;
bigendian = (*(char *)&one == 0);
/* -------------------------------------------------------------------- */
/* Search for APP1 chunk. */
/* -------------------------------------------------------------------- */
GByte abyChunkHeader[10];
int nChunkLoc = 2;
for( ; TRUE; )
{
if( VSIFSeekL( fp, nChunkLoc, SEEK_SET ) != 0 )
return FALSE;
if( VSIFReadL( abyChunkHeader, sizeof(abyChunkHeader), 1, fp ) != 1 )
return FALSE;
int nChunkLength = abyChunkHeader[2] * 256 + abyChunkHeader[3];
// COM marker
if( abyChunkHeader[0] == 0xFF && abyChunkHeader[1] == 0xFE &&
nChunkLength >= 2 )
{
char* pszComment = (char*)CPLMalloc(nChunkLength - 2 + 1);
if( nChunkLength > 2 &&
VSIFSeekL( fp, nChunkLoc + 4, SEEK_SET ) == 0 &&
VSIFReadL(pszComment, nChunkLength - 2, 1, fp) == 1 )
{
pszComment[nChunkLength-2] = 0;
/* Avoid setting the PAM dirty bit just for that */
int nOldPamFlags = nPamFlags;
/* Set ICC profile metadata */
SetMetadataItem( "COMMENT", pszComment );
nPamFlags = nOldPamFlags;
}
CPLFree(pszComment);
}
else
{
if( abyChunkHeader[0] != 0xFF
|| (abyChunkHeader[1] & 0xf0) != 0xe0 )
break; // Not an APP chunk.
if( abyChunkHeader[1] == 0xe1
&& strncmp((const char *) abyChunkHeader + 4,"Exif",4) == 0 )
{
nTIFFHEADER = nChunkLoc + 10;
}
}
nChunkLoc += 2 + nChunkLength;
}
if( nTIFFHEADER < 0 )
return FALSE;
/* -------------------------------------------------------------------- */
/* Read TIFF header */
/* -------------------------------------------------------------------- */
VSIFSeekL(fp, nTIFFHEADER, SEEK_SET);
if(VSIFReadL(&hdr,1,sizeof(hdr),fp) != sizeof(hdr))
{
CPLError( CE_Failure, CPLE_FileIO,
"Failed to read %d byte from image header.",
(int) sizeof(hdr));
return FALSE;
}
if (hdr.tiff_magic != TIFF_BIGENDIAN && hdr.tiff_magic != TIFF_LITTLEENDIAN)
{
CPLError( CE_Failure, CPLE_AppDefined,
"Not a TIFF file, bad magic number %u (%#x)",
hdr.tiff_magic, hdr.tiff_magic);
return FALSE;
}
if (hdr.tiff_magic == TIFF_BIGENDIAN) bSwabflag = !bigendian;
if (hdr.tiff_magic == TIFF_LITTLEENDIAN) bSwabflag = bigendian;
if (bSwabflag) {
CPL_SWAP16PTR(&hdr.tiff_version);
CPL_SWAP32PTR(&hdr.tiff_diroff);
}
if (hdr.tiff_version != TIFF_VERSION)
{
CPLError(CE_Failure, CPLE_AppDefined,
"Not a TIFF file, bad version number %u (%#x)",
hdr.tiff_version, hdr.tiff_version);
return FALSE;
}
nTiffDirStart = hdr.tiff_diroff;
CPLDebug( "JPEG", "Magic: %#x <%s-endian> Version: %#x\n",
hdr.tiff_magic,
hdr.tiff_magic == TIFF_BIGENDIAN ? "big" : "little",
hdr.tiff_version );
return TRUE;
}
/************************************************************************/
/* JPGMaskBand() */
/************************************************************************/
JPGMaskBand::JPGMaskBand( JPGDataset *poDS )
{
this->poDS = poDS;
nBand = 0;
nRasterXSize = poDS->GetRasterXSize();
nRasterYSize = poDS->GetRasterYSize();
eDataType = GDT_Byte;
nBlockXSize = nRasterXSize;
nBlockYSize = 1;
}
/************************************************************************/
/* IReadBlock() */
/************************************************************************/
CPLErr JPGMaskBand::IReadBlock( CPL_UNUSED int nBlockX, int nBlockY, void *pImage )
{
JPGDataset *poJDS = (JPGDataset *) poDS;
/* -------------------------------------------------------------------- */
/* Make sure the mask is loaded and decompressed. */
/* -------------------------------------------------------------------- */
poJDS->DecompressMask();
if( poJDS->pabyBitMask == NULL )
return CE_Failure;
/* -------------------------------------------------------------------- */
/* Set mask based on bitmask for this scanline. */
/* -------------------------------------------------------------------- */
int iX;
GUInt32 iBit = (GUInt32)nBlockY * (GUInt32)nBlockXSize;
if( poJDS->bMaskLSBOrder )
{
for( iX = 0; iX < nBlockXSize; iX++ )
{
if( poJDS->pabyBitMask[iBit>>3] & (0x1 << (iBit&7)) )
((GByte *) pImage)[iX] = 255;
else
((GByte *) pImage)[iX] = 0;
iBit++;
}
}
else
{
for( iX = 0; iX < nBlockXSize; iX++ )
{
if( poJDS->pabyBitMask[iBit>>3] & (0x1 << (7 - (iBit&7))) )
((GByte *) pImage)[iX] = 255;
else
((GByte *) pImage)[iX] = 0;
iBit++;
}
}
return CE_None;
}
/************************************************************************/
/* JPGRasterBand() */
/************************************************************************/
JPGRasterBand::JPGRasterBand( JPGDatasetCommon *poDS, int nBand )
{
this->poDS = poGDS = poDS;
this->nBand = nBand;
if( poDS->GetDataPrecision() == 12 )
eDataType = GDT_UInt16;
else
eDataType = GDT_Byte;
nBlockXSize = poDS->nRasterXSize;;
nBlockYSize = 1;
GDALMajorObject::SetMetadataItem("COMPRESSION","JPEG","IMAGE_STRUCTURE");
}
/************************************************************************/
/* JPGCreateBand() */
/************************************************************************/
GDALRasterBand* JPGCreateBand(JPGDatasetCommon* poDS, int nBand)
{
return new JPGRasterBand(poDS, nBand);
}
/************************************************************************/
/* IReadBlock() */
/************************************************************************/
CPLErr JPGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
void * pImage )
{
CPLErr eErr;
int nXSize = GetXSize();
int nWordSize = GDALGetDataTypeSize(eDataType) / 8;
CPLAssert( nBlockXOff == 0 );
if (poGDS->fpImage == NULL)
{
memset( pImage, 0, nXSize * nWordSize );
return CE_None;
}
/* -------------------------------------------------------------------- */
/* Load the desired scanline into the working buffer. */
/* -------------------------------------------------------------------- */
eErr = poGDS->LoadScanline( nBlockYOff );
if( eErr != CE_None )
return eErr;
/* -------------------------------------------------------------------- */
/* Transfer between the working buffer the the callers buffer. */
/* -------------------------------------------------------------------- */
if( poGDS->GetRasterCount() == 1 )
{
#ifdef JPEG_LIB_MK1_OR_12BIT
GDALCopyWords( poGDS->pabyScanline, GDT_UInt16, 2,
pImage, eDataType, nWordSize,
nXSize );
#else
memcpy( pImage, poGDS->pabyScanline, nXSize * nWordSize );
#endif
}
else
{
#ifdef JPEG_LIB_MK1_OR_12BIT
GDALCopyWords( poGDS->pabyScanline + (nBand-1) * 2,
GDT_UInt16, 6,
pImage, eDataType, nWordSize,
nXSize );
#else
if (poGDS->eGDALColorSpace == JCS_RGB &&
poGDS->GetOutColorSpace() == JCS_CMYK)
{
CPLAssert(eDataType == GDT_Byte);
int i;
if (nBand == 1)
{
for(i=0;i<nXSize;i++)
{
int C = poGDS->pabyScanline[i * 4 + 0];
int K = poGDS->pabyScanline[i * 4 + 3];
((GByte*)pImage)[i] = (GByte) ((C * K) / 255);
}
}
else if (nBand == 2)
{
for(i=0;i<nXSize;i++)
{
int M = poGDS->pabyScanline[i * 4 + 1];
int K = poGDS->pabyScanline[i * 4 + 3];
((GByte*)pImage)[i] = (GByte) ((M * K) / 255);
}
}
else if (nBand == 3)
{
for(i=0;i<nXSize;i++)
{
int Y = poGDS->pabyScanline[i * 4 + 2];
int K = poGDS->pabyScanline[i * 4 + 3];
((GByte*)pImage)[i] = (GByte) ((Y * K) / 255);
}
}
}
else
{
GDALCopyWords( poGDS->pabyScanline + (nBand-1) * nWordSize,
eDataType, nWordSize * poGDS->GetRasterCount(),
pImage, eDataType, nWordSize,
nXSize );
}
#endif
}
/* -------------------------------------------------------------------- */
/* Forceably load the other bands associated with this scanline. */
/* -------------------------------------------------------------------- */
if( nBand == 1 )
{
GDALRasterBlock *poBlock;
int iBand;
for(iBand = 2; iBand <= poGDS->GetRasterCount() ; iBand++)
{
poBlock =
poGDS->GetRasterBand(iBand)->GetLockedBlockRef(nBlockXOff,nBlockYOff);
if( poBlock != NULL )
poBlock->DropLock();
}
}
return CE_None;
}
/************************************************************************/
/* GetColorInterpretation() */
/************************************************************************/
GDALColorInterp JPGRasterBand::GetColorInterpretation()
{
if( poGDS->eGDALColorSpace == JCS_GRAYSCALE )
return GCI_GrayIndex;
else if( poGDS->eGDALColorSpace == JCS_RGB)
{
if ( nBand == 1 )
return GCI_RedBand;
else if( nBand == 2 )
return GCI_GreenBand;
else
return GCI_BlueBand;
}
else if( poGDS->eGDALColorSpace == JCS_CMYK)
{
if ( nBand == 1 )
return GCI_CyanBand;
else if( nBand == 2 )
return GCI_MagentaBand;
else if ( nBand == 3 )
return GCI_YellowBand;
else
return GCI_BlackBand;
}
else if( poGDS->eGDALColorSpace == JCS_YCbCr ||
poGDS->eGDALColorSpace == JCS_YCCK)
{
if ( nBand == 1 )
return GCI_YCbCr_YBand;
else if( nBand == 2 )
return GCI_YCbCr_CbBand;
else if ( nBand == 3 )
return GCI_YCbCr_CrBand;
else
return GCI_BlackBand;
}
else
{
CPLAssert(0);
return GCI_Undefined;
}
}
/************************************************************************/
/* GetMaskBand() */
/************************************************************************/
GDALRasterBand *JPGRasterBand::GetMaskBand()
{
if (poGDS->nScaleFactor > 1 )
return GDALPamRasterBand::GetMaskBand();
if (poGDS->fpImage == NULL)
return NULL;
if( !poGDS->bHasCheckedForMask)
{
if( CSLTestBoolean(CPLGetConfigOption("JPEG_READ_MASK", "YES")))
poGDS->CheckForMask();
poGDS->bHasCheckedForMask = TRUE;
}
if( poGDS->pabyCMask )
{
if( poGDS->poMaskBand == NULL )
poGDS->poMaskBand = new JPGMaskBand( (JPGDataset *) poDS );
return poGDS->poMaskBand;
}
else
return GDALPamRasterBand::GetMaskBand();
}
/************************************************************************/
/* GetMaskFlags() */
/************************************************************************/
int JPGRasterBand::GetMaskFlags()
{
if (poGDS->nScaleFactor > 1 )
return GDALPamRasterBand::GetMaskFlags();
if (poGDS->fpImage == NULL)
return 0;
GetMaskBand();
if( poGDS->poMaskBand != NULL )
return GMF_PER_DATASET;
else
return GDALPamRasterBand::GetMaskFlags();
}
/************************************************************************/
/* GetOverview() */
/************************************************************************/
GDALRasterBand* JPGRasterBand::GetOverview(int i)
{
poGDS->InitInternalOverviews();
if( poGDS->nInternalOverviewsCurrent == 0 )
return GDALPamRasterBand::GetOverview(i);
if( i < 0 || i >= poGDS->nInternalOverviewsCurrent )
return NULL;
else
return poGDS->papoInternalOverviews[i]->GetRasterBand(nBand);
}
/************************************************************************/
/* GetOverviewCount() */
/************************************************************************/
int JPGRasterBand::GetOverviewCount()
{
poGDS->InitInternalOverviews();
if( poGDS->nInternalOverviewsCurrent == 0 )
return GDALPamRasterBand::GetOverviewCount();
return poGDS->nInternalOverviewsCurrent;
}
/************************************************************************/
/* ==================================================================== */
/* JPGDataset */
/* ==================================================================== */
/************************************************************************/
JPGDatasetCommon::JPGDatasetCommon()
{
fpImage = NULL;
nScaleFactor = 1;
bHasInitInternalOverviews = FALSE;
nInternalOverviewsCurrent = 0;
nInternalOverviewsToFree = 0;
papoInternalOverviews = NULL;
pabyScanline = NULL;
nLoadedScanline = -1;
bHasReadEXIFMetadata = FALSE;
bHasReadXMPMetadata = FALSE;
bHasReadICCMetadata = FALSE;
papszMetadata = NULL;
papszSubDatasets= NULL;
nExifOffset = -1;
nInterOffset = -1;
nGPSOffset = -1;
nTiffDirStart = -1;
nTIFFHEADER = -1;
pszProjection = NULL;
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;
nGCPCount = 0;
pasGCPList = NULL;
bHasDoneJpegCreateDecompress = FALSE;
bHasDoneJpegStartDecompress = FALSE;
bHasCheckedForMask = FALSE;
poMaskBand = NULL;
pabyBitMask = NULL;
bMaskLSBOrder = TRUE;
pabyCMask = NULL;
nCMaskSize = 0;
eGDALColorSpace = JCS_UNKNOWN;
bIsSubfile = FALSE;
bHasTriedLoadWorldFileOrTab = FALSE;
sErrorStruct.bNonFatalErrorEncountered = FALSE;
sErrorStruct.p_previous_emit_message = NULL;
}
/************************************************************************/
/* ~JPGDataset() */
/************************************************************************/
JPGDatasetCommon::~JPGDatasetCommon()
{
if( fpImage != NULL )
VSIFCloseL( fpImage );
if( pabyScanline != NULL )
CPLFree( pabyScanline );
if( papszMetadata != NULL )
CSLDestroy( papszMetadata );
if ( pszProjection )
CPLFree( pszProjection );
if ( nGCPCount > 0 )
{
GDALDeinitGCPs( nGCPCount, pasGCPList );
CPLFree( pasGCPList );
}
CPLFree( pabyBitMask );
CPLFree( pabyCMask );
delete poMaskBand;
CloseDependentDatasets();
}
/************************************************************************/
/* CloseDependentDatasets() */
/************************************************************************/
int JPGDatasetCommon::CloseDependentDatasets()
{
int bRet = GDALPamDataset::CloseDependentDatasets();
if( nInternalOverviewsToFree )
{
bRet = TRUE;
for(int i = 0; i < nInternalOverviewsToFree; i++)
delete papoInternalOverviews[i];
nInternalOverviewsToFree = 0;
}
CPLFree(papoInternalOverviews);
papoInternalOverviews = NULL;
return bRet;
}
/************************************************************************/
/* InitEXIFOverview() */
/************************************************************************/
GDALDataset* JPGDatasetCommon::InitEXIFOverview()
{
if( !EXIFInit(fpImage) )
return NULL;
GUInt16 nEntryCount;
/* -------------------------------------------------------------------- */
/* Read number of entry in directory */
/* -------------------------------------------------------------------- */
if( VSIFSeekL(fpImage, nTiffDirStart+nTIFFHEADER, SEEK_SET) != 0
|| VSIFReadL(&nEntryCount,1,sizeof(GUInt16),fpImage) != sizeof(GUInt16) )
{
CPLError( CE_Failure, CPLE_AppDefined,
"Error reading EXIF Directory count at %d.",
nTiffDirStart + nTIFFHEADER );
return NULL;
}
if (bSwabflag)
TIFFSwabShort(&nEntryCount);
// Some files are corrupt, a large entry count is a sign of this.
if( nEntryCount > 125 )
{
CPLError( CE_Warning, CPLE_AppDefined,
"Ignoring EXIF directory with unlikely entry count (%d).",
nEntryCount );
return NULL;
}
// Skip EXIF entries
VSIFSeekL(fpImage, nEntryCount * sizeof(TIFFDirEntry), SEEK_CUR );
// Read offset of next directory (IFD1)
GUInt32 nNextDirOff;
if( VSIFReadL(&nNextDirOff, 1, sizeof(GUInt32), fpImage) != sizeof(GUInt32) )
return NULL;
if( bSwabflag )
CPL_SWAP32PTR(&nNextDirOff);
if( nNextDirOff == 0 || nNextDirOff > 0xFFFFFFFFU - nTIFFHEADER )
return NULL;
// Seek to IFD1
if( VSIFSeekL(fpImage, nTIFFHEADER+nNextDirOff, SEEK_SET) != 0
|| VSIFReadL(&nEntryCount,1,sizeof(GUInt16),fpImage) != sizeof(GUInt16) )
{
CPLError( CE_Failure, CPLE_AppDefined,
"Error reading IFD1 Directory count at %d.",
nTIFFHEADER + nNextDirOff );
return NULL;
}
if (bSwabflag)
TIFFSwabShort(&nEntryCount);
if( nEntryCount > 125 )
{
CPLError( CE_Warning, CPLE_AppDefined,
"Ignoring IFD1 directory with unlikely entry count (%d).",
nEntryCount );
return NULL;
}
/* CPLDebug("JPEG", "IFD1 entry count = %d", nEntryCount); */
int nImageWidth = 0, nImageHeight = 0, nCompression = 6;
GUInt32 nJpegIFOffset = 0, nJpegIFByteCount = 0;
for( int i = 0; i < nEntryCount; i ++ )
{
TIFFDirEntry sEntry;
if( VSIFReadL(&sEntry,1,sizeof(sEntry),fpImage) != sizeof(sEntry) )
{
CPLError( CE_Warning, CPLE_AppDefined, "Cannot read entry %d of IFD1",
i );
return NULL;
}
if (bSwabflag)
{
TIFFSwabShort(&sEntry.tdir_tag);
TIFFSwabShort(&sEntry.tdir_type);
TIFFSwabLong (&sEntry.tdir_count);
TIFFSwabLong (&sEntry.tdir_offset);
}
/*CPLDebug("JPEG", "tag = %d (0x%4X), type = %d, count = %d, offset = %d",
sEntry.tdir_tag, sEntry.tdir_tag, sEntry.tdir_type, sEntry.tdir_count, sEntry.tdir_offset );*/
if( (sEntry.tdir_type == TIFF_SHORT || sEntry.tdir_type == TIFF_LONG) &&
sEntry.tdir_count == 1 )
{
switch( sEntry.tdir_tag )
{
case JPEG_TIFF_IMAGEWIDTH:
nImageWidth = sEntry.tdir_offset;
break;
case JPEG_TIFF_IMAGEHEIGHT:
nImageHeight = sEntry.tdir_offset;
break;
case JPEG_TIFF_COMPRESSION:
nCompression = sEntry.tdir_offset;
break;
case JPEG_EXIF_JPEGIFOFSET:
nJpegIFOffset = sEntry.tdir_offset;
break;
case JPEG_EXIF_JPEGIFBYTECOUNT:
nJpegIFByteCount = sEntry.tdir_offset;
break;
default:
break;
}
}
}
if( nCompression != 6 ||
nImageWidth >= nRasterXSize ||
nImageHeight >= nRasterYSize ||
nJpegIFOffset == 0 ||
nJpegIFOffset > 0xFFFFFFFFU - nTIFFHEADER ||
(int)nJpegIFByteCount <= 0 )
{
return NULL;
}
const char* pszSubfile = CPLSPrintf("JPEG_SUBFILE:%u,%d,%s",
nTIFFHEADER + nJpegIFOffset, nJpegIFByteCount, GetDescription());
return JPGDataset::Open(pszSubfile, NULL, NULL, 1, FALSE);
}
/************************************************************************/
/* InitInternalOverviews() */
/************************************************************************/
void JPGDatasetCommon::InitInternalOverviews()
{
if( bHasInitInternalOverviews )
return;
bHasInitInternalOverviews = TRUE;
/* -------------------------------------------------------------------- */
/* Instanciate on-the-fly overviews (if no external ones). */
/* -------------------------------------------------------------------- */
if( nScaleFactor == 1 && GetRasterBand(1)->GetOverviewCount() == 0 )
{
/* EXIF overview */
GDALDataset* poEXIFOverview = NULL;
if( nRasterXSize > 512 || nRasterYSize > 512 )
{
vsi_l_offset nCurOffset = VSIFTellL(fpImage);
poEXIFOverview = InitEXIFOverview();
if( poEXIFOverview != NULL )
{
if( poEXIFOverview->GetRasterCount() != nBands ||
poEXIFOverview->GetRasterXSize() >= nRasterXSize ||
poEXIFOverview->GetRasterYSize() >= nRasterYSize )
{
GDALClose(poEXIFOverview);
poEXIFOverview = NULL;
}
else
{
CPLDebug("JPEG", "EXIF overview (%d x %d) detected",
poEXIFOverview->GetRasterXSize(),
poEXIFOverview->GetRasterYSize());
}
}
VSIFSeekL( fpImage, nCurOffset, SEEK_SET );
}
/* libjpeg-6b only suppports 2, 4 and 8 scale denominators */
/* TODO: Later versions support more */
int i;
int nImplicitOverviews = 0;
/* For the needs of the implicit JPEG-in-TIFF overview mechanism */
if( CSLTestBoolean(CPLGetConfigOption("JPEG_FORCE_INTERNAL_OVERVIEWS", "NO")) )
nImplicitOverviews = 3;
else
{
for(i = 2; i >= 0; i--)
{
if( nRasterXSize >= (256 << i) || nRasterYSize >= (256 << i) )
{
nImplicitOverviews = i + 1;
break;
}
}
}
if( nImplicitOverviews > 0 )
{
papoInternalOverviews = (GDALDataset**)
CPLMalloc((nImplicitOverviews + (poEXIFOverview ? 1 : 0)) * sizeof(GDALDataset*));
for(i = 0; i < nImplicitOverviews; i++ )
{
if( poEXIFOverview != NULL &&
poEXIFOverview->GetRasterXSize() >= nRasterXSize >> (i + 1) )
{
break;
}
GDALDataset* poImplicitOverview =
JPGDataset::Open(GetDescription(), NULL, NULL, 1 << (i + 1), FALSE);
if( poImplicitOverview == NULL )
break;
papoInternalOverviews[nInternalOverviewsCurrent] = poImplicitOverview;
nInternalOverviewsCurrent ++;
nInternalOverviewsToFree ++;
}
if( poEXIFOverview != NULL )
{
papoInternalOverviews[nInternalOverviewsCurrent] = poEXIFOverview;
nInternalOverviewsCurrent ++;
nInternalOverviewsToFree ++;
}
}
else if( poEXIFOverview )
{
papoInternalOverviews = (GDALDataset**) CPLMalloc(sizeof(GDALDataset*));
papoInternalOverviews[0] = poEXIFOverview;
nInternalOverviewsCurrent ++;
nInternalOverviewsToFree ++;
}
}
}
/************************************************************************/
/* IBuildOverviews() */
/************************************************************************/
CPLErr JPGDatasetCommon::IBuildOverviews( const char *pszResampling,
int nOverviewsListCount,
int *panOverviewList,
int nListBands, int *panBandList,
GDALProgressFunc pfnProgress,
void * pProgressData )
{
CPLErr eErr;
bHasInitInternalOverviews = TRUE;
nInternalOverviewsCurrent = 0;
eErr = GDALPamDataset::IBuildOverviews(pszResampling,
nOverviewsListCount,
panOverviewList,
nListBands, panBandList,
pfnProgress, pProgressData);
return eErr;
}
/************************************************************************/
/* ErrorOutOnNonFatalError() */
/************************************************************************/
int JPGDatasetCommon::ErrorOutOnNonFatalError()
{
if( sErrorStruct.bNonFatalErrorEncountered )
{
sErrorStruct.bNonFatalErrorEncountered = FALSE;
return TRUE;
}
return FALSE;
}
/************************************************************************/
/* FlushCache() */
/************************************************************************/
void JPGDatasetCommon::FlushCache()
{
GDALPamDataset::FlushCache();
if (bHasDoneJpegStartDecompress)
{
Restart();
}
/* For the needs of the implicit JPEG-in-TIFF overview mechanism */
for(int i = 0; i < nInternalOverviewsCurrent; i++)
papoInternalOverviews[i]->FlushCache();
}
#endif // !defined(JPGDataset)
/************************************************************************/
/* JPGDataset() */
/************************************************************************/
JPGDataset::JPGDataset()
{
sDInfo.data_precision = 8;
}
/************************************************************************/
/* ~JPGDataset() */
/************************************************************************/
JPGDataset::~JPGDataset()
{
GDALPamDataset::FlushCache();
if (bHasDoneJpegStartDecompress)
{
jpeg_abort_decompress( &sDInfo );
}
if (bHasDoneJpegCreateDecompress)
{
jpeg_destroy_decompress( &sDInfo );
}
}
/************************************************************************/
/* LoadScanline() */
/************************************************************************/
CPLErr JPGDataset::LoadScanline( int iLine )
{
if( nLoadedScanline == iLine )
return CE_None;
// setup to trap a fatal error.
if (setjmp(sErrorStruct.setjmp_buffer))
return CE_Failure;
if (!bHasDoneJpegStartDecompress)
{
jpeg_start_decompress( &sDInfo );
bHasDoneJpegStartDecompress = TRUE;
}
if( pabyScanline == NULL )
{
int nJPEGBands = 0;
switch(sDInfo.out_color_space)
{
case JCS_GRAYSCALE:
nJPEGBands = 1;
break;
case JCS_RGB:
case JCS_YCbCr:
nJPEGBands = 3;
break;
case JCS_CMYK:
case JCS_YCCK:
nJPEGBands = 4;
break;
default:
CPLAssert(0);
}
pabyScanline = (GByte *)
CPLMalloc(nJPEGBands * GetRasterXSize() * 2);
}
if( iLine < nLoadedScanline )
{
if( Restart() != CE_None )
return CE_Failure;
}
while( nLoadedScanline < iLine )
{
JSAMPLE *ppSamples;
ppSamples = (JSAMPLE *) pabyScanline;
jpeg_read_scanlines( &sDInfo, &ppSamples, 1 );
if( ErrorOutOnNonFatalError() )
return CE_Failure;
nLoadedScanline++;
}
return CE_None;
}
/************************************************************************/
/* LoadDefaultTables() */
/************************************************************************/
#if !defined(JPGDataset)
#define Q1table GDALJPEG_Q1table
#define Q2table GDALJPEG_Q2table
#define Q3table GDALJPEG_Q3table
#define Q4table GDALJPEG_Q4table
#define Q5table GDALJPEG_Q5table
#define AC_BITS GDALJPEG_AC_BITS
#define AC_HUFFVAL GDALJPEG_AC_HUFFVAL
#define DC_BITS GDALJPEG_DC_BITS
#define DC_HUFFVAL GDALJPEG_DC_HUFFVAL
static const GByte Q1table[64] =
{
8, 72, 72, 72, 72, 72, 72, 72, // 0 - 7
72, 72, 78, 74, 76, 74, 78, 89, // 8 - 15
81, 84, 84, 81, 89, 106, 93, 94, // 16 - 23
99, 94, 93, 106, 129, 111, 108, 116, // 24 - 31
116, 108, 111, 129, 135, 128, 136, 145, // 32 - 39
136, 128, 135, 155, 160, 177, 177, 160, // 40 - 47
155, 193, 213, 228, 213, 193, 255, 255, // 48 - 55
255, 255, 255, 255, 255, 255, 255, 255 // 56 - 63
};
static const GByte Q2table[64] =
{
8, 36, 36, 36,
36, 36, 36, 36, 36, 36, 39, 37, 38, 37, 39, 45, 41, 42, 42, 41, 45, 53,
47, 47, 50, 47, 47, 53, 65, 56, 54, 59, 59, 54, 56, 65, 68, 64, 69, 73,
69, 64, 68, 78, 81, 89, 89, 81, 78, 98,108,115,108, 98,130,144,144,130,
178,190,178,243,243,255
};
static const GByte Q3table[64] =
{
8, 10, 10, 10,
10, 10, 10, 10, 10, 10, 11, 10, 11, 10, 11, 13, 11, 12, 12, 11, 13, 15,
13, 13, 14, 13, 13, 15, 18, 16, 15, 16, 16, 15, 16, 18, 19, 18, 19, 21,
19, 18, 19, 22, 23, 25, 25, 23, 22, 27, 30, 32, 30, 27, 36, 40, 40, 36,
50, 53, 50, 68, 68, 91
};
static const GByte Q4table[64] =
{
8, 7, 7, 7,
7, 7, 7, 7, 7, 7, 8, 7, 8, 7, 8, 9, 8, 8, 8, 8, 9, 11,
9, 9, 10, 9, 9, 11, 13, 11, 11, 12, 12, 11, 11, 13, 14, 13, 14, 15,
14, 13, 14, 16, 16, 18, 18, 16, 16, 20, 22, 23, 22, 20, 26, 29, 29, 26,
36, 38, 36, 49, 49, 65
};
static const GByte Q5table[64] =
{
4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6,
5, 5, 6, 5, 5, 6, 7, 6, 6, 6, 6, 6, 6, 7, 8, 7, 8, 8,
8, 7, 8, 9, 9, 10, 10, 9, 9, 11, 12, 13, 12, 11, 14, 16, 16, 14,
20, 21, 20, 27, 27, 36
};
static const GByte AC_BITS[16] =
{ 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 };
static const GByte AC_HUFFVAL[256] = {
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08,
0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16,
0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6,
0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4,
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
0xF9, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const GByte DC_BITS[16] =
{ 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 };
static const GByte DC_HUFFVAL[256] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B };
void JPGDataset::LoadDefaultTables( int n )
{
if( nQLevel < 1 )
return;
/* -------------------------------------------------------------------- */
/* Load quantization table */
/* -------------------------------------------------------------------- */
int i;
JQUANT_TBL *quant_ptr;
const GByte *pabyQTable;
if( nQLevel == 1 )
pabyQTable = Q1table;
else if( nQLevel == 2 )
pabyQTable = Q2table;
else if( nQLevel == 3 )
pabyQTable = Q3table;
else if( nQLevel == 4 )
pabyQTable = Q4table;
else if( nQLevel == 5 )
pabyQTable = Q5table;
else
return;
if (sDInfo.quant_tbl_ptrs[n] == NULL)
sDInfo.quant_tbl_ptrs[n] =
jpeg_alloc_quant_table((j_common_ptr) &(sDInfo));
quant_ptr = sDInfo.quant_tbl_ptrs[n]; /* quant_ptr is JQUANT_TBL* */
for (i = 0; i < 64; i++) {
/* Qtable[] is desired quantization table, in natural array order */
quant_ptr->quantval[i] = pabyQTable[i];
}
/* -------------------------------------------------------------------- */
/* Load AC huffman table. */
/* -------------------------------------------------------------------- */
JHUFF_TBL *huff_ptr;
if (sDInfo.ac_huff_tbl_ptrs[n] == NULL)
sDInfo.ac_huff_tbl_ptrs[n] =
jpeg_alloc_huff_table((j_common_ptr)&sDInfo);
huff_ptr = sDInfo.ac_huff_tbl_ptrs[n]; /* huff_ptr is JHUFF_TBL* */
for (i = 1; i <= 16; i++) {
/* counts[i] is number of Huffman codes of length i bits, i=1..16 */
huff_ptr->bits[i] = AC_BITS[i-1];
}
for (i = 0; i < 256; i++) {
/* symbols[] is the list of Huffman symbols, in code-length order */
huff_ptr->huffval[i] = AC_HUFFVAL[i];
}
/* -------------------------------------------------------------------- */
/* Load DC huffman table. */
/* -------------------------------------------------------------------- */
if (sDInfo.dc_huff_tbl_ptrs[n] == NULL)
sDInfo.dc_huff_tbl_ptrs[n] =
jpeg_alloc_huff_table((j_common_ptr)&sDInfo);
huff_ptr = sDInfo.dc_huff_tbl_ptrs[n]; /* huff_ptr is JHUFF_TBL* */
for (i = 1; i <= 16; i++) {
/* counts[i] is number of Huffman codes of length i bits, i=1..16 */
huff_ptr->bits[i] = DC_BITS[i-1];
}
for (i = 0; i < 256; i++) {
/* symbols[] is the list of Huffman symbols, in code-length order */
huff_ptr->huffval[i] = DC_HUFFVAL[i];
}
}
#endif // !defined(JPGDataset)
/************************************************************************/
/* SetScaleNumAndDenom() */
/************************************************************************/
void JPGDataset::SetScaleNumAndDenom()
{
#if JPEG_LIB_VERSION > 62
sDInfo.scale_num = 8 / nScaleFactor;
sDInfo.scale_denom = 8;
#else
sDInfo.scale_num = 1;
sDInfo.scale_denom = nScaleFactor;
#endif
}
/************************************************************************/
/* Restart() */
/* */
/* Restart compressor at the beginning of the file. */
/************************************************************************/
CPLErr JPGDataset::Restart()
{
// setup to trap a fatal error.
if (setjmp(sErrorStruct.setjmp_buffer))
return CE_Failure;
J_COLOR_SPACE colorSpace = sDInfo.out_color_space;
J_COLOR_SPACE jpegColorSpace = sDInfo.jpeg_color_space;
jpeg_abort_decompress( &sDInfo );
jpeg_destroy_decompress( &sDInfo );
jpeg_create_decompress( &sDInfo );
#if !defined(JPGDataset)
LoadDefaultTables( 0 );
LoadDefaultTables( 1 );
LoadDefaultTables( 2 );
LoadDefaultTables( 3 );
#endif // !defined(JPGDataset)
/* -------------------------------------------------------------------- */
/* restart io. */
/* -------------------------------------------------------------------- */
VSIFSeekL( fpImage, nSubfileOffset, SEEK_SET );
jpeg_vsiio_src( &sDInfo, fpImage );
jpeg_read_header( &sDInfo, TRUE );
sDInfo.out_color_space = colorSpace;
nLoadedScanline = -1;
SetScaleNumAndDenom();
/* The following errors could happen when "recycling" an existing dataset */
/* particularly when triggered by the implicit overviews of JPEG-in-TIFF */
/* with a corrupted TIFF file */
if( nRasterXSize != (int)(sDInfo.image_width + nScaleFactor - 1) / nScaleFactor ||
nRasterYSize != (int)(sDInfo.image_height + nScaleFactor - 1) / nScaleFactor )
{
CPLError(CE_Failure, CPLE_AppDefined,
"Unexpected image dimension (%d x %d), where as (%d x %d) was expected",
(int)(sDInfo.image_width + nScaleFactor - 1) / nScaleFactor,
(int)(sDInfo.image_height + nScaleFactor - 1) / nScaleFactor,
nRasterXSize, nRasterYSize);
bHasDoneJpegStartDecompress = FALSE;
}
else if( jpegColorSpace != sDInfo.jpeg_color_space )
{
CPLError(CE_Failure, CPLE_AppDefined,
"Unexpected jpeg color space : %d",
sDInfo.jpeg_color_space);
bHasDoneJpegStartDecompress = FALSE;
}
else
{
jpeg_start_decompress( &sDInfo );
bHasDoneJpegStartDecompress = TRUE;
}
return CE_None;
}
#if !defined(JPGDataset)
/************************************************************************/
/* GetGeoTransform() */
/************************************************************************/
CPLErr JPGDatasetCommon::GetGeoTransform( double * padfTransform )
{
CPLErr eErr = GDALPamDataset::GetGeoTransform( padfTransform );
if( eErr != CE_Failure )
return eErr;
LoadWorldFileOrTab();
if( bGeoTransformValid )
{
memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 );
return CE_None;
}
else
return eErr;
}
/************************************************************************/
/* GetGCPCount() */
/************************************************************************/
int JPGDatasetCommon::GetGCPCount()
{
int nPAMGCPCount = GDALPamDataset::GetGCPCount();
if( nPAMGCPCount != 0 )
return nPAMGCPCount;
LoadWorldFileOrTab();
return nGCPCount;
}
/************************************************************************/
/* GetGCPProjection() */
/************************************************************************/
const char *JPGDatasetCommon::GetGCPProjection()
{
int nPAMGCPCount = GDALPamDataset::GetGCPCount();
if( nPAMGCPCount != 0 )
return GDALPamDataset::GetGCPProjection();
LoadWorldFileOrTab();
if( pszProjection && nGCPCount > 0 )
return pszProjection;
else
return "";
}
/************************************************************************/
/* GetGCPs() */
/************************************************************************/
const GDAL_GCP *JPGDatasetCommon::GetGCPs()
{
int nPAMGCPCount = GDALPamDataset::GetGCPCount();
if( nPAMGCPCount != 0 )
return GDALPamDataset::GetGCPs();
LoadWorldFileOrTab();
return pasGCPList;
}
/************************************************************************/
/* IRasterIO() */
/* */
/* Checks for what might be the most common read case */
/* (reading an entire 8bit, RGB JPEG), and */
/* optimizes for that case */
/************************************************************************/
CPLErr JPGDatasetCommon::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((eRWFlag == GF_Read) &&
(nBandCount == 3) &&
(nBands == 3) &&
(nXOff == 0) && (nYOff == 0) &&
(nXSize == nBufXSize) && (nXSize == nRasterXSize) &&
(nYSize == nBufYSize) && (nYSize == nRasterYSize) &&
(eBufType == GDT_Byte) && (GetDataPrecision() != 12) &&
(pData != NULL) &&
(panBandMap != NULL) &&
(panBandMap[0] == 1) && (panBandMap[1] == 2) && (panBandMap[2] == 3) &&
/* those color spaces need transformation to RGB */
GetOutColorSpace() != JCS_YCCK && GetOutColorSpace() != JCS_CMYK )
{
Restart();
int y;
CPLErr tmpError;
int x;
// Pixel interleaved case
if( nBandSpace == 1 )
{
for(y = 0; y < nYSize; ++y)
{
tmpError = LoadScanline(y);
if(tmpError != CE_None) return tmpError;
if( nPixelSpace == 3 )
{
memcpy(&(((GByte*)pData)[(y*nLineSpace)]),
pabyScanline, 3 * nXSize);
}
else
{
for(x = 0; x < nXSize; ++x)
{
memcpy(&(((GByte*)pData)[(y*nLineSpace) + (x*nPixelSpace)]),
(const GByte*)&(pabyScanline[x*3]), 3);
}
}
}
}
else
{
for(y = 0; y < nYSize; ++y)
{
tmpError = LoadScanline(y);
if(tmpError != CE_None) return tmpError;
for(x = 0; x < nXSize; ++x)
{
((GByte*)pData)[(y*nLineSpace) + (x*nPixelSpace)] = pabyScanline[x*3];
((GByte*)pData)[(y*nLineSpace) + (x*nPixelSpace) + nBandSpace] = pabyScanline[x*3+1];
((GByte*)pData)[(y*nLineSpace) + (x*nPixelSpace) + 2 * nBandSpace] = pabyScanline[x*3+2];
}
}
}
return CE_None;
}
return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
pData, nBufXSize, nBufYSize, eBufType,
nBandCount, panBandMap,
nPixelSpace, nLineSpace, nBandSpace,
psExtraArg);
}
#if JPEG_LIB_VERSION_MAJOR < 9
/************************************************************************/
/* JPEGDatasetIsJPEGLS() */
/************************************************************************/
static int JPEGDatasetIsJPEGLS( GDALOpenInfo * poOpenInfo )
{
GByte *pabyHeader = poOpenInfo->pabyHeader;
int nHeaderBytes = poOpenInfo->nHeaderBytes;
if( nHeaderBytes < 10 )
return FALSE;
if( pabyHeader[0] != 0xff
|| pabyHeader[1] != 0xd8 )
return FALSE;
int nOffset = 2;
for (;nOffset + 4 < nHeaderBytes;)
{
if (pabyHeader[nOffset] != 0xFF)
return FALSE;
int nMarker = pabyHeader[nOffset + 1];
if (nMarker == 0xF7 /* JPEG Extension 7, JPEG-LS */)
return TRUE;
if (nMarker == 0xF8 /* JPEG Extension 8, JPEG-LS Extension */)
return TRUE;
if (nMarker == 0xC3 /* Start of Frame 3 */)
return TRUE;
if (nMarker == 0xC7 /* Start of Frame 7 */)
return TRUE;
if (nMarker == 0xCB /* Start of Frame 11 */)
return TRUE;
if (nMarker == 0xCF /* Start of Frame 15 */)
return TRUE;
nOffset += 2 + pabyHeader[nOffset + 2] * 256 + pabyHeader[nOffset + 3];
}
return FALSE;
}
#endif
/************************************************************************/
/* Identify() */
/************************************************************************/
int JPGDatasetCommon::Identify( GDALOpenInfo * poOpenInfo )
{
GByte *pabyHeader = NULL;
int nHeaderBytes = poOpenInfo->nHeaderBytes;
/* -------------------------------------------------------------------- */
/* If it is a subfile, read the JPEG header. */
/* -------------------------------------------------------------------- */
if( EQUALN(poOpenInfo->pszFilename,"JPEG_SUBFILE:",13) )
return TRUE;
/* -------------------------------------------------------------------- */
/* First we check to see if the file has the expected header */
/* bytes. */
/* -------------------------------------------------------------------- */
pabyHeader = poOpenInfo->pabyHeader;
if( nHeaderBytes < 10 )
return FALSE;
if( pabyHeader[0] != 0xff
|| pabyHeader[1] != 0xd8
|| pabyHeader[2] != 0xff )
return FALSE;
#if JPEG_LIB_VERSION_MAJOR < 9
if (JPEGDatasetIsJPEGLS(poOpenInfo))
{
return FALSE;
}
#endif
return TRUE;
}
/************************************************************************/
/* Open() */
/************************************************************************/
GDALDataset *JPGDatasetCommon::Open( GDALOpenInfo * poOpenInfo )
{
if( !Identify( poOpenInfo ) )
return NULL;
if( poOpenInfo->eAccess == GA_Update )
{
CPLError( CE_Failure, CPLE_NotSupported,
"The JPEG driver does not support update access to existing"
" datasets.\n" );
return NULL;
}
VSILFILE* fpL = poOpenInfo->fpL;
poOpenInfo->fpL = NULL;
return JPGDataset::Open(poOpenInfo->pszFilename,
fpL,
poOpenInfo->GetSiblingFiles(),
1, TRUE);
}
#endif // !defined(JPGDataset)
/************************************************************************/
/* Open() */
/************************************************************************/
GDALDataset *JPGDataset::Open( const char* pszFilename,
VSILFILE* fpLin,
char** papszSiblingFiles,
int nScaleFactor,
int bDoPAMInitialize )
{
/* -------------------------------------------------------------------- */
/* If it is a subfile, read the JPEG header. */
/* -------------------------------------------------------------------- */
int bIsSubfile = FALSE;
GUIntBig subfile_offset = 0;
GUIntBig subfile_size = 0;
const char *real_filename = pszFilename;
int nQLevel = -1;
if( EQUALN(pszFilename,"JPEG_SUBFILE:",13) )
{
char** papszTokens;
int bScan = FALSE;
if( EQUALN(pszFilename,"JPEG_SUBFILE:Q",14) )
{
papszTokens = CSLTokenizeString2(pszFilename + 14, ",", 0);
if (CSLCount(papszTokens) >= 3)
{
nQLevel = atoi(papszTokens[0]);
subfile_offset = CPLScanUIntBig(papszTokens[1], strlen(papszTokens[1]));
subfile_size = CPLScanUIntBig(papszTokens[2], strlen(papszTokens[2]));
bScan = TRUE;
}
CSLDestroy(papszTokens);
}
else
{
papszTokens = CSLTokenizeString2(pszFilename + 13, ",", 0);
if (CSLCount(papszTokens) >= 2)
{
subfile_offset = CPLScanUIntBig(papszTokens[0], strlen(papszTokens[0]));
subfile_size = CPLScanUIntBig(papszTokens[1], strlen(papszTokens[1]));
bScan = TRUE;
}
CSLDestroy(papszTokens);
}
if( !bScan )
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Corrupt subfile definition: %s",
pszFilename );
return NULL;
}
real_filename = strstr(pszFilename,",");
if( real_filename != NULL )
real_filename = strstr(real_filename+1,",");
if( real_filename != NULL && nQLevel != -1 )
real_filename = strstr(real_filename+1,",");
if( real_filename != NULL )
real_filename++;
else
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Could not find filename in subfile definition.");
return NULL;
}
CPLDebug( "JPG",
"real_filename %s, offset=" CPL_FRMT_GUIB ", size=" CPL_FRMT_GUIB "\n",
real_filename, subfile_offset, subfile_size);
bIsSubfile = TRUE;
}
/* -------------------------------------------------------------------- */
/* Open the file using the large file api if necessary. */
/* -------------------------------------------------------------------- */
VSILFILE* fpImage;
if( fpLin == NULL )
{
fpImage = VSIFOpenL( real_filename, "rb" );
if( fpImage == NULL )
{
CPLError( CE_Failure, CPLE_OpenFailed,
"VSIFOpenL(%s) failed unexpectedly in jpgdataset.cpp",
real_filename );
return NULL;
}
}
else
fpImage = fpLin;
/* -------------------------------------------------------------------- */
/* Create a corresponding GDALDataset. */
/* -------------------------------------------------------------------- */
JPGDataset *poDS;
poDS = new JPGDataset();
poDS->nQLevel = nQLevel;
poDS->fpImage = fpImage;
/* -------------------------------------------------------------------- */
/* Move to the start of jpeg data. */
/* -------------------------------------------------------------------- */
poDS->nSubfileOffset = subfile_offset;
VSIFSeekL( poDS->fpImage, poDS->nSubfileOffset, SEEK_SET );
poDS->eAccess = GA_ReadOnly;
/* Will detect mismatch between compile-time and run-time libjpeg versions */
if (setjmp(poDS->sErrorStruct.setjmp_buffer))
{
delete poDS;
return NULL;
}
poDS->sDInfo.err = jpeg_std_error( &(poDS->sJErr) );
poDS->sJErr.error_exit = JPGDataset::ErrorExit;
poDS->sErrorStruct.p_previous_emit_message = poDS->sJErr.emit_message;
poDS->sJErr.emit_message = JPGDatasetCommon::EmitMessage;
poDS->sDInfo.client_data = (void *) &(poDS->sErrorStruct);
jpeg_create_decompress( &(poDS->sDInfo) );
poDS->bHasDoneJpegCreateDecompress = TRUE;
/* This is to address bug related in ticket #1795 */
if (CPLGetConfigOption("JPEGMEM", NULL) == NULL)
{
/* If the user doesn't provide a value for JPEGMEM, we want to be sure */
/* that at least 500 MB will be used before creating the temporary file */
poDS->sDInfo.mem->max_memory_to_use =
MAX(poDS->sDInfo.mem->max_memory_to_use, 500 * 1024 * 1024);
}
/* -------------------------------------------------------------------- */
/* Preload default NITF JPEG quantization tables. */
/* -------------------------------------------------------------------- */
#if !defined(JPGDataset)
poDS->LoadDefaultTables( 0 );
poDS->LoadDefaultTables( 1 );
poDS->LoadDefaultTables( 2 );
poDS->LoadDefaultTables( 3 );
#endif // !defined(JPGDataset)
/* -------------------------------------------------------------------- */
/* If a fatal error occurs after this, we will return NULL */
/* -------------------------------------------------------------------- */
if (setjmp(poDS->sErrorStruct.setjmp_buffer))
{
#if defined(JPEG_DUAL_MODE_8_12) && !defined(JPGDataset)
if (poDS->sDInfo.data_precision == 12)
{
fpImage = poDS->fpImage;
poDS->fpImage = NULL;
delete poDS;
return JPEGDataset12Open(pszFilename, fpImage, papszSiblingFiles,
nScaleFactor, bDoPAMInitialize);
}
#endif
delete poDS;
return NULL;
}
/* -------------------------------------------------------------------- */
/* Read pre-image data after ensuring the file is rewound. */
/* -------------------------------------------------------------------- */
VSIFSeekL( poDS->fpImage, poDS->nSubfileOffset, SEEK_SET );
jpeg_vsiio_src( &(poDS->sDInfo), poDS->fpImage );
jpeg_read_header( &(poDS->sDInfo), TRUE );
if( poDS->sDInfo.data_precision != 8
&& poDS->sDInfo.data_precision != 12 )
{
CPLError( CE_Failure, CPLE_NotSupported,
"GDAL JPEG Driver doesn't support files with precision of"
" other than 8 or 12 bits." );
delete poDS;
return NULL;
}
/* -------------------------------------------------------------------- */
/* Capture some information from the file that is of interest. */
/* -------------------------------------------------------------------- */
poDS->nScaleFactor = nScaleFactor;
poDS->SetScaleNumAndDenom();
poDS->nRasterXSize = (poDS->sDInfo.image_width + nScaleFactor - 1) / nScaleFactor;
poDS->nRasterYSize = (poDS->sDInfo.image_height + nScaleFactor - 1) / nScaleFactor;
poDS->sDInfo.out_color_space = poDS->sDInfo.jpeg_color_space;
poDS->eGDALColorSpace = poDS->sDInfo.jpeg_color_space;
if( poDS->sDInfo.jpeg_color_space == JCS_GRAYSCALE )
{
poDS->nBands = 1;
}
else if( poDS->sDInfo.jpeg_color_space == JCS_RGB )
{
poDS->nBands = 3;
}
else if( poDS->sDInfo.jpeg_color_space == JCS_YCbCr )
{
poDS->nBands = 3;
if (CSLTestBoolean(CPLGetConfigOption("GDAL_JPEG_TO_RGB", "YES")))
{
poDS->sDInfo.out_color_space = JCS_RGB;
poDS->eGDALColorSpace = JCS_RGB;
poDS->SetMetadataItem( "SOURCE_COLOR_SPACE", "YCbCr", "IMAGE_STRUCTURE" );
}
}
else if( poDS->sDInfo.jpeg_color_space == JCS_CMYK )
{
if (CSLTestBoolean(CPLGetConfigOption("GDAL_JPEG_TO_RGB", "YES")))
{
poDS->eGDALColorSpace = JCS_RGB;
poDS->nBands = 3;
poDS->SetMetadataItem( "SOURCE_COLOR_SPACE", "CMYK", "IMAGE_STRUCTURE" );
}
else
{
poDS->nBands = 4;
}
}
else if( poDS->sDInfo.jpeg_color_space == JCS_YCCK )
{
if (CSLTestBoolean(CPLGetConfigOption("GDAL_JPEG_TO_RGB", "YES")))
{
poDS->eGDALColorSpace = JCS_RGB;
poDS->nBands = 3;
poDS->SetMetadataItem( "SOURCE_COLOR_SPACE", "YCbCrK", "IMAGE_STRUCTURE" );
/* libjpeg does the translation from YCrCbK -> CMYK internally */
/* and we'll do the translation to RGB in IReadBlock() */
poDS->sDInfo.out_color_space = JCS_CMYK;
}
else
{
poDS->nBands = 4;
}
}
else
{
CPLError( CE_Failure, CPLE_NotSupported,
"Unrecognised jpeg_color_space value of %d.\n",
poDS->sDInfo.jpeg_color_space );
delete poDS;
return NULL;
}
/* -------------------------------------------------------------------- */
/* Create band information objects. */
/* -------------------------------------------------------------------- */
for( int iBand = 0; iBand < poDS->nBands; iBand++ )
poDS->SetBand( iBand+1, JPGCreateBand( poDS, iBand+1 ) );
/* -------------------------------------------------------------------- */
/* More metadata. */
/* -------------------------------------------------------------------- */
if( poDS->nBands > 1 )
{
poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
poDS->SetMetadataItem( "COMPRESSION", "JPEG", "IMAGE_STRUCTURE" );
}
/* -------------------------------------------------------------------- */
/* Initialize any PAM information. */
/* -------------------------------------------------------------------- */
poDS->SetDescription( pszFilename );
if( nScaleFactor == 1 && bDoPAMInitialize )
{
if( !bIsSubfile )
poDS->TryLoadXML( papszSiblingFiles );
else
poDS->nPamFlags |= GPF_NOSAVE;
/* -------------------------------------------------------------------- */
/* Open (external) overviews. */
/* -------------------------------------------------------------------- */
poDS->oOvManager.Initialize( poDS, real_filename, papszSiblingFiles );
/* In the case of a file downloaded through the HTTP driver, this one */
/* will unlink the temporary /vsimem file just after GDALOpen(), so */
/* later VSIFOpenL() when reading internal overviews would fail. */
/* Initialize them now */
if( strncmp(real_filename, "/vsimem/http_", strlen("/vsimem/http_")) == 0 )
{
poDS->InitInternalOverviews();
}
}
else
poDS->nPamFlags |= GPF_NOSAVE;
poDS->bIsSubfile = bIsSubfile;
return poDS;
}
#if !defined(JPGDataset)
/************************************************************************/
/* LoadWorldFileOrTab() */
/************************************************************************/
void JPGDatasetCommon::LoadWorldFileOrTab()
{
if (bIsSubfile)
return;
if (bHasTriedLoadWorldFileOrTab)
return;
bHasTriedLoadWorldFileOrTab = TRUE;
char* pszWldFilename = NULL;
/* TIROS3 JPEG files have a .wld extension, so don't look for .wld as */
/* as worldfile ! */
int bEndsWithWld = strlen(GetDescription()) > 4 &&
EQUAL( GetDescription() + strlen(GetDescription()) - 4, ".wld");
bGeoTransformValid =
GDALReadWorldFile2( GetDescription(), NULL,
adfGeoTransform,
oOvManager.GetSiblingFiles(), &pszWldFilename )
|| GDALReadWorldFile2( GetDescription(), ".jpw",
adfGeoTransform,
oOvManager.GetSiblingFiles(), &pszWldFilename )
|| ( !bEndsWithWld && GDALReadWorldFile2( GetDescription(), ".wld",
adfGeoTransform,
oOvManager.GetSiblingFiles(), &pszWldFilename ));
if( !bGeoTransformValid )
{
int bTabFileOK =
GDALReadTabFile2( GetDescription(), adfGeoTransform,
&pszProjection,
&nGCPCount, &pasGCPList,
oOvManager.GetSiblingFiles(), &pszWldFilename );
if( bTabFileOK && nGCPCount == 0 )
bGeoTransformValid = TRUE;
}
if (pszWldFilename)
{
osWldFilename = pszWldFilename;
CPLFree(pszWldFilename);
}
}
/************************************************************************/
/* GetFileList() */
/************************************************************************/
char **JPGDatasetCommon::GetFileList()
{
char **papszFileList = GDALPamDataset::GetFileList();
LoadWorldFileOrTab();
if (osWldFilename.size() != 0 &&
CSLFindString(papszFileList, osWldFilename) == -1)
{
papszFileList = CSLAddString( papszFileList, osWldFilename );
}
return papszFileList;
}
/************************************************************************/
/* CheckForMask() */
/************************************************************************/
void JPGDatasetCommon::CheckForMask()
{
GIntBig nFileSize;
GUInt32 nImageSize;
/* Save current position to avoid disturbing JPEG stream decoding */
vsi_l_offset nCurOffset = VSIFTellL(fpImage);
/* -------------------------------------------------------------------- */
/* Go to the end of the file, pull off four bytes, and see if */
/* it is plausibly the size of the real image data. */
/* -------------------------------------------------------------------- */
VSIFSeekL( fpImage, 0, SEEK_END );
nFileSize = VSIFTellL( fpImage );
VSIFSeekL( fpImage, nFileSize - 4, SEEK_SET );
VSIFReadL( &nImageSize, 4, 1, fpImage );
CPL_LSBPTR32( &nImageSize );
if( nImageSize < nFileSize / 2 || nImageSize > nFileSize - 4 )
goto end;
/* -------------------------------------------------------------------- */
/* If that seems ok, seek back, and verify that just preceeding */
/* the bitmask is an apparent end-of-jpeg-data marker. */
/* -------------------------------------------------------------------- */
GByte abyEOD[2];
VSIFSeekL( fpImage, nImageSize - 2, SEEK_SET );
VSIFReadL( abyEOD, 2, 1, fpImage );
if( abyEOD[0] != 0xff || abyEOD[1] != 0xd9 )
goto end;
/* -------------------------------------------------------------------- */
/* We seem to have a mask. Read it in. */
/* -------------------------------------------------------------------- */
nCMaskSize = (int) (nFileSize - nImageSize - 4);
pabyCMask = (GByte *) VSIMalloc(nCMaskSize);
if (pabyCMask == NULL)
{
CPLError(CE_Failure, CPLE_OutOfMemory,
"Cannot allocate memory (%d bytes) for mask compressed buffer",
nCMaskSize);
goto end;
}
VSIFReadL( pabyCMask, nCMaskSize, 1, fpImage );
CPLDebug( "JPEG", "Got %d byte compressed bitmask.",
nCMaskSize );
end:
VSIFSeekL( fpImage, nCurOffset, SEEK_SET );
}
/************************************************************************/
/* DecompressMask() */
/************************************************************************/
void JPGDatasetCommon::DecompressMask()
{
if( pabyCMask == NULL || pabyBitMask != NULL )
return;
/* -------------------------------------------------------------------- */
/* Allocate 1bit buffer - may be slightly larger than needed. */
/* -------------------------------------------------------------------- */
int nBufSize = nRasterYSize * ((nRasterXSize+7)/8);
pabyBitMask = (GByte *) VSIMalloc( nBufSize );
if (pabyBitMask == NULL)
{
CPLError(CE_Failure, CPLE_OutOfMemory,
"Cannot allocate memory (%d bytes) for mask uncompressed buffer",
nBufSize);
CPLFree(pabyCMask);
pabyCMask = NULL;
return;
}
/* -------------------------------------------------------------------- */
/* Decompress */
/* -------------------------------------------------------------------- */
void* pOut = CPLZLibInflate( pabyCMask, nCMaskSize,
pabyBitMask, nBufSize, NULL );
/* -------------------------------------------------------------------- */
/* Cleanup if an error occurs. */
/* -------------------------------------------------------------------- */
if( pOut == NULL )
{
CPLError( CE_Failure, CPLE_AppDefined,
"Failure decoding JPEG validity bitmask." );
CPLFree( pabyCMask );
pabyCMask = NULL;
CPLFree( pabyBitMask );
pabyBitMask = NULL;
}
else
{
const char* pszJPEGMaskBitOrder = CPLGetConfigOption("JPEG_MASK_BIT_ORDER", "AUTO");
if( EQUAL(pszJPEGMaskBitOrder, "LSB") )
bMaskLSBOrder = TRUE;
else if( EQUAL(pszJPEGMaskBitOrder, "MSB") )
bMaskLSBOrder = FALSE;
else if( nRasterXSize > 8 && nRasterYSize > 1 )
{
/* Test MSB ordering hypothesis in a very restrictive case where it is */
/* *obviously* ordered as MSB ! (unless someone coded something */
/* specifically to defeat the below logic) */
/* The case considered here is dop_465_6100.jpg from #5102 */
/* The mask is identical for each line, starting with 1's and ending with 0's */
/* (or starting with 0's and ending with 1's), and no other intermediate change. */
/* We can detect the MSB ordering since the lsb bits at the end of the first */
/* line will be set with the 1's of the beginning of the second line. */
/* We can only be sure of this heuristics if the change of value occurs in the */
/* middle of a byte, or if the raster width is not a multiple of 8. */
int iX;
int nPrevValBit = 0;
int nChangedValBit = 0;
for( iX = 0; iX < nRasterXSize; iX++ )
{
int nValBit = (pabyBitMask[iX>>3] & (0x1 << (7 - (iX&7)))) != 0;
if( iX == 0 )
nPrevValBit = nValBit;
else if( nValBit != nPrevValBit )
{
nPrevValBit = nValBit;
nChangedValBit ++;
if( nChangedValBit == 1 )
{
int bValChangedOnByteBoundary = ((iX % 8) == 0);
if( bValChangedOnByteBoundary && ((nRasterXSize % 8) == 0 ) )
break;
}
else
break;
}
int iNextLineX = iX + nRasterXSize;
int nNextLineValBit = (pabyBitMask[iNextLineX>>3] & (0x1 << (7 - (iNextLineX&7)))) != 0;
if( nValBit != nNextLineValBit )
break;
}
if( iX == nRasterXSize )
{
CPLDebug("JPEG", "Bit ordering in mask is guessed to be msb (unusual)");
bMaskLSBOrder = FALSE;
}
else
bMaskLSBOrder = TRUE;
}
else
bMaskLSBOrder = TRUE;
}
}
#endif // !defined(JPGDataset)
/************************************************************************/
/* ErrorExit() */
/************************************************************************/
void JPGDataset::ErrorExit(j_common_ptr cinfo)
{
GDALJPEGErrorStruct* psErrorStruct = (GDALJPEGErrorStruct* ) cinfo->client_data;
char buffer[JMSG_LENGTH_MAX];
/* Create the message */
(*cinfo->err->format_message) (cinfo, buffer);
/* Avoid error for a 12bit JPEG if reading from the 8bit JPEG driver and */
/* we have JPEG_DUAL_MODE_8_12 support, as we'll try again with 12bit JPEG */
/* driver */
#if defined(JPEG_DUAL_MODE_8_12) && !defined(JPGDataset)
if (strstr(buffer, "Unsupported JPEG data precision 12") == NULL)
#endif
CPLError( CE_Failure, CPLE_AppDefined,
"libjpeg: %s", buffer );
/* Return control to the setjmp point */
longjmp(psErrorStruct->setjmp_buffer, 1);
}
#if !defined(JPGDataset)
/************************************************************************/
/* EmitMessage() */
/************************************************************************/
void JPGDatasetCommon::EmitMessage(j_common_ptr cinfo, int msg_level)
{
GDALJPEGErrorStruct* psErrorStruct = (GDALJPEGErrorStruct* ) cinfo->client_data;
if( msg_level >= 0 ) /* Trace message */
{
if( psErrorStruct->p_previous_emit_message != NULL )
psErrorStruct->p_previous_emit_message(cinfo, msg_level);
}
else /* Warning : libjpeg will try to recover but the image will be likely corrupted */
{
struct jpeg_error_mgr * err = cinfo->err;
/* It's a warning message. Since corrupt files may generate many warnings,
* the policy implemented here is to show only the first warning,
* unless trace_level >= 3.
*/
if (err->num_warnings == 0 || err->trace_level >= 3)
{
char buffer[JMSG_LENGTH_MAX];
/* Create the message */
(*cinfo->err->format_message) (cinfo, buffer);
if( CSLTestBoolean(CPLGetConfigOption("GDAL_ERROR_ON_LIBJPEG_WARNING", "NO")) )
{
psErrorStruct->bNonFatalErrorEncountered = TRUE;
CPLError( CE_Failure, CPLE_AppDefined, "libjpeg: %s", buffer );
}
else
{
CPLError( CE_Warning, CPLE_AppDefined, "libjpeg: %s (this warning can be turned as an error by setting GDAL_ERROR_ON_LIBJPEG_WARNING to TRUE)", buffer );
}
}
/* Always count warnings in num_warnings. */
err->num_warnings++;
}
}
/************************************************************************/
/* JPGAddICCProfile() */
/* */
/* This function adds an ICC profile to a JPEG file. */
/************************************************************************/
void JPGAddICCProfile( struct jpeg_compress_struct *pInfo,
const char *pszICCProfile,
void (*p_jpeg_write_m_header) (j_compress_ptr cinfo, int marker, unsigned int datalen),
void (*p_jpeg_write_m_byte) (j_compress_ptr cinfo, int val))
{
if( pszICCProfile == NULL )
return;
/* Write out each segment of the ICC profile */
char *pEmbedBuffer = CPLStrdup(pszICCProfile);
GInt32 nEmbedLen = CPLBase64DecodeInPlace((GByte*)pEmbedBuffer);
char *pEmbedPtr = pEmbedBuffer;
char const * const paHeader = "ICC_PROFILE";
int nSegments = (nEmbedLen + 65518) / 65519;
int nSegmentID = 1;
while( nEmbedLen != 0)
{
/* 65535 - 16 bytes for header = 65519 */
int nChunkLen = (nEmbedLen > 65519)?65519:nEmbedLen;
nEmbedLen -= nChunkLen;
/* write marker and length. */
p_jpeg_write_m_header(pInfo, JPEG_APP0 + 2,
(unsigned int) (nChunkLen + 14));
/* Write identifier */
for(int i = 0; i < 12; i++)
p_jpeg_write_m_byte(pInfo, paHeader[i]);
/* Write ID and max ID */
p_jpeg_write_m_byte(pInfo, nSegmentID);
p_jpeg_write_m_byte(pInfo, nSegments);
/* Write ICC Profile */
for(int i = 0; i < nChunkLen; i++)
p_jpeg_write_m_byte(pInfo, pEmbedPtr[i]);
nSegmentID++;
pEmbedPtr += nChunkLen;
}
CPLFree(pEmbedBuffer);
}
/************************************************************************/
/* JPGAppendMask() */
/* */
/* This function appends a zlib compressed bitmask to a JPEG */
/* file (or really any file) pulled from an existing mask band. */
/************************************************************************/
// MSVC does not know that memset() has initialized sStream.
#ifdef _MSC_VER
# pragma warning(disable:4701)
#endif
CPLErr JPGAppendMask( const char *pszJPGFilename, GDALRasterBand *poMask,
GDALProgressFunc pfnProgress, void * pProgressData )
{
int nXSize = poMask->GetXSize();
int nYSize = poMask->GetYSize();
int nBitBufSize = nYSize * ((nXSize+7)/8);
int iX, iY;
GByte *pabyBitBuf, *pabyMaskLine;
CPLErr eErr = CE_None;
/* -------------------------------------------------------------------- */
/* Allocate uncompressed bit buffer. */
/* -------------------------------------------------------------------- */
pabyBitBuf = (GByte *) VSICalloc(1,nBitBufSize);
pabyMaskLine = (GByte *) VSIMalloc(nXSize);
if (pabyBitBuf == NULL || pabyMaskLine == NULL)
{
CPLError( CE_Failure, CPLE_OutOfMemory, "Out of memory");
eErr = CE_Failure;
}
/* No reason to set it to MSB, unless for debugging purposes */
/* to be able to generate a unusual LSB ordered mask (#5102) */
const char* pszJPEGMaskBitOrder = CPLGetConfigOption("JPEG_WRITE_MASK_BIT_ORDER", "LSB");
int bMaskLSBOrder = EQUAL(pszJPEGMaskBitOrder, "LSB");
/* -------------------------------------------------------------------- */
/* Set bit buffer from mask band, scanline by scanline. */
/* -------------------------------------------------------------------- */
GUInt32 iBit = 0;
for( iY = 0; eErr == CE_None && iY < nYSize; iY++ )
{
eErr = poMask->RasterIO( GF_Read, 0, iY, nXSize, 1,
pabyMaskLine, nXSize, 1, GDT_Byte, 0, 0, NULL );
if( eErr != CE_None )
break;
if( bMaskLSBOrder )
{
for( iX = 0; iX < nXSize; iX++ )
{
if( pabyMaskLine[iX] != 0 )
pabyBitBuf[iBit>>3] |= (0x1 << (iBit&7));
iBit++;
}
}
else
{
for( iX = 0; iX < nXSize; iX++ )
{
if( pabyMaskLine[iX] != 0 )
pabyBitBuf[iBit>>3] |= (0x1 << (7 - (iBit&7)));
iBit++;
}
}
if( eErr == CE_None
&& !pfnProgress( (iY + 1) / (double) nYSize, NULL, pProgressData ) )
{
eErr = CE_Failure;
CPLError( CE_Failure, CPLE_UserInterrupt,
"User terminated JPGAppendMask()" );
}
}
CPLFree( pabyMaskLine );
/* -------------------------------------------------------------------- */
/* Compress */
/* -------------------------------------------------------------------- */
GByte *pabyCMask = NULL;
if( eErr == CE_None )
{
pabyCMask = (GByte *) VSIMalloc(nBitBufSize + 30);
if (pabyCMask == NULL)
{
CPLError( CE_Failure, CPLE_OutOfMemory, "Out of memory");
eErr = CE_Failure;
}
}
size_t nTotalOut = 0;
if ( eErr == CE_None )
{
if( CPLZLibDeflate( pabyBitBuf, nBitBufSize, 9,
pabyCMask, nBitBufSize + 30,
&nTotalOut ) == NULL )
{
CPLError( CE_Failure, CPLE_AppDefined,
"Deflate compression of jpeg bit mask failed." );
eErr = CE_Failure;
}
}
/* -------------------------------------------------------------------- */
/* Write to disk, along with image file size. */
/* -------------------------------------------------------------------- */
if( eErr == CE_None )
{
VSILFILE *fpOut;
GUInt32 nImageSize;
fpOut = VSIFOpenL( pszJPGFilename, "r+" );
if( fpOut == NULL )
{
CPLError( CE_Failure, CPLE_AppDefined,
"Failed to open jpeg to append bitmask." );
eErr = CE_Failure;
}
else
{
VSIFSeekL( fpOut, 0, SEEK_END );
nImageSize = (GUInt32) VSIFTellL( fpOut );
CPL_LSBPTR32( &nImageSize );
if( VSIFWriteL( pabyCMask, 1, nTotalOut, fpOut )
!= nTotalOut )
{
CPLError( CE_Failure, CPLE_FileIO,
"Failure writing compressed bitmask.\n%s",
VSIStrerror( errno ) );
eErr = CE_Failure;
}
else
VSIFWriteL( &nImageSize, 4, 1, fpOut );
VSIFCloseL( fpOut );
}
}
CPLFree( pabyBitBuf );
CPLFree( pabyCMask );
return eErr;
}
/************************************************************************/
/* JPGAddEXIFOverview() */
/************************************************************************/
void JPGAddEXIFOverview( GDALDataType eWorkDT,
GDALDataset* poSrcDS, char** papszOptions,
j_compress_ptr cinfo,
void (*p_jpeg_write_m_header) (j_compress_ptr cinfo, int marker, unsigned int datalen),
void (*p_jpeg_write_m_byte) (j_compress_ptr cinfo, int val),
GDALDataset *(pCreateCopy)( const char *, GDALDataset *,
int, char **,
GDALProgressFunc pfnProgress,
void * pProgressData ))
{
int nBands = poSrcDS->GetRasterCount();
int nXSize = poSrcDS->GetRasterXSize();
int nYSize = poSrcDS->GetRasterYSize();
int bGenerateEXIFThumbnail =
CSLTestBoolean(CSLFetchNameValueDef(papszOptions, "EXIF_THUMBNAIL", "NO"));
const char* pszThumbnailWidth = CSLFetchNameValue(papszOptions, "THUMBNAIL_WIDTH");
const char* pszThumbnailHeight = CSLFetchNameValue(papszOptions, "THUMBNAIL_HEIGHT");
int nOvrWidth = 0, nOvrHeight = 0;
if( pszThumbnailWidth == NULL && pszThumbnailHeight == NULL )
{
if( nXSize >= nYSize )
{
nOvrWidth = 128;
}
else
{
nOvrHeight = 128;
}
}
if( pszThumbnailWidth != NULL )
{
nOvrWidth = atoi(pszThumbnailWidth);
if( nOvrWidth < 32 )
nOvrWidth = 32;
if( nOvrWidth > 1024 )
nOvrWidth = 1024;
}
if( pszThumbnailHeight != NULL )
{
nOvrHeight = atoi(pszThumbnailHeight);
if( nOvrHeight < 32 )
nOvrHeight = 32;
if( nOvrHeight > 1024 )
nOvrHeight = 1024;
}
if( nOvrWidth == 0 )
{
nOvrWidth = (int)((GIntBig)nOvrHeight * nXSize / nYSize);
if( nOvrWidth == 0 ) nOvrWidth = 1;
}
else if( nOvrHeight == 0 )
{
nOvrHeight = (int)((GIntBig)nOvrWidth * nYSize / nXSize);
if( nOvrHeight == 0 ) nOvrHeight = 1;
}
if( bGenerateEXIFThumbnail && nXSize > nOvrWidth && nYSize > nOvrHeight )
{
GDALDataset* poMemDS = MEMDataset::Create("", nOvrWidth, nOvrHeight, nBands, eWorkDT, NULL);
GDALRasterBand** papoSrcBands = (GDALRasterBand**)CPLMalloc(nBands * sizeof(GDALRasterBand*));
GDALRasterBand*** papapoOverviewBands= (GDALRasterBand***)CPLMalloc(nBands * sizeof(GDALRasterBand**));
int i;
for(i=0;i<nBands;i++)
{
papoSrcBands[i] = poSrcDS->GetRasterBand(i+1);
papapoOverviewBands[i] = (GDALRasterBand**)CPLMalloc(sizeof(GDALRasterBand*));
papapoOverviewBands[i][0] = poMemDS->GetRasterBand(i+1);
}
CPLErr eErr = GDALRegenerateOverviewsMultiBand(nBands, papoSrcBands,
1, papapoOverviewBands,
"AVERAGE", NULL, NULL);
CPLFree(papoSrcBands);
for(i=0;i<nBands;i++)
{
CPLFree(papapoOverviewBands[i]);
}
CPLFree(papapoOverviewBands);
if( eErr != CE_None )
{
GDALClose(poMemDS);
return;
}
CPLString osTmpFile(CPLSPrintf("/vsimem/ovrjpg%p",poMemDS));
GDALDataset* poOutDS = pCreateCopy(osTmpFile, poMemDS, 0, NULL, GDALDummyProgress, NULL);
delete poOutDS;
GDALClose(poMemDS);
vsi_l_offset nJPEGIfByteCount = 0;
GByte* pabyOvr = NULL;
if( poOutDS != NULL )
pabyOvr = VSIGetMemFileBuffer( osTmpFile, &nJPEGIfByteCount, TRUE );
VSIUnlink(osTmpFile);
unsigned int nMarkerSize = 6 + 16 + 5 * 12 + 4 + nJPEGIfByteCount;
if( pabyOvr == NULL )
{
CPLError(CE_Warning, CPLE_AppDefined, "Could not generate EXIF overview");
}
else if( nMarkerSize < 65536 )
{
p_jpeg_write_m_header( cinfo, JPEG_APP0 + 1, nMarkerSize );
p_jpeg_write_m_byte( cinfo, 'E' ); /* EXIF signature */
p_jpeg_write_m_byte( cinfo, 'x' );
p_jpeg_write_m_byte( cinfo, 'i' );
p_jpeg_write_m_byte( cinfo, 'f' );
p_jpeg_write_m_byte( cinfo, '\0' );
p_jpeg_write_m_byte( cinfo, '\0' );
p_jpeg_write_m_byte( cinfo, TIFF_LITTLEENDIAN & 0xff ); /* TIFF little-endian signature */
p_jpeg_write_m_byte( cinfo, TIFF_LITTLEENDIAN >> 8 );
p_jpeg_write_m_byte( cinfo, TIFF_VERSION );
p_jpeg_write_m_byte( cinfo, 0x00 );
p_jpeg_write_m_byte( cinfo, 8 ); /* Offset of IFD0 */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 ); /* Number of entries of IFD0 */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 14 ); /* Offset of IFD1 */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 5 ); /* Number of entries of IFD1 */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, JPEG_TIFF_IMAGEWIDTH & 0xff );
p_jpeg_write_m_byte( cinfo, (JPEG_TIFF_IMAGEWIDTH >> 8) & 0xff );
p_jpeg_write_m_byte( cinfo, TIFF_LONG );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 1 ); /* 1 value */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, nOvrWidth & 0xff );
p_jpeg_write_m_byte( cinfo, nOvrWidth >> 8 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, JPEG_TIFF_IMAGEHEIGHT & 0xff );
p_jpeg_write_m_byte( cinfo, JPEG_TIFF_IMAGEHEIGHT >> 8 );
p_jpeg_write_m_byte( cinfo, TIFF_LONG );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 1 ); /* 1 value */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, nOvrHeight & 0xff );
p_jpeg_write_m_byte( cinfo, nOvrHeight >> 8 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, JPEG_TIFF_COMPRESSION & 0xff );
p_jpeg_write_m_byte( cinfo, JPEG_TIFF_COMPRESSION >> 8 );
p_jpeg_write_m_byte( cinfo, TIFF_SHORT );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 1 ); /* 1 value */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 6 ); /* JPEG compression */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, JPEG_EXIF_JPEGIFOFSET & 0xff );
p_jpeg_write_m_byte( cinfo, JPEG_EXIF_JPEGIFOFSET >> 8 );
p_jpeg_write_m_byte( cinfo, TIFF_LONG );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 1 ); /* 1 value */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
const unsigned int nJPEGIfOffset = 16 + 5 * 12 + 4;
p_jpeg_write_m_byte( cinfo, nJPEGIfOffset & 0xff );
p_jpeg_write_m_byte( cinfo, nJPEGIfOffset >> 8 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, JPEG_EXIF_JPEGIFBYTECOUNT & 0xff );
p_jpeg_write_m_byte( cinfo, JPEG_EXIF_JPEGIFBYTECOUNT >> 8 );
p_jpeg_write_m_byte( cinfo, TIFF_LONG );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 1 ); /* 1 value */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, nJPEGIfByteCount & 0xff );
p_jpeg_write_m_byte( cinfo, nJPEGIfByteCount >> 8 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 ); /* Offset of IFD2 == 0 ==> end of TIFF directory */
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
p_jpeg_write_m_byte( cinfo, 0 );
for(int i=0; i<(int)nJPEGIfByteCount; i++)
p_jpeg_write_m_byte( cinfo, pabyOvr[i] );
}
else
{
CPLError(CE_Warning, CPLE_AppDefined,
"Cannot write EXIF thumbnail. The size of the EXIF segment exceeds 65536 bytes");
}
CPLFree(pabyOvr);
}
}
#endif // !defined(JPGDataset)
/************************************************************************/
/* CreateCopy() */
/************************************************************************/
GDALDataset *
JPGDataset::CreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
int bStrict, char ** papszOptions,
GDALProgressFunc pfnProgress, void * pProgressData )
{
int nBands = poSrcDS->GetRasterCount();
int nXSize = poSrcDS->GetRasterXSize();
int nYSize = poSrcDS->GetRasterYSize();
int nQuality = 75;
int bProgressive = FALSE;
int nCloneFlags = GCIF_PAM_DEFAULT;
const char* pszVal;
if( !pfnProgress( 0.0, NULL, pProgressData ) )
return NULL;
/* -------------------------------------------------------------------- */
/* Some some rudimentary checks */
/* -------------------------------------------------------------------- */
if( nBands != 1 && nBands != 3 && nBands != 4 )
{
CPLError( CE_Failure, CPLE_NotSupported,
"JPEG driver doesn't support %d bands. Must be 1 (grey), "
"3 (RGB) or 4 bands.\n", nBands );
return NULL;
}
if (nBands == 1 &&
poSrcDS->GetRasterBand(1)->GetColorTable() != NULL)
{
CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
"JPEG driver ignores color table. "
"The source raster band will be considered as grey level.\n"
"Consider using color table expansion (-expand option in gdal_translate)\n");
if (bStrict)
return NULL;
}
GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
#if defined(JPEG_LIB_MK1_OR_12BIT) || defined(JPEG_DUAL_MODE_8_12)
if( eDT != GDT_Byte && eDT != GDT_UInt16 )
{
CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
"JPEG driver doesn't support data type %s. "
"Only eight and twelve bit bands supported (Mk1 libjpeg).\n",
GDALGetDataTypeName(
poSrcDS->GetRasterBand(1)->GetRasterDataType()) );
if (bStrict)
return NULL;
}
if( eDT == GDT_UInt16 || eDT == GDT_Int16 )
{
#if defined(JPEG_DUAL_MODE_8_12) && !defined(JPGDataset)
return JPEGDataset12CreateCopy(pszFilename, poSrcDS,
bStrict, papszOptions,
pfnProgress, pProgressData );
#else
eDT = GDT_UInt16;
#endif
}
else
eDT = GDT_Byte;
#else
if( eDT != GDT_Byte )
{
CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
"JPEG driver doesn't support data type %s. "
"Only eight bit byte bands supported.\n",
GDALGetDataTypeName(
poSrcDS->GetRasterBand(1)->GetRasterDataType()) );
if (bStrict)
return NULL;
}
eDT = GDT_Byte; // force to 8bit.
#endif
/* -------------------------------------------------------------------- */
/* What options has the user selected? */
/* -------------------------------------------------------------------- */
if( CSLFetchNameValue(papszOptions,"QUALITY") != NULL )
{
nQuality = atoi(CSLFetchNameValue(papszOptions,"QUALITY"));
if( nQuality < 10 || nQuality > 100 )
{
CPLError( CE_Failure, CPLE_IllegalArg,
"QUALITY=%s is not a legal value in the range 10-100.",
CSLFetchNameValue(papszOptions,"QUALITY") );
return NULL;
}
}
bProgressive = CSLFetchBoolean( papszOptions, "PROGRESSIVE", FALSE );
/* -------------------------------------------------------------------- */
/* Create the dataset. */
/* -------------------------------------------------------------------- */
VSILFILE *fpImage;
fpImage = VSIFOpenL( pszFilename, "wb" );
if( fpImage == NULL )
{
CPLError( CE_Failure, CPLE_OpenFailed,
"Unable to create jpeg file %s.\n",
pszFilename );
return NULL;
}
/* -------------------------------------------------------------------- */
/* Initialize JPG access to the file. */
/* -------------------------------------------------------------------- */
struct jpeg_compress_struct sCInfo;
struct jpeg_error_mgr sJErr;
GDALJPEGErrorStruct sErrorStruct;
sErrorStruct.bNonFatalErrorEncountered = FALSE;
if (setjmp(sErrorStruct.setjmp_buffer))
{
VSIFCloseL( fpImage );
return NULL;
}
sCInfo.err = jpeg_std_error( &sJErr );
sJErr.error_exit = JPGDataset::ErrorExit;
sErrorStruct.p_previous_emit_message = sJErr.emit_message;
sJErr.emit_message = JPGDatasetCommon::EmitMessage;
sCInfo.client_data = (void *) &(sErrorStruct);
jpeg_create_compress( &sCInfo );
jpeg_vsiio_dest( &sCInfo, fpImage );
sCInfo.image_width = nXSize;
sCInfo.image_height = nYSize;
sCInfo.input_components = nBands;
if( nBands == 3 )
sCInfo.in_color_space = JCS_RGB;
else if( nBands == 1 )
sCInfo.in_color_space = JCS_GRAYSCALE;
else
sCInfo.in_color_space = JCS_UNKNOWN;
jpeg_set_defaults( &sCInfo );
/* This is to address bug related in ticket #1795 */
if (CPLGetConfigOption("JPEGMEM", NULL) == NULL)
{
/* If the user doesn't provide a value for JPEGMEM, we want to be sure */
/* that at least 500 MB will be used before creating the temporary file */
sCInfo.mem->max_memory_to_use =
MAX(sCInfo.mem->max_memory_to_use, 500 * 1024 * 1024);
}
if( eDT == GDT_UInt16 )
{
sCInfo.data_precision = 12;
}
else
{
sCInfo.data_precision = 8;
}
pszVal = CSLFetchNameValue(papszOptions, "ARITHMETIC");
if( pszVal )
sCInfo.arith_code = CSLTestBoolean(pszVal);
/* Optimized Huffman coding. Supposedly slower according to libjpeg doc */
/* but no longer significant with today computer standards */
if( !sCInfo.arith_code )
sCInfo.optimize_coding = TRUE;
#if JPEG_LIB_VERSION_MAJOR >= 8 && \
(JPEG_LIB_VERSION_MAJOR > 8 || JPEG_LIB_VERSION_MINOR >= 3)
pszVal = CSLFetchNameValue(papszOptions, "BLOCK");
if( pszVal )
sCInfo.block_size = atoi(pszVal);
#endif
#if JPEG_LIB_VERSION_MAJOR >= 9
pszVal = CSLFetchNameValue(papszOptions, "COLOR_TRANSFORM");
if( pszVal )
{
sCInfo.color_transform = EQUAL(pszVal, "RGB1") ? JCT_SUBTRACT_GREEN : JCT_NONE;
jpeg_set_colorspace(&sCInfo, JCS_RGB);
}
else
#endif
/* Mostly for debugging purposes */
if( nBands == 3 && CSLTestBoolean(CPLGetConfigOption("JPEG_WRITE_RGB", "NO")) )
{
jpeg_set_colorspace(&sCInfo, JCS_RGB);
}
GDALDataType eWorkDT;
#ifdef JPEG_LIB_MK1
sCInfo.bits_in_jsample = sCInfo.data_precision;
eWorkDT = GDT_UInt16; /* Always force to 16 bit for JPEG_LIB_MK1 */
#else
eWorkDT = eDT;
#endif
jpeg_set_quality( &sCInfo, nQuality, TRUE );
if( bProgressive )
jpeg_simple_progression( &sCInfo );
jpeg_start_compress( &sCInfo, TRUE );
JPGAddEXIFOverview( eWorkDT, poSrcDS, papszOptions,
&sCInfo, jpeg_write_m_header, jpeg_write_m_byte,
CreateCopy );
/* -------------------------------------------------------------------- */
/* Add comment if available */
/* -------------------------------------------------------------------- */
const char *pszComment = CSLFetchNameValue(papszOptions, "COMMENT");
if( pszComment )
jpeg_write_marker(&sCInfo, JPEG_COM, (const JOCTET*)pszComment,
(unsigned int)strlen(pszComment));
/* -------------------------------------------------------------------- */
/* Save ICC profile if available */
/* -------------------------------------------------------------------- */
const char *pszICCProfile = CSLFetchNameValue(papszOptions, "SOURCE_ICC_PROFILE");
if (pszICCProfile == NULL)
pszICCProfile = poSrcDS->GetMetadataItem( "SOURCE_ICC_PROFILE", "COLOR_PROFILE" );
if (pszICCProfile != NULL)
JPGAddICCProfile( &sCInfo, pszICCProfile,
jpeg_write_m_header, jpeg_write_m_byte );
/* -------------------------------------------------------------------- */
/* Does the source have a mask? If so, we will append it to the */
/* jpeg file after the imagery. */
/* -------------------------------------------------------------------- */
int nMaskFlags = poSrcDS->GetRasterBand(1)->GetMaskFlags();
int bAppendMask =( !(nMaskFlags & GMF_ALL_VALID)
&& (nBands == 1 || (nMaskFlags & GMF_PER_DATASET)) );
bAppendMask &= CSLFetchBoolean( papszOptions, "INTERNAL_MASK", TRUE );
/* -------------------------------------------------------------------- */
/* Loop over image, copying image data. */
/* -------------------------------------------------------------------- */
GByte *pabyScanline;
CPLErr eErr = CE_None;
int nWorkDTSize = GDALGetDataTypeSize(eWorkDT) / 8;
bool bClipWarn = false;
pabyScanline = (GByte *) CPLMalloc( nBands * nXSize * nWorkDTSize );
for( int iLine = 0; iLine < nYSize && eErr == CE_None; iLine++ )
{
JSAMPLE *ppSamples;
eErr = poSrcDS->RasterIO( GF_Read, 0, iLine, nXSize, 1,
pabyScanline, nXSize, 1, eWorkDT,
nBands, NULL,
nBands*nWorkDTSize,
nBands * nXSize * nWorkDTSize, nWorkDTSize, NULL );
// clamp 16bit values to 12bit.
if( nWorkDTSize == 2 )
{
GUInt16 *panScanline = (GUInt16 *) pabyScanline;
int iPixel;
for( iPixel = 0; iPixel < nXSize*nBands; iPixel++ )
{
if( panScanline[iPixel] > 4095 )
{
panScanline[iPixel] = 4095;
if( !bClipWarn )
{
bClipWarn = true;
CPLError( CE_Warning, CPLE_AppDefined,
"One or more pixels clipped to fit 12bit domain for jpeg output." );
}
}
}
}
ppSamples = (JSAMPLE *) pabyScanline;
if( eErr == CE_None )
jpeg_write_scanlines( &sCInfo, &ppSamples, 1 );
if( eErr == CE_None
&& !pfnProgress( (iLine+1) / ((bAppendMask ? 2 : 1) * (double) nYSize),
NULL, pProgressData ) )
{
eErr = CE_Failure;
CPLError( CE_Failure, CPLE_UserInterrupt,
"User terminated CreateCopy()" );
}
}
/* -------------------------------------------------------------------- */
/* Cleanup and close. */
/* -------------------------------------------------------------------- */
CPLFree( pabyScanline );
if( eErr == CE_None )
jpeg_finish_compress( &sCInfo );
jpeg_destroy_compress( &sCInfo );
VSIFCloseL( fpImage );
if( eErr != CE_None )
{
VSIUnlink( pszFilename );
return NULL;
}
/* -------------------------------------------------------------------- */
/* Append masks to the jpeg file if necessary. */
/* -------------------------------------------------------------------- */
if( bAppendMask )
{
CPLDebug( "JPEG", "Appending Mask Bitmap" );
void* pScaledData = GDALCreateScaledProgress( 0.5, 1, pfnProgress, pProgressData );
eErr = JPGAppendMask( pszFilename, poSrcDS->GetRasterBand(1)->GetMaskBand(),
GDALScaledProgress, pScaledData );
GDALDestroyScaledProgress( pScaledData );
nCloneFlags &= (~GCIF_MASK);
if( eErr != CE_None )
{
VSIUnlink( pszFilename );
return NULL;
}
}
/* -------------------------------------------------------------------- */
/* Do we need a world file? */
/* -------------------------------------------------------------------- */
if( CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) )
{
double adfGeoTransform[6];
poSrcDS->GetGeoTransform( adfGeoTransform );
GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform );
}
/* -------------------------------------------------------------------- */
/* Re-open dataset, and copy any auxiliary pam information. */
/* -------------------------------------------------------------------- */
/* If outputing to stdout, we can't reopen it, so we'll return */
/* a fake dataset to make the caller happy */
if( CSLTestBoolean(CPLGetConfigOption("GDAL_OPEN_AFTER_COPY", "YES")) )
{
CPLPushErrorHandler(CPLQuietErrorHandler);
JPGDataset *poDS = (JPGDataset*) Open( pszFilename, NULL, NULL, 1, TRUE );
CPLPopErrorHandler();
if( poDS )
{
poDS->CloneInfo( poSrcDS, nCloneFlags );
return poDS;
}
CPLErrorReset();
}
JPGDataset* poJPG_DS = new JPGDataset();
poJPG_DS->nRasterXSize = nXSize;
poJPG_DS->nRasterYSize = nYSize;
for(int i=0;i<nBands;i++)
poJPG_DS->SetBand( i+1, JPGCreateBand( poJPG_DS, i+1) );
return poJPG_DS;
}
/************************************************************************/
/* GDALRegister_JPEG() */
/************************************************************************/
#if !defined(JPGDataset)
class GDALJPGDriver: public GDALDriver
{
public:
GDALJPGDriver() {}
char **GetMetadata( const char * pszDomain = "" );
const char *GetMetadataItem( const char * pszName,
const char * pszDomain = "" );
};
char** GDALJPGDriver::GetMetadata( const char * pszDomain )
{
GetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST);
return GDALDriver::GetMetadata(pszDomain);
}
static void GDALJPEGIsArithmeticCodingAvailableErrorExit(j_common_ptr cinfo)
{
jmp_buf* p_setjmp_buffer = (jmp_buf* ) cinfo->client_data;
/* Return control to the setjmp point */
longjmp(*p_setjmp_buffer, 1);
}
/* Runtime check if arithmetic coding is available */
static int GDALJPEGIsArithmeticCodingAvailable()
{
struct jpeg_compress_struct sCInfo;
struct jpeg_error_mgr sJErr;
jmp_buf setjmp_buffer;
if (setjmp(setjmp_buffer))
{
jpeg_destroy_compress( &sCInfo );
return FALSE;
}
sCInfo.err = jpeg_std_error( &sJErr );
sJErr.error_exit = GDALJPEGIsArithmeticCodingAvailableErrorExit;
sCInfo.client_data = (void *) &(setjmp_buffer);
jpeg_create_compress( &sCInfo );
/* Hopefully nothing will be written */
jpeg_stdio_dest(&sCInfo, stderr);
sCInfo.image_width = 1;
sCInfo.image_height = 1;
sCInfo.input_components = 1;
sCInfo.in_color_space = JCS_UNKNOWN;
jpeg_set_defaults( &sCInfo );
sCInfo.arith_code = TRUE;
jpeg_start_compress( &sCInfo, FALSE );
jpeg_abort_compress( &sCInfo );
jpeg_destroy_compress( &sCInfo );
return TRUE;
}
const char *GDALJPGDriver::GetMetadataItem( const char * pszName,
const char * pszDomain )
{
if( pszName != NULL && EQUAL(pszName, GDAL_DMD_CREATIONOPTIONLIST) &&
(pszDomain == NULL || EQUAL(pszDomain, "")) &&
GDALDriver::GetMetadataItem(pszName, pszDomain) == NULL )
{
CPLString osCreationOptions =
"<CreationOptionList>\n"
" <Option name='PROGRESSIVE' type='boolean' description='whether to generate a progressive JPEG' default='NO'/>\n"
" <Option name='QUALITY' type='int' description='good=100, bad=0, default=75'/>\n"
" <Option name='WORLDFILE' type='boolean' description='whether to geneate a worldfile' default='NO'/>\n"
" <Option name='INTERNAL_MASK' type='boolean' description='whether to generate a validity mask' default='YES'/>\n";
if( GDALJPEGIsArithmeticCodingAvailable() )
osCreationOptions +=
" <Option name='ARITHMETIC' type='boolean' description='whether to use arithmetic encoding' default='NO'/>\n";
osCreationOptions +=
#if JPEG_LIB_VERSION_MAJOR >= 8 && \
(JPEG_LIB_VERSION_MAJOR > 8 || JPEG_LIB_VERSION_MINOR >= 3)
" <Option name='BLOCK' type='int' description='between 1 and 16'/>\n"
#endif
#if JPEG_LIB_VERSION_MAJOR >= 9
" <Option name='COLOR_TRANSFORM' type='string-select'>\n"
" <Value>RGB</Value>"
" <Value>RGB1</Value>"
" </Option>"
#endif
" <Option name='COMMENT' description='Comment' type='string'/>\n"
" <Option name='SOURCE_ICC_PROFILE' description='ICC profile encoded in Base64' type='string'/>\n"
" <Option name='EXIF_THUMBNAIL' type='boolean' description='whether to generate an EXIF thumbnail(overview). By default its max dimension will be 128' default='NO'/>\n"
" <Option name='THUMBNAIL_WIDTH' type='int' description='Forced thumbnail width' min='32' max='512'/>\n"
" <Option name='THUMBNAIL_HEIGHT' type='int' description='Forced thumbnail height' min='32' max='512'/>\n"
"</CreationOptionList>\n";
SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST, osCreationOptions );
}
return GDALDriver::GetMetadataItem(pszName, pszDomain);
}
void GDALRegister_JPEG()
{
GDALDriver *poDriver;
if( GDALGetDriverByName( "JPEG" ) == NULL )
{
poDriver = new GDALJPGDriver();
poDriver->SetDescription( "JPEG" );
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
"JPEG JFIF" );
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
"frmt_jpeg.html" );
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "jpg" );
poDriver->SetMetadataItem( GDAL_DMD_EXTENSIONS, "jpg jpeg" );
poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/jpeg" );
#if defined(JPEG_LIB_MK1_OR_12BIT) || defined(JPEG_DUAL_MODE_8_12)
poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
"Byte UInt16" );
#else
poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
"Byte" );
#endif
poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
poDriver->pfnIdentify = JPGDatasetCommon::Identify;
poDriver->pfnOpen = JPGDatasetCommon::Open;
poDriver->pfnCreateCopy = JPGDataset::CreateCopy;
GetGDALDriverManager()->RegisterDriver( poDriver );
}
}
#endif