mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-19 14:15:21 -06:00
3579 lines
141 KiB
C++
3579 lines
141 KiB
C++
/******************************************************************************
|
|
* $Id: openjpegdataset.cpp 29330 2015-06-14 12:11:11Z rouault $
|
|
*
|
|
* Project: JPEG2000 driver based on OpenJPEG library
|
|
* Purpose: JPEG2000 driver based on OpenJPEG library
|
|
* Author: Even Rouault, <even dot rouault at spatialys dot com>
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys dot com>
|
|
* Copyright (c) 2015, European Union (European Environment Agency)
|
|
*
|
|
* 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.
|
|
****************************************************************************/
|
|
|
|
/* This file is to be used with openjpeg 2.0 */
|
|
|
|
#if defined(OPENJPEG_VERSION) && OPENJPEG_VERSION >= 20100
|
|
#include <openjpeg-2.1/openjpeg.h>
|
|
#else
|
|
#include <stdio.h> /* openjpeg.h needs FILE* */
|
|
#include <openjpeg-2.0/openjpeg.h>
|
|
#endif
|
|
#include <vector>
|
|
|
|
#include "gdaljp2abstractdataset.h"
|
|
#include "cpl_string.h"
|
|
#include "gdaljp2metadata.h"
|
|
#include "cpl_multiproc.h"
|
|
#include "cpl_atomic_ops.h"
|
|
#include "vrt/vrtdataset.h"
|
|
|
|
CPL_CVSID("$Id: openjpegdataset.cpp 29330 2015-06-14 12:11:11Z rouault $");
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGDataset_ErrorCallback() */
|
|
/************************************************************************/
|
|
|
|
static void JP2OpenJPEGDataset_ErrorCallback(const char *pszMsg, CPL_UNUSED void *unused)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "%s", pszMsg);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGDataset_WarningCallback() */
|
|
/************************************************************************/
|
|
|
|
static void JP2OpenJPEGDataset_WarningCallback(const char *pszMsg, CPL_UNUSED void *unused)
|
|
{
|
|
if( strcmp(pszMsg, "Empty SOT marker detected: Psot=12.\n") == 0 )
|
|
{
|
|
static int bWarningEmitted = FALSE;
|
|
if( bWarningEmitted )
|
|
return;
|
|
bWarningEmitted = TRUE;
|
|
}
|
|
if( strcmp(pszMsg, "JP2 box which are after the codestream will not be read by this function.\n") != 0 )
|
|
CPLError(CE_Warning, CPLE_AppDefined, "%s", pszMsg);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGDataset_InfoCallback() */
|
|
/************************************************************************/
|
|
|
|
static void JP2OpenJPEGDataset_InfoCallback(const char *pszMsg, CPL_UNUSED void *unused)
|
|
{
|
|
char* pszMsgTmp = CPLStrdup(pszMsg);
|
|
int nLen = (int)strlen(pszMsgTmp);
|
|
while( nLen > 0 && pszMsgTmp[nLen-1] == '\n' )
|
|
{
|
|
pszMsgTmp[nLen-1] = '\0';
|
|
nLen --;
|
|
}
|
|
CPLDebug("OPENJPEG", "info: %s", pszMsgTmp);
|
|
CPLFree(pszMsgTmp);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
VSILFILE* fp;
|
|
vsi_l_offset nBaseOffset;
|
|
} JP2OpenJPEGFile;
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGDataset_Read() */
|
|
/************************************************************************/
|
|
|
|
static OPJ_SIZE_T JP2OpenJPEGDataset_Read(void* pBuffer, OPJ_SIZE_T nBytes,
|
|
void *pUserData)
|
|
{
|
|
JP2OpenJPEGFile* psJP2OpenJPEGFile = (JP2OpenJPEGFile* )pUserData;
|
|
int nRet = VSIFReadL(pBuffer, 1, nBytes, psJP2OpenJPEGFile->fp);
|
|
#ifdef DEBUG_IO
|
|
CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Read(%d) = %d", (int)nBytes, nRet);
|
|
#endif
|
|
if (nRet == 0)
|
|
nRet = -1;
|
|
|
|
return nRet;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGDataset_Write() */
|
|
/************************************************************************/
|
|
|
|
static OPJ_SIZE_T JP2OpenJPEGDataset_Write(void* pBuffer, OPJ_SIZE_T nBytes,
|
|
void *pUserData)
|
|
{
|
|
JP2OpenJPEGFile* psJP2OpenJPEGFile = (JP2OpenJPEGFile* )pUserData;
|
|
int nRet = VSIFWriteL(pBuffer, 1, nBytes, psJP2OpenJPEGFile->fp);
|
|
#ifdef DEBUG_IO
|
|
CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Write(%d) = %d", (int)nBytes, nRet);
|
|
#endif
|
|
return nRet;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGDataset_Seek() */
|
|
/************************************************************************/
|
|
|
|
static OPJ_BOOL JP2OpenJPEGDataset_Seek(OPJ_OFF_T nBytes, void * pUserData)
|
|
{
|
|
JP2OpenJPEGFile* psJP2OpenJPEGFile = (JP2OpenJPEGFile* )pUserData;
|
|
#ifdef DEBUG_IO
|
|
CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Seek(%d)", (int)nBytes);
|
|
#endif
|
|
return VSIFSeekL(psJP2OpenJPEGFile->fp, psJP2OpenJPEGFile->nBaseOffset +nBytes,
|
|
SEEK_SET) == 0;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGDataset_Skip() */
|
|
/************************************************************************/
|
|
|
|
static OPJ_OFF_T JP2OpenJPEGDataset_Skip(OPJ_OFF_T nBytes, void * pUserData)
|
|
{
|
|
JP2OpenJPEGFile* psJP2OpenJPEGFile = (JP2OpenJPEGFile* )pUserData;
|
|
vsi_l_offset nOffset = VSIFTellL(psJP2OpenJPEGFile->fp);
|
|
nOffset += nBytes;
|
|
#ifdef DEBUG_IO
|
|
CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Skip(%d -> " CPL_FRMT_GUIB ")",
|
|
(int)nBytes, (GUIntBig)nOffset);
|
|
#endif
|
|
VSIFSeekL(psJP2OpenJPEGFile->fp, nOffset, SEEK_SET);
|
|
return nBytes;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* JP2OpenJPEGDataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class JP2OpenJPEGRasterBand;
|
|
|
|
class JP2OpenJPEGDataset : public GDALJP2AbstractDataset
|
|
{
|
|
friend class JP2OpenJPEGRasterBand;
|
|
|
|
VSILFILE *fp; /* Large FILE API */
|
|
vsi_l_offset nCodeStreamStart;
|
|
vsi_l_offset nCodeStreamLength;
|
|
|
|
OPJ_COLOR_SPACE eColorSpace;
|
|
int nRedIndex;
|
|
int nGreenIndex;
|
|
int nBlueIndex;
|
|
int nAlphaIndex;
|
|
|
|
int bIs420;
|
|
|
|
int iLevel;
|
|
int nOverviewCount;
|
|
JP2OpenJPEGDataset** papoOverviewDS;
|
|
int bUseSetDecodeArea;
|
|
|
|
int nThreads;
|
|
int GetNumThreads();
|
|
int bEnoughMemoryToLoadOtherBands;
|
|
int bRewrite;
|
|
int bHasGeoreferencingAtOpening;
|
|
|
|
protected:
|
|
virtual int CloseDependentDatasets();
|
|
|
|
public:
|
|
JP2OpenJPEGDataset();
|
|
~JP2OpenJPEGDataset();
|
|
|
|
static int Identify( GDALOpenInfo * poOpenInfo );
|
|
static GDALDataset *Open( GDALOpenInfo * );
|
|
static GDALDataset *CreateCopy( const char * pszFilename,
|
|
GDALDataset *poSrcDS,
|
|
int bStrict, char ** papszOptions,
|
|
GDALProgressFunc pfnProgress,
|
|
void * pProgressData );
|
|
|
|
virtual CPLErr SetProjection( const char * );
|
|
virtual CPLErr SetGeoTransform( double* );
|
|
virtual CPLErr SetGCPs( int nGCPCount, const GDAL_GCP *pasGCPList,
|
|
const char *pszGCPProjection );
|
|
virtual CPLErr SetMetadata( char ** papszMetadata,
|
|
const char * pszDomain = "" );
|
|
virtual CPLErr SetMetadataItem( const char * pszName,
|
|
const char * pszValue,
|
|
const char * pszDomain = "" );
|
|
|
|
virtual CPLErr 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);
|
|
|
|
static void WriteBox(VSILFILE* fp, GDALJP2Box* poBox);
|
|
static void WriteGDALMetadataBox( VSILFILE* fp, GDALDataset* poSrcDS,
|
|
char** papszOptions );
|
|
static void WriteXMLBoxes( VSILFILE* fp, GDALDataset* poSrcDS,
|
|
char** papszOptions );
|
|
static void WriteXMPBox( VSILFILE* fp, GDALDataset* poSrcDS,
|
|
char** papszOptions );
|
|
static void WriteIPRBox( VSILFILE* fp, GDALDataset* poSrcDS,
|
|
char** papszOptions );
|
|
|
|
CPLErr ReadBlock( int nBand, VSILFILE* fp,
|
|
int nBlockXOff, int nBlockYOff, void * pImage,
|
|
int nBandCount, int *panBandMap );
|
|
|
|
int PreloadBlocks( JP2OpenJPEGRasterBand* poBand,
|
|
int nXOff, int nYOff, int nXSize, int nYSize,
|
|
int nBandCount, int *panBandMap );
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* JP2OpenJPEGRasterBand */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class JP2OpenJPEGRasterBand : public GDALPamRasterBand
|
|
{
|
|
friend class JP2OpenJPEGDataset;
|
|
int bPromoteTo8Bit;
|
|
GDALColorTable* poCT;
|
|
|
|
public:
|
|
|
|
JP2OpenJPEGRasterBand( JP2OpenJPEGDataset * poDS, int nBand,
|
|
GDALDataType eDataType, int nBits,
|
|
int bPromoteTo8Bit,
|
|
int nBlockXSize, int nBlockYSize );
|
|
~JP2OpenJPEGRasterBand();
|
|
|
|
virtual CPLErr IReadBlock( int, int, void * );
|
|
virtual CPLErr IRasterIO( GDALRWFlag eRWFlag,
|
|
int nXOff, int nYOff, int nXSize, int nYSize,
|
|
void * pData, int nBufXSize, int nBufYSize,
|
|
GDALDataType eBufType,
|
|
GSpacing nPixelSpace, GSpacing nLineSpace,
|
|
GDALRasterIOExtraArg* psExtraArg);
|
|
|
|
virtual GDALColorInterp GetColorInterpretation();
|
|
virtual GDALColorTable* GetColorTable() { return poCT; }
|
|
|
|
virtual int GetOverviewCount();
|
|
virtual GDALRasterBand* GetOverview(int iOvrLevel);
|
|
|
|
virtual int HasArbitraryOverviews() { return poCT == NULL; }
|
|
};
|
|
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGRasterBand() */
|
|
/************************************************************************/
|
|
|
|
JP2OpenJPEGRasterBand::JP2OpenJPEGRasterBand( JP2OpenJPEGDataset *poDS, int nBand,
|
|
GDALDataType eDataType, int nBits,
|
|
int bPromoteTo8Bit,
|
|
int nBlockXSize, int nBlockYSize )
|
|
|
|
{
|
|
this->eDataType = eDataType;
|
|
this->nBlockXSize = nBlockXSize;
|
|
this->nBlockYSize = nBlockYSize;
|
|
this->bPromoteTo8Bit = bPromoteTo8Bit;
|
|
poCT = NULL;
|
|
|
|
if( (nBits % 8) != 0 )
|
|
GDALRasterBand::SetMetadataItem("NBITS",
|
|
CPLString().Printf("%d",nBits),
|
|
"IMAGE_STRUCTURE" );
|
|
GDALRasterBand::SetMetadataItem("COMPRESSION", "JPEG2000",
|
|
"IMAGE_STRUCTURE" );
|
|
this->poDS = poDS;
|
|
this->nBand = nBand;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~JP2OpenJPEGRasterBand() */
|
|
/************************************************************************/
|
|
|
|
JP2OpenJPEGRasterBand::~JP2OpenJPEGRasterBand()
|
|
{
|
|
delete poCT;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CLAMP_0_255() */
|
|
/************************************************************************/
|
|
|
|
static CPL_INLINE GByte CLAMP_0_255(int val)
|
|
{
|
|
if (val < 0)
|
|
return 0;
|
|
else if (val > 255)
|
|
return 255;
|
|
else
|
|
return (GByte)val;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
|
|
void * pImage )
|
|
{
|
|
JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *) poDS;
|
|
if ( poGDS->bEnoughMemoryToLoadOtherBands )
|
|
return poGDS->ReadBlock(nBand, poGDS->fp, nBlockXOff, nBlockYOff, pImage,
|
|
poGDS->nBands, NULL);
|
|
else
|
|
return poGDS->ReadBlock(nBand, poGDS->fp, nBlockXOff, nBlockYOff, pImage,
|
|
1, &nBand);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IRasterIO() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGRasterBand::IRasterIO( GDALRWFlag eRWFlag,
|
|
int nXOff, int nYOff, int nXSize, int nYSize,
|
|
void * pData, int nBufXSize, int nBufYSize,
|
|
GDALDataType eBufType,
|
|
GSpacing nPixelSpace, GSpacing nLineSpace,
|
|
GDALRasterIOExtraArg* psExtraArg )
|
|
{
|
|
JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *) poDS;
|
|
|
|
if( eRWFlag != GF_Read )
|
|
return CE_Failure;
|
|
|
|
/* ==================================================================== */
|
|
/* Do we have overviews that would be appropriate to satisfy */
|
|
/* this request? */
|
|
/* ==================================================================== */
|
|
if( (nBufXSize < nXSize || nBufYSize < nYSize)
|
|
&& GetOverviewCount() > 0 && eRWFlag == GF_Read )
|
|
{
|
|
int nOverview;
|
|
GDALRasterIOExtraArg sExtraArg;
|
|
|
|
GDALCopyRasterIOExtraArg(&sExtraArg, psExtraArg);
|
|
|
|
nOverview =
|
|
GDALBandGetBestOverviewLevel2(this, nXOff, nYOff, nXSize, nYSize,
|
|
nBufXSize, nBufYSize, &sExtraArg);
|
|
if (nOverview >= 0)
|
|
{
|
|
GDALRasterBand* poOverviewBand = GetOverview(nOverview);
|
|
if (poOverviewBand == NULL)
|
|
return CE_Failure;
|
|
|
|
return poOverviewBand->RasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize,
|
|
pData, nBufXSize, nBufYSize, eBufType,
|
|
nPixelSpace, nLineSpace, &sExtraArg );
|
|
}
|
|
}
|
|
|
|
poGDS->bEnoughMemoryToLoadOtherBands = poGDS->PreloadBlocks(this, nXOff, nYOff, nXSize, nYSize, 0, NULL);
|
|
|
|
CPLErr eErr = GDALPamRasterBand::IRasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize,
|
|
pData, nBufXSize, nBufYSize, eBufType,
|
|
nPixelSpace, nLineSpace, psExtraArg );
|
|
|
|
poGDS->bEnoughMemoryToLoadOtherBands = TRUE;
|
|
return eErr;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetNumThreads() */
|
|
/************************************************************************/
|
|
|
|
int JP2OpenJPEGDataset::GetNumThreads()
|
|
{
|
|
if( nThreads >= 1 )
|
|
return nThreads;
|
|
|
|
const char* pszThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS");
|
|
if (EQUAL(pszThreads, "ALL_CPUS"))
|
|
nThreads = CPLGetNumCPUs();
|
|
else
|
|
nThreads = atoi(pszThreads);
|
|
if (nThreads > 128)
|
|
nThreads = 128;
|
|
if (nThreads <= 0)
|
|
nThreads = 1;
|
|
return nThreads;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGReadBlockInThread() */
|
|
/************************************************************************/
|
|
|
|
class JobStruct
|
|
{
|
|
public:
|
|
|
|
JP2OpenJPEGDataset* poGDS;
|
|
int nBand;
|
|
std::vector< std::pair<int, int> > oPairs;
|
|
volatile int nCurPair;
|
|
int nBandCount;
|
|
int *panBandMap;
|
|
};
|
|
|
|
static void JP2OpenJPEGReadBlockInThread(void* userdata)
|
|
{
|
|
int nPair;
|
|
JobStruct* poJob = (JobStruct*) userdata;
|
|
JP2OpenJPEGDataset* poGDS = poJob->poGDS;
|
|
int nBand = poJob->nBand;
|
|
int nPairs = (int)poJob->oPairs.size();
|
|
int nBandCount = poJob->nBandCount;
|
|
int* panBandMap = poJob->panBandMap;
|
|
VSILFILE* fp = VSIFOpenL(poGDS->GetDescription(), "rb");
|
|
if( fp == NULL )
|
|
{
|
|
CPLDebug("OPENJPEG", "Cannot open %s", poGDS->GetDescription());
|
|
return;
|
|
}
|
|
|
|
while( (nPair = CPLAtomicInc(&(poJob->nCurPair))) < nPairs )
|
|
{
|
|
int nBlockXOff = poJob->oPairs[nPair].first;
|
|
int nBlockYOff = poJob->oPairs[nPair].second;
|
|
GDALRasterBlock* poBlock = poGDS->GetRasterBand(nBand)->
|
|
GetLockedBlockRef(nBlockXOff,nBlockYOff, TRUE);
|
|
if (poBlock == NULL)
|
|
break;
|
|
|
|
void* pDstBuffer = poBlock->GetDataRef();
|
|
if (!pDstBuffer)
|
|
{
|
|
poBlock->DropLock();
|
|
break;
|
|
}
|
|
|
|
poGDS->ReadBlock(nBand, fp, nBlockXOff, nBlockYOff, pDstBuffer,
|
|
nBandCount, panBandMap);
|
|
|
|
poBlock->DropLock();
|
|
}
|
|
|
|
VSIFCloseL(fp);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* PreloadBlocks() */
|
|
/************************************************************************/
|
|
|
|
int JP2OpenJPEGDataset::PreloadBlocks(JP2OpenJPEGRasterBand* poBand,
|
|
int nXOff, int nYOff, int nXSize, int nYSize,
|
|
int nBandCount, int *panBandMap)
|
|
{
|
|
int bRet = TRUE;
|
|
int nXStart = nXOff / poBand->nBlockXSize;
|
|
int nXEnd = (nXOff + nXSize - 1) / poBand->nBlockXSize;
|
|
int nYStart = nYOff / poBand->nBlockYSize;
|
|
int nYEnd = (nYOff + nYSize - 1) / poBand->nBlockYSize;
|
|
GIntBig nReqMem = (GIntBig)(nXEnd - nXStart + 1) * (nYEnd - nYStart + 1) *
|
|
poBand->nBlockXSize * poBand->nBlockYSize * (GDALGetDataTypeSize(poBand->eDataType) / 8);
|
|
|
|
int nMaxThreads = GetNumThreads();
|
|
if( !bUseSetDecodeArea && nMaxThreads > 1 )
|
|
{
|
|
if( nReqMem > GDALGetCacheMax64() / (nBandCount == 0 ? 1 : nBandCount) )
|
|
return FALSE;
|
|
|
|
int nBlocksToLoad = 0;
|
|
std::vector< std::pair<int,int> > oPairs;
|
|
for(int nBlockXOff = nXStart; nBlockXOff <= nXEnd; ++nBlockXOff)
|
|
{
|
|
for(int nBlockYOff = nYStart; nBlockYOff <= nYEnd; ++nBlockYOff)
|
|
{
|
|
GDALRasterBlock* poBlock = poBand->TryGetLockedBlockRef(nBlockXOff,nBlockYOff);
|
|
if (poBlock != NULL)
|
|
{
|
|
poBlock->DropLock();
|
|
continue;
|
|
}
|
|
oPairs.push_back( std::pair<int,int>(nBlockXOff, nBlockYOff) );
|
|
nBlocksToLoad ++;
|
|
}
|
|
}
|
|
|
|
if( nBlocksToLoad > 1 )
|
|
{
|
|
int nThreads = MIN(nBlocksToLoad, nMaxThreads);
|
|
CPLJoinableThread** pahThreads = (CPLJoinableThread**) CPLMalloc( sizeof(CPLJoinableThread*) * nThreads );
|
|
int i;
|
|
|
|
CPLDebug("OPENJPEG", "%d blocks to load", nBlocksToLoad);
|
|
|
|
JobStruct oJob;
|
|
oJob.poGDS = this;
|
|
oJob.nBand = poBand->GetBand();
|
|
oJob.oPairs = oPairs;
|
|
oJob.nCurPair = -1;
|
|
if( nBandCount > 0 )
|
|
{
|
|
oJob.nBandCount = nBandCount;
|
|
oJob.panBandMap = panBandMap;
|
|
}
|
|
else
|
|
{
|
|
if( nReqMem <= GDALGetCacheMax64() / nBands )
|
|
{
|
|
oJob.nBandCount = nBands;
|
|
oJob.panBandMap = NULL;
|
|
}
|
|
else
|
|
{
|
|
bRet = FALSE;
|
|
oJob.nBandCount = 1;
|
|
oJob.panBandMap = &oJob.nBand;
|
|
}
|
|
}
|
|
|
|
/* Flushes all dirty blocks from cache to disk to avoid them */
|
|
/* to be flushed randomly, and simultaneously, from our worker threads, */
|
|
/* which might cause races in the output driver. */
|
|
/* This is a workaround to a design defect of the block cache */
|
|
GDALRasterBlock::FlushDirtyBlocks();
|
|
|
|
for(i=0;i<nThreads;i++)
|
|
pahThreads[i] = CPLCreateJoinableThread(JP2OpenJPEGReadBlockInThread, &oJob);
|
|
for(i=0;i<nThreads;i++)
|
|
CPLJoinThread( pahThreads[i] );
|
|
CPLFree(pahThreads);
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IRasterIO() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGDataset::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 )
|
|
return CE_Failure;
|
|
|
|
if( nBandCount < 1 )
|
|
return CE_Failure;
|
|
|
|
JP2OpenJPEGRasterBand* poBand = (JP2OpenJPEGRasterBand*) GetRasterBand(panBandMap[0]);
|
|
|
|
/* ==================================================================== */
|
|
/* Do we have overviews that would be appropriate to satisfy */
|
|
/* this request? */
|
|
/* ==================================================================== */
|
|
|
|
if( (nBufXSize < nXSize || nBufYSize < nYSize)
|
|
&& poBand->GetOverviewCount() > 0 && eRWFlag == GF_Read )
|
|
{
|
|
int nOverview;
|
|
GDALRasterIOExtraArg sExtraArg;
|
|
|
|
GDALCopyRasterIOExtraArg(&sExtraArg, psExtraArg);
|
|
|
|
nOverview =
|
|
GDALBandGetBestOverviewLevel2(poBand, nXOff, nYOff, nXSize, nYSize,
|
|
nBufXSize, nBufYSize, &sExtraArg);
|
|
if (nOverview >= 0)
|
|
{
|
|
return papoOverviewDS[nOverview]->RasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize,
|
|
pData, nBufXSize, nBufYSize, eBufType,
|
|
nBandCount, panBandMap,
|
|
nPixelSpace, nLineSpace, nBandSpace,
|
|
&sExtraArg);
|
|
}
|
|
}
|
|
|
|
bEnoughMemoryToLoadOtherBands = PreloadBlocks(poBand, nXOff, nYOff, nXSize, nYSize, nBandCount, panBandMap);
|
|
|
|
CPLErr eErr = GDALPamDataset::IRasterIO( eRWFlag,
|
|
nXOff, nYOff, nXSize, nYSize,
|
|
pData, nBufXSize, nBufYSize,
|
|
eBufType,
|
|
nBandCount, panBandMap,
|
|
nPixelSpace, nLineSpace, nBandSpace,
|
|
psExtraArg );
|
|
|
|
bEnoughMemoryToLoadOtherBands = TRUE;
|
|
return eErr;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGCreateReadStream() */
|
|
/************************************************************************/
|
|
|
|
static opj_stream_t* JP2OpenJPEGCreateReadStream(JP2OpenJPEGFile* psJP2OpenJPEGFile,
|
|
vsi_l_offset nSize)
|
|
{
|
|
opj_stream_t *pStream = opj_stream_create(1024, TRUE); // Default 1MB is way too big for some datasets
|
|
|
|
VSIFSeekL(psJP2OpenJPEGFile->fp, psJP2OpenJPEGFile->nBaseOffset, SEEK_SET);
|
|
opj_stream_set_user_data_length(pStream, nSize);
|
|
|
|
opj_stream_set_read_function(pStream, JP2OpenJPEGDataset_Read);
|
|
opj_stream_set_seek_function(pStream, JP2OpenJPEGDataset_Seek);
|
|
opj_stream_set_skip_function(pStream, JP2OpenJPEGDataset_Skip);
|
|
#if defined(OPENJPEG_VERSION) && OPENJPEG_VERSION >= 20100
|
|
opj_stream_set_user_data(pStream, psJP2OpenJPEGFile, NULL);
|
|
#else
|
|
opj_stream_set_user_data(pStream, psJP2OpenJPEGFile);
|
|
#endif
|
|
|
|
return pStream;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGDataset::ReadBlock( int nBand, VSILFILE* fp,
|
|
int nBlockXOff, int nBlockYOff, void * pImage,
|
|
int nBandCount, int* panBandMap )
|
|
{
|
|
CPLErr eErr = CE_None;
|
|
opj_codec_t* pCodec;
|
|
opj_stream_t * pStream;
|
|
opj_image_t * psImage;
|
|
|
|
JP2OpenJPEGRasterBand* poBand = (JP2OpenJPEGRasterBand*) GetRasterBand(nBand);
|
|
int nBlockXSize = poBand->nBlockXSize;
|
|
int nBlockYSize = poBand->nBlockYSize;
|
|
GDALDataType eDataType = poBand->eDataType;
|
|
|
|
int nDataTypeSize = (GDALGetDataTypeSize(eDataType) / 8);
|
|
|
|
int nTileNumber = nBlockXOff + nBlockYOff * poBand->nBlocksPerRow;
|
|
int nWidthToRead = MIN(nBlockXSize, nRasterXSize - nBlockXOff * nBlockXSize);
|
|
int nHeightToRead = MIN(nBlockYSize, nRasterYSize - nBlockYOff * nBlockYSize);
|
|
|
|
pCodec = opj_create_decompress(OPJ_CODEC_J2K);
|
|
|
|
opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback,NULL);
|
|
opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback, NULL);
|
|
opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback,NULL);
|
|
|
|
opj_dparameters_t parameters;
|
|
opj_set_default_decoder_parameters(¶meters);
|
|
|
|
if (! opj_setup_decoder(pCodec,¶meters))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "opj_setup_decoder() failed");
|
|
return CE_Failure;
|
|
}
|
|
|
|
JP2OpenJPEGFile sJP2OpenJPEGFile;
|
|
sJP2OpenJPEGFile.fp = fp;
|
|
sJP2OpenJPEGFile.nBaseOffset = nCodeStreamStart;
|
|
pStream = JP2OpenJPEGCreateReadStream(&sJP2OpenJPEGFile, nCodeStreamLength);
|
|
|
|
if(!opj_read_header(pStream,pCodec,&psImage))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "opj_read_header() failed");
|
|
return CE_Failure;
|
|
}
|
|
|
|
if (!opj_set_decoded_resolution_factor( pCodec, iLevel ))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "opj_set_decoded_resolution_factor() failed");
|
|
eErr = CE_Failure;
|
|
goto end;
|
|
}
|
|
|
|
if (bUseSetDecodeArea)
|
|
{
|
|
if (!opj_set_decode_area(pCodec,psImage,
|
|
nBlockXOff*nBlockXSize,
|
|
nBlockYOff*nBlockYSize,
|
|
nBlockXOff*nBlockXSize+nWidthToRead,
|
|
nBlockYOff*nBlockYSize+nHeightToRead))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "opj_set_decode_area() failed");
|
|
eErr = CE_Failure;
|
|
goto end;
|
|
}
|
|
if (!opj_decode(pCodec,pStream, psImage))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "opj_decode() failed");
|
|
eErr = CE_Failure;
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!opj_get_decoded_tile( pCodec, pStream, psImage, nTileNumber ))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "opj_get_decoded_tile() failed");
|
|
eErr = CE_Failure;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
for(int xBand = 0; xBand < nBandCount; xBand ++)
|
|
{
|
|
void* pDstBuffer;
|
|
GDALRasterBlock *poBlock = NULL;
|
|
int iBand = (panBandMap) ? panBandMap[xBand] : xBand + 1;
|
|
int bPromoteTo8Bit = ((JP2OpenJPEGRasterBand*)GetRasterBand(iBand))->bPromoteTo8Bit;
|
|
|
|
if (iBand == nBand)
|
|
pDstBuffer = pImage;
|
|
else
|
|
{
|
|
poBlock = ((JP2OpenJPEGRasterBand*)GetRasterBand(iBand))->
|
|
TryGetLockedBlockRef(nBlockXOff,nBlockYOff);
|
|
if (poBlock != NULL)
|
|
{
|
|
poBlock->DropLock();
|
|
continue;
|
|
}
|
|
|
|
poBlock = GetRasterBand(iBand)->
|
|
GetLockedBlockRef(nBlockXOff,nBlockYOff, TRUE);
|
|
if (poBlock == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pDstBuffer = poBlock->GetDataRef();
|
|
if (!pDstBuffer)
|
|
{
|
|
poBlock->DropLock();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (bIs420)
|
|
{
|
|
CPLAssert((int)psImage->comps[0].w >= nWidthToRead);
|
|
CPLAssert((int)psImage->comps[0].h >= nHeightToRead);
|
|
CPLAssert(psImage->comps[1].w == (psImage->comps[0].w + 1) / 2);
|
|
CPLAssert(psImage->comps[1].h == (psImage->comps[0].h + 1) / 2);
|
|
CPLAssert(psImage->comps[2].w == (psImage->comps[0].w + 1) / 2);
|
|
CPLAssert(psImage->comps[2].h == (psImage->comps[0].h + 1) / 2);
|
|
if( nBands == 4 )
|
|
{
|
|
CPLAssert((int)psImage->comps[3].w >= nWidthToRead);
|
|
CPLAssert((int)psImage->comps[3].h >= nHeightToRead);
|
|
}
|
|
|
|
OPJ_INT32* pSrcY = psImage->comps[0].data;
|
|
OPJ_INT32* pSrcCb = psImage->comps[1].data;
|
|
OPJ_INT32* pSrcCr = psImage->comps[2].data;
|
|
OPJ_INT32* pSrcA = (nBands == 4) ? psImage->comps[3].data : NULL;
|
|
GByte* pDst = (GByte*)pDstBuffer;
|
|
for(int j=0;j<nHeightToRead;j++)
|
|
{
|
|
for(int i=0;i<nWidthToRead;i++)
|
|
{
|
|
int Y = pSrcY[j * psImage->comps[0].w + i];
|
|
int Cb = pSrcCb[(j/2) * psImage->comps[1].w + (i/2)];
|
|
int Cr = pSrcCr[(j/2) * psImage->comps[2].w + (i/2)];
|
|
if (iBand == 1)
|
|
pDst[j * nBlockXSize + i] = CLAMP_0_255((int)(Y + 1.402 * (Cr - 128)));
|
|
else if (iBand == 2)
|
|
pDst[j * nBlockXSize + i] = CLAMP_0_255((int)(Y - 0.34414 * (Cb - 128) - 0.71414 * (Cr - 128)));
|
|
else if (iBand == 3)
|
|
pDst[j * nBlockXSize + i] = CLAMP_0_255((int)(Y + 1.772 * (Cb - 128)));
|
|
else if (iBand == 4)
|
|
pDst[j * nBlockXSize + i] = pSrcA[j * psImage->comps[0].w + i];
|
|
}
|
|
}
|
|
|
|
if( bPromoteTo8Bit )
|
|
{
|
|
for(int j=0;j<nHeightToRead;j++)
|
|
{
|
|
for(int i=0;i<nWidthToRead;i++)
|
|
{
|
|
pDst[j * nBlockXSize + i] *= 255;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLAssert((int)psImage->comps[iBand-1].w >= nWidthToRead);
|
|
CPLAssert((int)psImage->comps[iBand-1].h >= nHeightToRead);
|
|
|
|
if( bPromoteTo8Bit )
|
|
{
|
|
for(int j=0;j<nHeightToRead;j++)
|
|
{
|
|
for(int i=0;i<nWidthToRead;i++)
|
|
{
|
|
psImage->comps[iBand-1].data[j * psImage->comps[iBand-1].w + i] *= 255;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((int)psImage->comps[iBand-1].w == nBlockXSize &&
|
|
(int)psImage->comps[iBand-1].h == nBlockYSize)
|
|
{
|
|
GDALCopyWords(psImage->comps[iBand-1].data, GDT_Int32, 4,
|
|
pDstBuffer, eDataType, nDataTypeSize, nBlockXSize * nBlockYSize);
|
|
}
|
|
else
|
|
{
|
|
for(int j=0;j<nHeightToRead;j++)
|
|
{
|
|
GDALCopyWords(psImage->comps[iBand-1].data + j * psImage->comps[iBand-1].w, GDT_Int32, 4,
|
|
(GByte*)pDstBuffer + j * nBlockXSize * nDataTypeSize, eDataType, nDataTypeSize,
|
|
nWidthToRead);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (poBlock != NULL)
|
|
poBlock->DropLock();
|
|
}
|
|
|
|
end:
|
|
opj_end_decompress(pCodec,pStream);
|
|
opj_stream_destroy(pStream);
|
|
opj_destroy_codec(pCodec);
|
|
opj_image_destroy(psImage);
|
|
|
|
return eErr;
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* GetOverviewCount() */
|
|
/************************************************************************/
|
|
|
|
int JP2OpenJPEGRasterBand::GetOverviewCount()
|
|
{
|
|
JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *) poDS;
|
|
return poGDS->nOverviewCount;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetOverview() */
|
|
/************************************************************************/
|
|
|
|
GDALRasterBand* JP2OpenJPEGRasterBand::GetOverview(int iOvrLevel)
|
|
{
|
|
JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *) poDS;
|
|
if (iOvrLevel < 0 || iOvrLevel >= poGDS->nOverviewCount)
|
|
return NULL;
|
|
|
|
return poGDS->papoOverviewDS[iOvrLevel]->GetRasterBand(nBand);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetColorInterpretation() */
|
|
/************************************************************************/
|
|
|
|
GDALColorInterp JP2OpenJPEGRasterBand::GetColorInterpretation()
|
|
{
|
|
JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *) poDS;
|
|
|
|
if( poCT )
|
|
return GCI_PaletteIndex;
|
|
|
|
if( nBand == poGDS->nAlphaIndex + 1 )
|
|
return GCI_AlphaBand;
|
|
|
|
if (poGDS->nBands <= 2 && poGDS->eColorSpace == OPJ_CLRSPC_GRAY)
|
|
return GCI_GrayIndex;
|
|
else if (poGDS->eColorSpace == OPJ_CLRSPC_SRGB ||
|
|
poGDS->eColorSpace == OPJ_CLRSPC_SYCC)
|
|
{
|
|
if( nBand == poGDS->nRedIndex + 1 )
|
|
return GCI_RedBand;
|
|
if( nBand == poGDS->nGreenIndex + 1 )
|
|
return GCI_GreenBand;
|
|
if( nBand == poGDS->nBlueIndex + 1 )
|
|
return GCI_BlueBand;
|
|
}
|
|
|
|
return GCI_Undefined;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* JP2OpenJPEGDataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGDataset() */
|
|
/************************************************************************/
|
|
|
|
JP2OpenJPEGDataset::JP2OpenJPEGDataset()
|
|
{
|
|
fp = NULL;
|
|
nCodeStreamStart = 0;
|
|
nCodeStreamLength = 0;
|
|
nBands = 0;
|
|
eColorSpace = OPJ_CLRSPC_UNKNOWN;
|
|
nRedIndex = 0;
|
|
nGreenIndex = 1;
|
|
nBlueIndex = 2;
|
|
nAlphaIndex = -1;
|
|
bIs420 = FALSE;
|
|
iLevel = 0;
|
|
nOverviewCount = 0;
|
|
papoOverviewDS = NULL;
|
|
bUseSetDecodeArea = FALSE;
|
|
nThreads = -1;
|
|
bEnoughMemoryToLoadOtherBands = TRUE;
|
|
bRewrite = FALSE;
|
|
bHasGeoreferencingAtOpening = FALSE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~JP2OpenJPEGDataset() */
|
|
/************************************************************************/
|
|
|
|
JP2OpenJPEGDataset::~JP2OpenJPEGDataset()
|
|
|
|
{
|
|
FlushCache();
|
|
|
|
if( iLevel == 0 && fp != NULL )
|
|
{
|
|
if( bRewrite )
|
|
{
|
|
GDALJP2Box oBox( fp );
|
|
vsi_l_offset nOffsetJP2C = 0, nLengthJP2C = 0,
|
|
nOffsetXML = 0, nOffsetASOC = 0, nOffsetUUID = 0,
|
|
nOffsetIHDR = 0, nLengthIHDR = 0;
|
|
int bMSIBox = FALSE, bGMLData = FALSE;
|
|
int bUnsupportedConfiguration = FALSE;
|
|
if( oBox.ReadFirst() )
|
|
{
|
|
while( strlen(oBox.GetType()) > 0 )
|
|
{
|
|
if( EQUAL(oBox.GetType(),"jp2c") )
|
|
{
|
|
if( nOffsetJP2C == 0 )
|
|
{
|
|
nOffsetJP2C = VSIFTellL(fp);
|
|
nLengthJP2C = oBox.GetDataLength();
|
|
}
|
|
else
|
|
bUnsupportedConfiguration = TRUE;
|
|
}
|
|
else if( EQUAL(oBox.GetType(),"jp2h") )
|
|
{
|
|
GDALJP2Box oSubBox( fp );
|
|
if( oSubBox.ReadFirstChild( &oBox ) &&
|
|
EQUAL(oSubBox.GetType(),"ihdr") )
|
|
{
|
|
nOffsetIHDR = VSIFTellL(fp);
|
|
nLengthIHDR = oSubBox.GetDataLength();
|
|
}
|
|
}
|
|
else if( EQUAL(oBox.GetType(),"xml ") )
|
|
{
|
|
if( nOffsetXML == 0 )
|
|
nOffsetXML = VSIFTellL(fp);
|
|
}
|
|
else if( EQUAL(oBox.GetType(),"asoc") )
|
|
{
|
|
if( nOffsetASOC == 0 )
|
|
nOffsetASOC = VSIFTellL(fp);
|
|
|
|
GDALJP2Box oSubBox( fp );
|
|
if( oSubBox.ReadFirstChild( &oBox ) &&
|
|
EQUAL(oSubBox.GetType(),"lbl ") )
|
|
{
|
|
char *pszLabel = (char *) oSubBox.ReadBoxData();
|
|
if( pszLabel != NULL && EQUAL(pszLabel,"gml.data") )
|
|
{
|
|
bGMLData = TRUE;
|
|
}
|
|
else
|
|
bUnsupportedConfiguration = TRUE;
|
|
CPLFree( pszLabel );
|
|
}
|
|
else
|
|
bUnsupportedConfiguration = TRUE;
|
|
}
|
|
else if( EQUAL(oBox.GetType(),"uuid") )
|
|
{
|
|
if( nOffsetUUID == 0 )
|
|
nOffsetUUID = VSIFTellL(fp);
|
|
if( GDALJP2Metadata::IsUUID_MSI(oBox.GetUUID()) )
|
|
bMSIBox = TRUE;
|
|
else if( !GDALJP2Metadata::IsUUID_XMP(oBox.GetUUID()) )
|
|
bUnsupportedConfiguration = TRUE;
|
|
}
|
|
else if( !EQUAL(oBox.GetType(),"jP ") &&
|
|
!EQUAL(oBox.GetType(),"ftyp") &&
|
|
!EQUAL(oBox.GetType(),"rreq") &&
|
|
!EQUAL(oBox.GetType(),"jp2h") &&
|
|
!EQUAL(oBox.GetType(),"jp2i") )
|
|
{
|
|
bUnsupportedConfiguration = TRUE;
|
|
}
|
|
|
|
if (bUnsupportedConfiguration || !oBox.ReadNext())
|
|
break;
|
|
}
|
|
}
|
|
|
|
const char* pszGMLJP2;
|
|
int bGeoreferencingCompatOfGMLJP2 =
|
|
((pszProjection != NULL && pszProjection[0] != '\0' ) &&
|
|
bGeoTransformValid && nGCPCount == 0);
|
|
if( bGeoreferencingCompatOfGMLJP2 &&
|
|
((bHasGeoreferencingAtOpening && bGMLData) ||
|
|
(!bHasGeoreferencingAtOpening)) )
|
|
pszGMLJP2 = "GMLJP2=YES";
|
|
else
|
|
pszGMLJP2 = "GMLJP2=NO";
|
|
|
|
const char* pszGeoJP2;
|
|
int bGeoreferencingCompatOfGeoJP2 =
|
|
((pszProjection != NULL && pszProjection[0] != '\0' ) ||
|
|
nGCPCount != 0 || bGeoTransformValid);
|
|
if( bGeoreferencingCompatOfGeoJP2 &&
|
|
((bHasGeoreferencingAtOpening && bMSIBox) ||
|
|
(!bHasGeoreferencingAtOpening) || nGCPCount > 0) )
|
|
pszGeoJP2 = "GeoJP2=YES";
|
|
else
|
|
pszGeoJP2 = "GeoJP2=NO";
|
|
|
|
/* Test that the length of the JP2C box is not 0 */
|
|
int bJP2CBoxOKForRewriteInPlace = TRUE;
|
|
if( nOffsetJP2C > 16 && !bUnsupportedConfiguration )
|
|
{
|
|
VSIFSeekL(fp, nOffsetJP2C - 8, SEEK_SET);
|
|
GByte abyBuffer[8];
|
|
VSIFReadL(abyBuffer, 1, 8, fp);
|
|
if( EQUALN((const char*)abyBuffer + 4, "jp2c", 4) &&
|
|
abyBuffer[0] == 0 && abyBuffer[1] == 0 &&
|
|
abyBuffer[2] == 0 && abyBuffer[3] == 0 )
|
|
{
|
|
if( (vsi_l_offset)(GUInt32)(nLengthJP2C + 8) == (nLengthJP2C + 8) )
|
|
{
|
|
CPLDebug("OPENJPEG", "Patching length of JP2C box with real length");
|
|
VSIFSeekL(fp, nOffsetJP2C - 8, SEEK_SET);
|
|
GUInt32 nLength = (GUInt32)nLengthJP2C + 8;
|
|
CPL_MSBPTR32(&nLength);
|
|
VSIFWriteL(&nLength, 1, 4, fp);
|
|
}
|
|
else
|
|
bJP2CBoxOKForRewriteInPlace = FALSE;
|
|
}
|
|
}
|
|
|
|
if( nOffsetJP2C == 0 || bUnsupportedConfiguration )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Cannot rewrite file due to unsupported JP2 box configuration");
|
|
VSIFCloseL( fp );
|
|
}
|
|
else if( bJP2CBoxOKForRewriteInPlace &&
|
|
(nOffsetXML == 0 || nOffsetXML > nOffsetJP2C) &&
|
|
(nOffsetASOC == 0 || nOffsetASOC > nOffsetJP2C) &&
|
|
(nOffsetUUID == 0 || nOffsetUUID > nOffsetJP2C) )
|
|
{
|
|
CPLDebug("OPENJPEG", "Rewriting boxes after codestream");
|
|
|
|
/* Update IPR flag */
|
|
if( nLengthIHDR == 14 )
|
|
{
|
|
VSIFSeekL( fp, nOffsetIHDR + nLengthIHDR - 1, SEEK_SET );
|
|
GByte bIPR = GetMetadata("xml:IPR") != NULL;
|
|
VSIFWriteL( &bIPR, 1, 1, fp );
|
|
}
|
|
|
|
VSIFSeekL( fp, nOffsetJP2C + nLengthJP2C, SEEK_SET );
|
|
|
|
GDALJP2Metadata oJP2MD;
|
|
if( GetGCPCount() > 0 )
|
|
{
|
|
oJP2MD.SetGCPs( GetGCPCount(),
|
|
GetGCPs() );
|
|
oJP2MD.SetProjection( GetGCPProjection() );
|
|
}
|
|
else
|
|
{
|
|
const char* pszWKT = GetProjectionRef();
|
|
if( pszWKT != NULL && pszWKT[0] != '\0' )
|
|
{
|
|
oJP2MD.SetProjection( pszWKT );
|
|
}
|
|
if( bGeoTransformValid )
|
|
{
|
|
oJP2MD.SetGeoTransform( adfGeoTransform );
|
|
}
|
|
}
|
|
|
|
const char* pszAreaOrPoint = GetMetadataItem(GDALMD_AREA_OR_POINT);
|
|
oJP2MD.bPixelIsPoint = pszAreaOrPoint != NULL && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT);
|
|
|
|
WriteIPRBox(fp, this, NULL);
|
|
|
|
if( bGeoreferencingCompatOfGMLJP2 && EQUAL(pszGMLJP2, "GMLJP2=YES") )
|
|
{
|
|
GDALJP2Box* poBox = oJP2MD.CreateGMLJP2(nRasterXSize,nRasterYSize);
|
|
WriteBox(fp, poBox);
|
|
delete poBox;
|
|
}
|
|
|
|
WriteXMLBoxes(fp, this, NULL);
|
|
WriteGDALMetadataBox(fp, this, NULL);
|
|
|
|
if( bGeoreferencingCompatOfGeoJP2 && EQUAL(pszGeoJP2, "GeoJP2=YES") )
|
|
{
|
|
GDALJP2Box* poBox = oJP2MD.CreateJP2GeoTIFF();
|
|
WriteBox(fp, poBox);
|
|
delete poBox;
|
|
}
|
|
|
|
WriteXMPBox(fp, this, NULL);
|
|
|
|
VSIFTruncateL( fp, VSIFTellL(fp) );
|
|
|
|
VSIFCloseL( fp );
|
|
}
|
|
else
|
|
{
|
|
VSIFCloseL( fp );
|
|
|
|
CPLDebug("OPENJPEG", "Rewriting whole file");
|
|
|
|
const char* apszOptions[] = {
|
|
"USE_SRC_CODESTREAM=YES", "CODEC=JP2", "WRITE_METADATA=YES",
|
|
NULL, NULL, NULL };
|
|
apszOptions[3] = pszGMLJP2;
|
|
apszOptions[4] = pszGeoJP2;
|
|
CPLString osTmpFilename(CPLSPrintf("%s.tmp", GetDescription()));
|
|
GDALDataset* poOutDS = CreateCopy( osTmpFilename, this, FALSE,
|
|
(char**)apszOptions, GDALDummyProgress, NULL );
|
|
if( poOutDS )
|
|
{
|
|
GDALClose(poOutDS);
|
|
VSIRename(osTmpFilename, GetDescription());
|
|
}
|
|
else
|
|
VSIUnlink(osTmpFilename);
|
|
VSIUnlink(CPLSPrintf("%s.tmp.aux.xml", GetDescription()));
|
|
}
|
|
}
|
|
else
|
|
VSIFCloseL( fp );
|
|
}
|
|
|
|
CloseDependentDatasets();
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CloseDependentDatasets() */
|
|
/************************************************************************/
|
|
|
|
int JP2OpenJPEGDataset::CloseDependentDatasets()
|
|
{
|
|
int bRet = GDALJP2AbstractDataset::CloseDependentDatasets();
|
|
if ( papoOverviewDS )
|
|
{
|
|
for( int i = 0; i < nOverviewCount; i++ )
|
|
delete papoOverviewDS[i];
|
|
CPLFree( papoOverviewDS );
|
|
papoOverviewDS = NULL;
|
|
bRet = TRUE;
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetProjection() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGDataset::SetProjection( const char * pszProjectionIn )
|
|
{
|
|
if( eAccess == GA_Update )
|
|
{
|
|
bRewrite = TRUE;
|
|
CPLFree(pszProjection);
|
|
pszProjection = (pszProjectionIn) ? CPLStrdup(pszProjectionIn) : CPLStrdup("");
|
|
return CE_None;
|
|
}
|
|
else
|
|
return GDALJP2AbstractDataset::SetProjection(pszProjectionIn);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetGeoTransform() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGDataset::SetGeoTransform( double *padfGeoTransform )
|
|
{
|
|
if( eAccess == GA_Update )
|
|
{
|
|
bRewrite = TRUE;
|
|
memcpy(adfGeoTransform, padfGeoTransform, 6* sizeof(double));
|
|
bGeoTransformValid = !(
|
|
adfGeoTransform[0] == 0.0 && adfGeoTransform[1] == 1.0 &&
|
|
adfGeoTransform[2] == 0.0 && adfGeoTransform[3] == 0.0 &&
|
|
adfGeoTransform[4] == 0.0 && adfGeoTransform[5] == 1.0);
|
|
return CE_None;
|
|
}
|
|
else
|
|
return GDALJP2AbstractDataset::SetGeoTransform(padfGeoTransform);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetGCPs() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGDataset::SetGCPs( int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
|
|
const char *pszGCPProjectionIn )
|
|
{
|
|
if( eAccess == GA_Update )
|
|
{
|
|
bRewrite = TRUE;
|
|
CPLFree( pszProjection );
|
|
if( nGCPCount > 0 )
|
|
{
|
|
GDALDeinitGCPs( nGCPCount, pasGCPList );
|
|
CPLFree( pasGCPList );
|
|
}
|
|
|
|
pszProjection = (pszGCPProjectionIn) ? CPLStrdup(pszGCPProjectionIn) : CPLStrdup("");
|
|
nGCPCount = nGCPCountIn;
|
|
pasGCPList = GDALDuplicateGCPs( nGCPCount, pasGCPListIn );
|
|
|
|
return CE_None;
|
|
}
|
|
else
|
|
return GDALJP2AbstractDataset::SetGCPs(nGCPCountIn, pasGCPListIn,
|
|
pszGCPProjectionIn);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetMetadata() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGDataset::SetMetadata( char ** papszMetadata,
|
|
const char * pszDomain )
|
|
{
|
|
if( eAccess == GA_Update )
|
|
{
|
|
bRewrite = TRUE;
|
|
return GDALDataset::SetMetadata(papszMetadata, pszDomain);
|
|
}
|
|
return GDALJP2AbstractDataset::SetMetadata(papszMetadata, pszDomain);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* SetMetadata() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JP2OpenJPEGDataset::SetMetadataItem( const char * pszName,
|
|
const char * pszValue,
|
|
const char * pszDomain )
|
|
{
|
|
if( eAccess == GA_Update )
|
|
{
|
|
bRewrite = TRUE;
|
|
return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
|
|
}
|
|
return GDALJP2AbstractDataset::SetMetadataItem(pszName, pszValue, pszDomain);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Identify() */
|
|
/************************************************************************/
|
|
|
|
static const unsigned char jpc_header[] = {0xff,0x4f};
|
|
static const unsigned char jp2_box_jp[] = {0x6a,0x50,0x20,0x20}; /* 'jP ' */
|
|
|
|
int JP2OpenJPEGDataset::Identify( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
if( poOpenInfo->nHeaderBytes >= 16
|
|
&& (memcmp( poOpenInfo->pabyHeader, jpc_header,
|
|
sizeof(jpc_header) ) == 0
|
|
|| memcmp( poOpenInfo->pabyHeader + 4, jp2_box_jp,
|
|
sizeof(jp2_box_jp) ) == 0
|
|
) )
|
|
return TRUE;
|
|
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JP2OpenJPEGFindCodeStream() */
|
|
/************************************************************************/
|
|
|
|
static vsi_l_offset JP2OpenJPEGFindCodeStream( VSILFILE* fp,
|
|
vsi_l_offset* pnLength )
|
|
{
|
|
vsi_l_offset nCodeStreamStart = 0;
|
|
vsi_l_offset nCodeStreamLength = 0;
|
|
|
|
VSIFSeekL(fp, 0, SEEK_SET);
|
|
GByte abyHeader[16];
|
|
VSIFReadL(abyHeader, 1, 16, fp);
|
|
|
|
if (memcmp( abyHeader, jpc_header, sizeof(jpc_header) ) == 0)
|
|
{
|
|
VSIFSeekL(fp, 0, SEEK_END);
|
|
nCodeStreamLength = VSIFTellL(fp);
|
|
}
|
|
else if (memcmp( abyHeader + 4, jp2_box_jp, sizeof(jp2_box_jp) ) == 0)
|
|
{
|
|
/* Find offset of first jp2c box */
|
|
GDALJP2Box oBox( fp );
|
|
if( oBox.ReadFirst() )
|
|
{
|
|
while( strlen(oBox.GetType()) > 0 )
|
|
{
|
|
if( EQUAL(oBox.GetType(),"jp2c") )
|
|
{
|
|
nCodeStreamStart = VSIFTellL(fp);
|
|
nCodeStreamLength = oBox.GetDataLength();
|
|
break;
|
|
}
|
|
|
|
if (!oBox.ReadNext())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
*pnLength = nCodeStreamLength;
|
|
return nCodeStreamStart;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Open() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *JP2OpenJPEGDataset::Open( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
if (!Identify(poOpenInfo) || poOpenInfo->fpL == NULL)
|
|
return NULL;
|
|
|
|
/* Detect which codec to use : J2K or JP2 ? */
|
|
vsi_l_offset nCodeStreamLength = 0;
|
|
vsi_l_offset nCodeStreamStart = JP2OpenJPEGFindCodeStream(poOpenInfo->fpL,
|
|
&nCodeStreamLength);
|
|
|
|
if( nCodeStreamStart == 0 && nCodeStreamLength == 0 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "No code-stream in JP2 file");
|
|
return NULL;
|
|
}
|
|
|
|
OPJ_CODEC_FORMAT eCodecFormat = (nCodeStreamStart == 0) ? OPJ_CODEC_J2K : OPJ_CODEC_JP2;
|
|
|
|
|
|
opj_codec_t* pCodec;
|
|
|
|
pCodec = opj_create_decompress(OPJ_CODEC_J2K);
|
|
|
|
opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback,NULL);
|
|
opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback, NULL);
|
|
opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback,NULL);
|
|
|
|
opj_dparameters_t parameters;
|
|
opj_set_default_decoder_parameters(¶meters);
|
|
|
|
if (! opj_setup_decoder(pCodec,¶meters))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
JP2OpenJPEGFile sJP2OpenJPEGFile;
|
|
sJP2OpenJPEGFile.fp = poOpenInfo->fpL;
|
|
sJP2OpenJPEGFile.nBaseOffset = nCodeStreamStart;
|
|
opj_stream_t * pStream = JP2OpenJPEGCreateReadStream(&sJP2OpenJPEGFile,
|
|
nCodeStreamLength);
|
|
|
|
opj_image_t * psImage = NULL;
|
|
|
|
if(!opj_read_header(pStream,pCodec,&psImage))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "opj_read_header() failed");
|
|
opj_destroy_codec(pCodec);
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
return NULL;
|
|
}
|
|
|
|
opj_codestream_info_v2_t* pCodeStreamInfo = opj_get_cstr_info(pCodec);
|
|
OPJ_UINT32 nTileW,nTileH;
|
|
nTileW = pCodeStreamInfo->tdx;
|
|
nTileH = pCodeStreamInfo->tdy;
|
|
#ifdef DEBUG
|
|
OPJ_UINT32 nX0,nY0;
|
|
OPJ_UINT32 nTilesX,nTilesY;
|
|
nX0 = pCodeStreamInfo->tx0;
|
|
nY0 = pCodeStreamInfo->ty0;
|
|
nTilesX = pCodeStreamInfo->tw;
|
|
nTilesY = pCodeStreamInfo->th;
|
|
int mct = pCodeStreamInfo->m_default_tile_info.mct;
|
|
#endif
|
|
int numResolutions = pCodeStreamInfo->m_default_tile_info.tccp_info[0].numresolutions;
|
|
opj_destroy_cstr_info(&pCodeStreamInfo);
|
|
|
|
if (psImage == NULL)
|
|
{
|
|
opj_destroy_codec(pCodec);
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
int i;
|
|
CPLDebug("OPENJPEG", "nX0 = %u", nX0);
|
|
CPLDebug("OPENJPEG", "nY0 = %u", nY0);
|
|
CPLDebug("OPENJPEG", "nTileW = %u", nTileW);
|
|
CPLDebug("OPENJPEG", "nTileH = %u", nTileH);
|
|
CPLDebug("OPENJPEG", "nTilesX = %u", nTilesX);
|
|
CPLDebug("OPENJPEG", "nTilesY = %u", nTilesY);
|
|
CPLDebug("OPENJPEG", "mct = %d", mct);
|
|
CPLDebug("OPENJPEG", "psImage->x0 = %u", psImage->x0);
|
|
CPLDebug("OPENJPEG", "psImage->y0 = %u", psImage->y0);
|
|
CPLDebug("OPENJPEG", "psImage->x1 = %u", psImage->x1);
|
|
CPLDebug("OPENJPEG", "psImage->y1 = %u", psImage->y1);
|
|
CPLDebug("OPENJPEG", "psImage->numcomps = %d", psImage->numcomps);
|
|
//CPLDebug("OPENJPEG", "psImage->color_space = %d", psImage->color_space);
|
|
CPLDebug("OPENJPEG", "numResolutions = %d", numResolutions);
|
|
for(i=0;i<(int)psImage->numcomps;i++)
|
|
{
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].dx = %u", i, psImage->comps[i].dx);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].dy = %u", i, psImage->comps[i].dy);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].x0 = %u", i, psImage->comps[i].x0);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].y0 = %u", i, psImage->comps[i].y0);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].w = %u", i, psImage->comps[i].w);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].h = %u", i, psImage->comps[i].h);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].resno_decoded = %d", i, psImage->comps[i].resno_decoded);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].factor = %d", i, psImage->comps[i].factor);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].prec = %d", i, psImage->comps[i].prec);
|
|
CPLDebug("OPENJPEG", "psImage->comps[%d].sgnd = %d", i, psImage->comps[i].sgnd);
|
|
}
|
|
#endif
|
|
|
|
if (psImage->x1 <= psImage->x0 ||
|
|
psImage->y1 <= psImage->y0 ||
|
|
psImage->numcomps == 0 ||
|
|
(psImage->comps[0].w >> 31) != 0 ||
|
|
(psImage->comps[0].h >> 31) != 0 ||
|
|
(nTileW >> 31) != 0 ||
|
|
(nTileH >> 31) != 0 ||
|
|
psImage->comps[0].w != psImage->x1 - psImage->x0 ||
|
|
psImage->comps[0].h != psImage->y1 - psImage->y0)
|
|
{
|
|
CPLDebug("OPENJPEG", "Unable to handle that image (1)");
|
|
opj_destroy_codec(pCodec);
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
return NULL;
|
|
}
|
|
|
|
GDALDataType eDataType = GDT_Byte;
|
|
if (psImage->comps[0].prec > 16)
|
|
{
|
|
if (psImage->comps[0].sgnd)
|
|
eDataType = GDT_Int32;
|
|
else
|
|
eDataType = GDT_UInt32;
|
|
}
|
|
else if (psImage->comps[0].prec > 8)
|
|
{
|
|
if (psImage->comps[0].sgnd)
|
|
eDataType = GDT_Int16;
|
|
else
|
|
eDataType = GDT_UInt16;
|
|
}
|
|
|
|
int bIs420 = (psImage->color_space != OPJ_CLRSPC_SRGB &&
|
|
eDataType == GDT_Byte &&
|
|
(psImage->numcomps == 3 || psImage->numcomps == 4) &&
|
|
psImage->comps[1].w == psImage->comps[0].w / 2 &&
|
|
psImage->comps[1].h == psImage->comps[0].h / 2 &&
|
|
psImage->comps[2].w == psImage->comps[0].w / 2 &&
|
|
psImage->comps[2].h == psImage->comps[0].h / 2) &&
|
|
(psImage->numcomps == 3 ||
|
|
(psImage->numcomps == 4 &&
|
|
psImage->comps[3].w == psImage->comps[0].w &&
|
|
psImage->comps[3].h == psImage->comps[0].h));
|
|
|
|
if (bIs420)
|
|
{
|
|
CPLDebug("OPENJPEG", "420 format");
|
|
}
|
|
else
|
|
{
|
|
int iBand;
|
|
for(iBand = 2; iBand <= (int)psImage->numcomps; iBand ++)
|
|
{
|
|
if( psImage->comps[iBand-1].w != psImage->comps[0].w ||
|
|
psImage->comps[iBand-1].h != psImage->comps[0].h )
|
|
{
|
|
CPLDebug("OPENJPEG", "Unable to handle that image (2)");
|
|
opj_destroy_codec(pCodec);
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create a corresponding GDALDataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
JP2OpenJPEGDataset *poDS;
|
|
int iBand;
|
|
|
|
poDS = new JP2OpenJPEGDataset();
|
|
if( eCodecFormat == OPJ_CODEC_JP2 )
|
|
poDS->eAccess = poOpenInfo->eAccess;
|
|
poDS->eColorSpace = psImage->color_space;
|
|
poDS->nRasterXSize = psImage->x1 - psImage->x0;
|
|
poDS->nRasterYSize = psImage->y1 - psImage->y0;
|
|
poDS->nBands = psImage->numcomps;
|
|
poDS->fp = poOpenInfo->fpL;
|
|
poOpenInfo->fpL = NULL;
|
|
poDS->nCodeStreamStart = nCodeStreamStart;
|
|
poDS->nCodeStreamLength = nCodeStreamLength;
|
|
poDS->bIs420 = bIs420;
|
|
|
|
poDS->bUseSetDecodeArea =
|
|
(poDS->nRasterXSize == (int)nTileW &&
|
|
poDS->nRasterYSize == (int)nTileH &&
|
|
(poDS->nRasterXSize > 1024 ||
|
|
poDS->nRasterYSize > 1024));
|
|
|
|
if (poDS->bUseSetDecodeArea)
|
|
{
|
|
if (nTileW > 1024) nTileW = 1024;
|
|
if (nTileH > 1024) nTileH = 1024;
|
|
}
|
|
|
|
GDALColorTable* poCT = NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Look for color table or cdef box */
|
|
/* -------------------------------------------------------------------- */
|
|
if( eCodecFormat == OPJ_CODEC_JP2 )
|
|
{
|
|
GDALJP2Box oBox( poDS->fp );
|
|
if( oBox.ReadFirst() )
|
|
{
|
|
while( strlen(oBox.GetType()) > 0 )
|
|
{
|
|
if( EQUAL(oBox.GetType(),"jp2h") )
|
|
{
|
|
GDALJP2Box oSubBox( poDS->fp );
|
|
|
|
for( oSubBox.ReadFirstChild( &oBox );
|
|
strlen(oSubBox.GetType()) > 0;
|
|
oSubBox.ReadNextChild( &oBox ) )
|
|
{
|
|
GIntBig nDataLength = oSubBox.GetDataLength();
|
|
if( poCT == NULL &&
|
|
EQUAL(oSubBox.GetType(),"pclr") &&
|
|
nDataLength >= 3 &&
|
|
nDataLength <= 2 + 1 + 4 + 4 * 256 )
|
|
{
|
|
GByte* pabyCT = oSubBox.ReadBoxData();
|
|
if( pabyCT != NULL )
|
|
{
|
|
int nEntries = (pabyCT[0] << 8) | pabyCT[1];
|
|
int nComponents = pabyCT[2];
|
|
/* CPLDebug("OPENJPEG", "Color table found"); */
|
|
if( nEntries <= 256 && nComponents == 3 )
|
|
{
|
|
/*CPLDebug("OPENJPEG", "resol[0] = %d", pabyCT[3]);
|
|
CPLDebug("OPENJPEG", "resol[1] = %d", pabyCT[4]);
|
|
CPLDebug("OPENJPEG", "resol[2] = %d", pabyCT[5]);*/
|
|
if( pabyCT[3] == 7 && pabyCT[4] == 7 && pabyCT[5] == 7 &&
|
|
nDataLength == 2 + 1 + 3 + 3 * nEntries )
|
|
{
|
|
poCT = new GDALColorTable();
|
|
for(int i=0;i<nEntries;i++)
|
|
{
|
|
GDALColorEntry sEntry;
|
|
sEntry.c1 = pabyCT[6 + 3 * i];
|
|
sEntry.c2 = pabyCT[6 + 3 * i + 1];
|
|
sEntry.c3 = pabyCT[6 + 3 * i + 2];
|
|
sEntry.c4 = 255;
|
|
poCT->SetColorEntry(i, &sEntry);
|
|
}
|
|
}
|
|
}
|
|
else if ( nEntries <= 256 && nComponents == 4 )
|
|
{
|
|
if( pabyCT[3] == 7 && pabyCT[4] == 7 &&
|
|
pabyCT[5] == 7 && pabyCT[6] == 7 &&
|
|
nDataLength == 2 + 1 + 4 + 4 * nEntries )
|
|
{
|
|
poCT = new GDALColorTable();
|
|
for(int i=0;i<nEntries;i++)
|
|
{
|
|
GDALColorEntry sEntry;
|
|
sEntry.c1 = pabyCT[7 + 4 * i];
|
|
sEntry.c2 = pabyCT[7 + 4 * i + 1];
|
|
sEntry.c3 = pabyCT[7 + 4 * i + 2];
|
|
sEntry.c4 = pabyCT[7 + 4 * i + 3];
|
|
poCT->SetColorEntry(i, &sEntry);
|
|
}
|
|
}
|
|
}
|
|
CPLFree(pabyCT);
|
|
}
|
|
}
|
|
/* There's a bug/misfeature in openjpeg: the color_space
|
|
only gets set at read tile time */
|
|
else if( EQUAL(oSubBox.GetType(),"colr") &&
|
|
nDataLength == 7 )
|
|
{
|
|
GByte* pabyContent = oSubBox.ReadBoxData();
|
|
if( pabyContent != NULL )
|
|
{
|
|
if( pabyContent[0] == 1 /* enumerated colourspace */ )
|
|
{
|
|
GUInt32 enumcs = (pabyContent[3] << 24) |
|
|
(pabyContent[4] << 16) |
|
|
(pabyContent[5] << 8) |
|
|
(pabyContent[6]);
|
|
if( enumcs == 16 )
|
|
{
|
|
poDS->eColorSpace = OPJ_CLRSPC_SRGB;
|
|
CPLDebug("OPENJPEG", "SRGB color space");
|
|
}
|
|
else if( enumcs == 17 )
|
|
{
|
|
poDS->eColorSpace = OPJ_CLRSPC_GRAY;
|
|
CPLDebug("OPENJPEG", "Grayscale color space");
|
|
}
|
|
else if( enumcs == 18 )
|
|
{
|
|
poDS->eColorSpace = OPJ_CLRSPC_SYCC;
|
|
CPLDebug("OPENJPEG", "SYCC color space");
|
|
}
|
|
else if( enumcs == 20 )
|
|
{
|
|
/* Used by J2KP4files/testfiles_jp2/file7.jp2 */
|
|
poDS->eColorSpace = OPJ_CLRSPC_SRGB;
|
|
CPLDebug("OPENJPEG", "e-sRGB color space");
|
|
}
|
|
else if( enumcs == 21 )
|
|
{
|
|
/* Used by J2KP4files/testfiles_jp2/file5.jp2 */
|
|
poDS->eColorSpace = OPJ_CLRSPC_SRGB;
|
|
CPLDebug("OPENJPEG", "ROMM-RGB color space");
|
|
}
|
|
else
|
|
{
|
|
poDS->eColorSpace = OPJ_CLRSPC_UNKNOWN;
|
|
CPLDebug("OPENJPEG", "Unknown color space");
|
|
}
|
|
}
|
|
CPLFree(pabyContent);
|
|
}
|
|
}
|
|
/* Check if there's an alpha channel or odd channel attribution */
|
|
else if( EQUAL(oSubBox.GetType(),"cdef") &&
|
|
nDataLength == 2 + poDS->nBands * 6 )
|
|
{
|
|
GByte* pabyContent = oSubBox.ReadBoxData();
|
|
if( pabyContent != NULL )
|
|
{
|
|
int nEntries = (pabyContent[0] << 8) | pabyContent[1];
|
|
if( nEntries == poDS->nBands )
|
|
{
|
|
poDS->nRedIndex = -1;
|
|
poDS->nGreenIndex = -1;
|
|
poDS->nBlueIndex = -1;
|
|
for(int i=0;i<poDS->nBands;i++)
|
|
{
|
|
int CNi = (pabyContent[2+6*i] << 8) | pabyContent[2+6*i+1];
|
|
int Typi = (pabyContent[2+6*i+2] << 8) | pabyContent[2+6*i+3];
|
|
int Asoci = (pabyContent[2+6*i+4] << 8) | pabyContent[2+6*i+5];
|
|
if( CNi < 0 || CNi >= poDS->nBands )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Wrong value of CN%d=%d", i, CNi);
|
|
break;
|
|
}
|
|
if( Typi == 0 )
|
|
{
|
|
if( Asoci == 1 )
|
|
poDS->nRedIndex = CNi;
|
|
else if( Asoci == 2 )
|
|
poDS->nGreenIndex = CNi;
|
|
else if( Asoci == 3 )
|
|
poDS->nBlueIndex = CNi;
|
|
else if( Asoci < 0 || (Asoci > poDS->nBands && Asoci != 65535) )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Wrong value of Asoc%d=%d", i, Asoci);
|
|
break;
|
|
}
|
|
}
|
|
else if( Typi == 1 )
|
|
{
|
|
poDS->nAlphaIndex = CNi;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLDebug("OPENJPEG", "Unsupported cdef content");
|
|
}
|
|
CPLFree(pabyContent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!oBox.ReadNext())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create band information objects. */
|
|
/* -------------------------------------------------------------------- */
|
|
for( iBand = 1; iBand <= poDS->nBands; iBand++ )
|
|
{
|
|
int bPromoteTo8Bit = (
|
|
iBand == poDS->nAlphaIndex + 1 &&
|
|
psImage->comps[(poDS->nAlphaIndex==0 && poDS->nBands > 1) ? 1 : 0].prec == 8 &&
|
|
psImage->comps[poDS->nAlphaIndex ].prec == 1 &&
|
|
CSLFetchBoolean(poOpenInfo->papszOpenOptions, "1BIT_ALPHA_PROMOTION",
|
|
CSLTestBoolean(CPLGetConfigOption("JP2OPENJPEG_PROMOTE_1BIT_ALPHA_AS_8BIT", "YES"))) );
|
|
if( bPromoteTo8Bit )
|
|
CPLDebug("JP2OpenJPEG", "Alpha band is promoted from 1 bit to 8 bit");
|
|
|
|
JP2OpenJPEGRasterBand* poBand =
|
|
new JP2OpenJPEGRasterBand( poDS, iBand, eDataType,
|
|
bPromoteTo8Bit ? 8: psImage->comps[iBand-1].prec,
|
|
bPromoteTo8Bit,
|
|
nTileW, nTileH);
|
|
if( iBand == 1 && poCT != NULL )
|
|
poBand->poCT = poCT;
|
|
poDS->SetBand( iBand, poBand );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create overview datasets. */
|
|
/* -------------------------------------------------------------------- */
|
|
int nW = poDS->nRasterXSize;
|
|
int nH = poDS->nRasterYSize;
|
|
|
|
/* Lower resolutions are not compatible with a color-table */
|
|
if( poCT != NULL )
|
|
numResolutions = 0;
|
|
|
|
while (poDS->nOverviewCount+1 < numResolutions &&
|
|
(nW > 128 || nH > 128) &&
|
|
(poDS->bUseSetDecodeArea || ((nTileW % 2) == 0 && (nTileH % 2) == 0)))
|
|
{
|
|
nW /= 2;
|
|
nH /= 2;
|
|
|
|
poDS->papoOverviewDS = (JP2OpenJPEGDataset**) CPLRealloc(
|
|
poDS->papoOverviewDS,
|
|
(poDS->nOverviewCount + 1) * sizeof(JP2OpenJPEGDataset*));
|
|
JP2OpenJPEGDataset* poODS = new JP2OpenJPEGDataset();
|
|
poODS->SetDescription( poOpenInfo->pszFilename );
|
|
poODS->iLevel = poDS->nOverviewCount + 1;
|
|
poODS->bUseSetDecodeArea = poDS->bUseSetDecodeArea;
|
|
poODS->nRedIndex = poDS->nRedIndex;
|
|
poODS->nGreenIndex = poDS->nGreenIndex;
|
|
poODS->nBlueIndex = poDS->nBlueIndex;
|
|
poODS->nAlphaIndex = poDS->nAlphaIndex;
|
|
if (!poDS->bUseSetDecodeArea)
|
|
{
|
|
nTileW /= 2;
|
|
nTileH /= 2;
|
|
}
|
|
else
|
|
{
|
|
if (nW < (int)nTileW || nH < (int)nTileH)
|
|
{
|
|
nTileW = nW;
|
|
nTileH = nH;
|
|
poODS->bUseSetDecodeArea = FALSE;
|
|
}
|
|
}
|
|
|
|
poODS->eColorSpace = poDS->eColorSpace;
|
|
poODS->nRasterXSize = nW;
|
|
poODS->nRasterYSize = nH;
|
|
poODS->nBands = poDS->nBands;
|
|
poODS->fp = poDS->fp;
|
|
poODS->nCodeStreamStart = nCodeStreamStart;
|
|
poODS->nCodeStreamLength = nCodeStreamLength;
|
|
poODS->bIs420 = bIs420;
|
|
for( iBand = 1; iBand <= poDS->nBands; iBand++ )
|
|
{
|
|
int bPromoteTo8Bit = (
|
|
iBand == poDS->nAlphaIndex + 1 &&
|
|
psImage->comps[(poDS->nAlphaIndex==0 && poDS->nBands > 1) ? 1 : 0].prec == 8 &&
|
|
psImage->comps[poDS->nAlphaIndex].prec == 1 &&
|
|
CSLFetchBoolean(poOpenInfo->papszOpenOptions, "1BIT_ALPHA_PROMOTION",
|
|
CSLTestBoolean(CPLGetConfigOption("JP2OPENJPEG_PROMOTE_1BIT_ALPHA_AS_8BIT", "YES"))) );
|
|
|
|
poODS->SetBand( iBand, new JP2OpenJPEGRasterBand( poODS, iBand, eDataType,
|
|
bPromoteTo8Bit ? 8: psImage->comps[iBand-1].prec,
|
|
bPromoteTo8Bit,
|
|
nTileW, nTileH ) );
|
|
}
|
|
|
|
poDS->papoOverviewDS[poDS->nOverviewCount ++] = poODS;
|
|
|
|
}
|
|
|
|
opj_destroy_codec(pCodec);
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
pCodec = NULL;
|
|
pStream = NULL;
|
|
psImage = NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* More metadata. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( poDS->nBands > 1 )
|
|
{
|
|
poDS->GDALDataset::SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
|
|
}
|
|
|
|
poOpenInfo->fpL = poDS->fp;
|
|
poDS->LoadJP2Metadata(poOpenInfo);
|
|
poOpenInfo->fpL = NULL;
|
|
|
|
poDS->bHasGeoreferencingAtOpening =
|
|
((poDS->pszProjection != NULL && poDS->pszProjection[0] != '\0' )||
|
|
poDS->nGCPCount != 0 || poDS->bGeoTransformValid);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Vector layers */
|
|
/* -------------------------------------------------------------------- */
|
|
if( poOpenInfo->nOpenFlags & GDAL_OF_VECTOR )
|
|
{
|
|
poDS->LoadVectorLayers(
|
|
CSLFetchBoolean(poOpenInfo->papszOpenOptions, "OPEN_REMOTE_GML", FALSE));
|
|
|
|
// If file opened in vector-only mode and there's no vector,
|
|
// return
|
|
if( (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
|
|
poDS->GetLayerCount() == 0 )
|
|
{
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Initialize any PAM information. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->SetDescription( poOpenInfo->pszFilename );
|
|
poDS->TryLoadXML();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for overviews. */
|
|
/* -------------------------------------------------------------------- */
|
|
//poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
|
|
|
|
return( poDS );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* WriteBox() */
|
|
/************************************************************************/
|
|
|
|
void JP2OpenJPEGDataset::WriteBox(VSILFILE* fp, GDALJP2Box* poBox)
|
|
{
|
|
GUInt32 nLBox;
|
|
GUInt32 nTBox;
|
|
|
|
if( poBox == NULL )
|
|
return;
|
|
|
|
nLBox = (int) poBox->GetDataLength() + 8;
|
|
nLBox = CPL_MSBWORD32( nLBox );
|
|
|
|
memcpy(&nTBox, poBox->GetType(), 4);
|
|
|
|
VSIFWriteL( &nLBox, 4, 1, fp );
|
|
VSIFWriteL( &nTBox, 4, 1, fp );
|
|
VSIFWriteL(poBox->GetWritableData(), 1, (int) poBox->GetDataLength(), fp);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* WriteGDALMetadataBox() */
|
|
/************************************************************************/
|
|
|
|
void JP2OpenJPEGDataset::WriteGDALMetadataBox( VSILFILE* fp,
|
|
GDALDataset* poSrcDS,
|
|
char** papszOptions )
|
|
{
|
|
GDALJP2Box* poBox = GDALJP2Metadata::CreateGDALMultiDomainMetadataXMLBox(
|
|
poSrcDS, CSLFetchBoolean(papszOptions, "MAIN_MD_DOMAIN_ONLY", FALSE));
|
|
if( poBox )
|
|
WriteBox(fp, poBox);
|
|
delete poBox;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* WriteXMLBoxes() */
|
|
/************************************************************************/
|
|
|
|
void JP2OpenJPEGDataset::WriteXMLBoxes( VSILFILE* fp, GDALDataset* poSrcDS,
|
|
CPL_UNUSED char** papszOptions )
|
|
{
|
|
int nBoxes = 0;
|
|
GDALJP2Box** papoBoxes = GDALJP2Metadata::CreateXMLBoxes(poSrcDS, &nBoxes);
|
|
for(int i=0;i<nBoxes;i++)
|
|
{
|
|
WriteBox(fp, papoBoxes[i]);
|
|
delete papoBoxes[i];
|
|
}
|
|
CPLFree(papoBoxes);
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* WriteXMPBox() */
|
|
/************************************************************************/
|
|
|
|
void JP2OpenJPEGDataset::WriteXMPBox ( VSILFILE* fp, GDALDataset* poSrcDS,
|
|
CPL_UNUSED char** papszOptions )
|
|
{
|
|
GDALJP2Box* poBox = GDALJP2Metadata::CreateXMPBox(poSrcDS);
|
|
if( poBox )
|
|
WriteBox(fp, poBox);
|
|
delete poBox;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* WriteIPRBox() */
|
|
/************************************************************************/
|
|
|
|
void JP2OpenJPEGDataset::WriteIPRBox ( VSILFILE* fp, GDALDataset* poSrcDS,
|
|
CPL_UNUSED char** papszOptions )
|
|
{
|
|
GDALJP2Box* poBox = GDALJP2Metadata::CreateIPRBox(poSrcDS);
|
|
if( poBox )
|
|
WriteBox(fp, poBox);
|
|
delete poBox;
|
|
}
|
|
/************************************************************************/
|
|
/* FloorPowerOfTwo() */
|
|
/************************************************************************/
|
|
|
|
static int FloorPowerOfTwo(int nVal)
|
|
{
|
|
int nBits = 0;
|
|
while( nVal > 1 )
|
|
{
|
|
nBits ++;
|
|
nVal >>= 1;
|
|
}
|
|
return 1 << nBits;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CreateCopy() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset * JP2OpenJPEGDataset::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();
|
|
|
|
if( nBands == 0 || nBands > 16384 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"Unable to export files with %d bands. Must be >= 1 and <= 16384", nBands );
|
|
return NULL;
|
|
}
|
|
|
|
GDALColorTable* poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
|
|
if (poCT != NULL && nBands != 1)
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"JP2OpenJPEG driver only supports a color table for a single-band dataset");
|
|
return NULL;
|
|
}
|
|
|
|
GDALDataType eDataType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
|
|
int nDataTypeSize = (GDALGetDataTypeSize(eDataType) / 8);
|
|
if (eDataType != GDT_Byte && eDataType != GDT_Int16 && eDataType != GDT_UInt16
|
|
&& eDataType != GDT_Int32 && eDataType != GDT_UInt32)
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"JP2OpenJPEG driver only supports creating Byte, GDT_Int16, GDT_UInt16, GDT_Int32, GDT_UInt32");
|
|
return NULL;
|
|
}
|
|
|
|
int bInspireTG = CSLFetchBoolean(papszOptions, "INSPIRE_TG", FALSE);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Analyze creation options. */
|
|
/* -------------------------------------------------------------------- */
|
|
OPJ_CODEC_FORMAT eCodecFormat = OPJ_CODEC_J2K;
|
|
const char* pszCodec = CSLFetchNameValueDef(papszOptions, "CODEC", NULL);
|
|
if (pszCodec)
|
|
{
|
|
if (EQUAL(pszCodec, "JP2"))
|
|
eCodecFormat = OPJ_CODEC_JP2;
|
|
else if (EQUAL(pszCodec, "J2K"))
|
|
eCodecFormat = OPJ_CODEC_J2K;
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Unsupported value for CODEC : %s. Defaulting to J2K",
|
|
pszCodec);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (strlen(pszFilename) > 4 &&
|
|
EQUAL(pszFilename + strlen(pszFilename) - 4, ".JP2"))
|
|
{
|
|
eCodecFormat = OPJ_CODEC_JP2;
|
|
}
|
|
}
|
|
if( eCodecFormat != OPJ_CODEC_JP2 && bInspireTG )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"INSPIRE_TG=YES mandates CODEC=JP2 (TG requirement 21)");
|
|
return NULL;
|
|
}
|
|
|
|
int nBlockXSize =
|
|
atoi(CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", "1024"));
|
|
int nBlockYSize =
|
|
atoi(CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", "1024"));
|
|
if (nBlockXSize < 32 || nBlockYSize < 32)
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported, "Invalid block size");
|
|
return NULL;
|
|
}
|
|
|
|
if (nXSize < nBlockXSize)
|
|
nBlockXSize = nXSize;
|
|
if (nYSize < nBlockYSize)
|
|
nBlockYSize = nYSize;
|
|
|
|
OPJ_PROG_ORDER eProgOrder = OPJ_LRCP;
|
|
const char* pszPROGORDER =
|
|
CSLFetchNameValueDef(papszOptions, "PROGRESSION", "LRCP");
|
|
if (EQUAL(pszPROGORDER, "LRCP"))
|
|
eProgOrder = OPJ_LRCP;
|
|
else if (EQUAL(pszPROGORDER, "RLCP"))
|
|
eProgOrder = OPJ_RLCP;
|
|
else if (EQUAL(pszPROGORDER, "RPCL"))
|
|
eProgOrder = OPJ_RPCL;
|
|
else if (EQUAL(pszPROGORDER, "PCRL"))
|
|
eProgOrder = OPJ_PCRL;
|
|
else if (EQUAL(pszPROGORDER, "CPRL"))
|
|
eProgOrder = OPJ_CPRL;
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Unsupported value for PROGRESSION : %s. Defaulting to LRCP",
|
|
pszPROGORDER);
|
|
}
|
|
|
|
int bIsIrreversible =
|
|
! (CSLFetchBoolean(papszOptions, "REVERSIBLE", poCT != NULL));
|
|
|
|
std::vector<double> adfRates;
|
|
const char* pszQuality = CSLFetchNameValueDef(papszOptions, "QUALITY", NULL);
|
|
double dfDefaultQuality = ( poCT != NULL ) ? 100.0 : 25.0;
|
|
if (pszQuality)
|
|
{
|
|
char **papszTokens = CSLTokenizeStringComplex( pszQuality, ",", FALSE, FALSE );
|
|
for(int i=0; papszTokens[i] != NULL; i++ )
|
|
{
|
|
double dfQuality = CPLAtof(papszTokens[i]);
|
|
if (dfQuality > 0 && dfQuality <= 100)
|
|
{
|
|
double dfRate = 100 / dfQuality;
|
|
adfRates.push_back(dfRate);
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Unsupported value for QUALITY: %s. Defaulting to single-layer, with quality=%.0f",
|
|
papszTokens[i], dfDefaultQuality);
|
|
adfRates.resize(0);
|
|
break;
|
|
}
|
|
}
|
|
if( papszTokens[0] == NULL )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Unsupported value for QUALITY: %s. Defaulting to single-layer, with quality=%.0f",
|
|
pszQuality, dfDefaultQuality);
|
|
}
|
|
CSLDestroy(papszTokens);
|
|
}
|
|
if( adfRates.size() == 0 )
|
|
{
|
|
adfRates.push_back(100. / dfDefaultQuality);
|
|
}
|
|
|
|
if( poCT != NULL && (bIsIrreversible || adfRates[adfRates.size()-1] != 100.0 / 100.0) )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Encoding a dataset with a color table with REVERSIBLE != YES "
|
|
"or QUALITY != 100 will likely lead to bad visual results");
|
|
}
|
|
|
|
int nMaxTileDim = MAX(nBlockXSize, nBlockYSize);
|
|
int nNumResolutions = 1;
|
|
/* Pickup a reasonable value compatible with PROFILE_1 requirements */
|
|
while( (nMaxTileDim >> (nNumResolutions-1)) > 128 )
|
|
nNumResolutions ++;
|
|
int nMinProfile1Resolutions = nNumResolutions;
|
|
const char* pszResolutions = CSLFetchNameValueDef(papszOptions, "RESOLUTIONS", NULL);
|
|
if (pszResolutions)
|
|
{
|
|
nNumResolutions = atoi(pszResolutions);
|
|
if (nNumResolutions <= 0 || nNumResolutions >= 32 ||
|
|
(nMaxTileDim >> nNumResolutions) == 0 )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Unsupported value for RESOLUTIONS : %s. Defaulting to %d",
|
|
pszResolutions, nMinProfile1Resolutions);
|
|
nNumResolutions = nMinProfile1Resolutions;
|
|
}
|
|
}
|
|
|
|
int bSOP = CSLTestBoolean(CSLFetchNameValueDef(papszOptions, "SOP", "FALSE"));
|
|
int bEPH = CSLTestBoolean(CSLFetchNameValueDef(papszOptions, "EPH", "FALSE"));
|
|
|
|
int nRedBandIndex = -1, nGreenBandIndex = -1, nBlueBandIndex = -1;
|
|
int nAlphaBandIndex = -1;
|
|
for(int i=0;i<nBands;i++)
|
|
{
|
|
GDALColorInterp eInterp = poSrcDS->GetRasterBand(i+1)->GetColorInterpretation();
|
|
if( eInterp == GCI_RedBand )
|
|
nRedBandIndex = i;
|
|
else if( eInterp == GCI_GreenBand )
|
|
nGreenBandIndex = i;
|
|
else if( eInterp == GCI_BlueBand )
|
|
nBlueBandIndex = i;
|
|
else if( eInterp == GCI_AlphaBand )
|
|
nAlphaBandIndex = i;
|
|
}
|
|
const char* pszAlpha = CSLFetchNameValue(papszOptions, "ALPHA");
|
|
if( nAlphaBandIndex < 0 && nBands > 1 && pszAlpha != NULL && CSLTestBoolean(pszAlpha) )
|
|
{
|
|
nAlphaBandIndex = nBands - 1;
|
|
}
|
|
|
|
const char* pszYCBCR420 = CSLFetchNameValue(papszOptions, "YCBCR420");
|
|
int bYCBCR420 = FALSE;
|
|
if( pszYCBCR420 && CSLTestBoolean(pszYCBCR420) )
|
|
{
|
|
if ((nBands == 3 || nBands == 4) && eDataType == GDT_Byte &&
|
|
nRedBandIndex == 0 && nGreenBandIndex == 1 && nBlueBandIndex == 2)
|
|
{
|
|
if( ((nXSize % 2) == 0 && (nYSize % 2) == 0 && (nBlockXSize % 2) == 0 && (nBlockYSize % 2) == 0) )
|
|
{
|
|
bYCBCR420 = TRUE;
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"YCBCR420 unsupported when image size and/or tile size are not multiple of 2");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"YCBCR420 unsupported with this image band count and/or data byte");
|
|
}
|
|
}
|
|
|
|
const char* pszYCC = CSLFetchNameValue(papszOptions, "YCC");
|
|
int bYCC = ((nBands == 3 || nBands == 4) && eDataType == GDT_Byte &&
|
|
CSLTestBoolean(CSLFetchNameValueDef(papszOptions, "YCC", "TRUE")));
|
|
|
|
/* TODO: when OpenJPEG 2.2 is released, make this conditional */
|
|
/* Depending on the way OpenJPEG <= r2950 is built, YCC with 4 bands might work on
|
|
* Debug mode, but this relies on unreliable stack buffer overflows, so
|
|
* better err on the safe side */
|
|
if( bYCC && nBands > 3 )
|
|
{
|
|
if( pszYCC != NULL )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"OpenJPEG r2950 and below can generate invalid output with "
|
|
"MCT YCC transform and more than 3 bands. Disabling YCC");
|
|
}
|
|
bYCC = FALSE;
|
|
}
|
|
|
|
if( bYCBCR420 && bYCC )
|
|
{
|
|
if( pszYCC != NULL )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"YCC unsupported when YCbCr requesting");
|
|
}
|
|
bYCC = FALSE;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Deal with codeblocks size */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
int nCblockW = atoi(CSLFetchNameValueDef( papszOptions, "CODEBLOCK_WIDTH", "64" ));
|
|
int nCblockH = atoi(CSLFetchNameValueDef( papszOptions, "CODEBLOCK_HEIGHT", "64" ));
|
|
if( nCblockW < 4 || nCblockW > 1024 || nCblockH < 4 || nCblockH > 1024 )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Invalid values for codeblock size. Defaulting to 64x64");
|
|
nCblockW = 64;
|
|
nCblockH = 64;
|
|
}
|
|
else if( nCblockW * nCblockH > 4096 )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Invalid values for codeblock size. "
|
|
"CODEBLOCK_WIDTH * CODEBLOCK_HEIGHT should be <= 4096. "
|
|
"Defaulting to 64x64");
|
|
nCblockW = 64;
|
|
nCblockH = 64;
|
|
}
|
|
int nCblockW_po2 = FloorPowerOfTwo(nCblockW);
|
|
int nCblockH_po2 = FloorPowerOfTwo(nCblockH);
|
|
if( nCblockW_po2 != nCblockW || nCblockH_po2 != nCblockH )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Non power of two values used for codeblock size. "
|
|
"Using to %dx%d",
|
|
nCblockW_po2, nCblockH_po2);
|
|
}
|
|
nCblockW = nCblockW_po2;
|
|
nCblockH = nCblockH_po2;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Deal with codestream PROFILE */
|
|
/* -------------------------------------------------------------------- */
|
|
const char* pszProfile = CSLFetchNameValueDef( papszOptions, "PROFILE", "AUTO" );
|
|
int bProfile1 = FALSE;
|
|
if( EQUAL(pszProfile, "UNRESTRICTED") )
|
|
{
|
|
bProfile1 = FALSE;
|
|
if( bInspireTG )
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"INSPIRE_TG=YES mandates PROFILE=PROFILE_1 (TG requirement 21)");
|
|
return NULL;
|
|
}
|
|
}
|
|
else if( EQUAL(pszProfile, "UNRESTRICTED_FORCED") )
|
|
{
|
|
bProfile1 = FALSE;
|
|
}
|
|
else if( EQUAL(pszProfile, "PROFILE_1_FORCED") ) /* For debug only: can produce inconsistent codestream */
|
|
{
|
|
bProfile1 = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if( !(EQUAL(pszProfile, "PROFILE_1") || EQUAL(pszProfile, "AUTO")) )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Unsupported value for PROFILE : %s. Defaulting to AUTO",
|
|
pszProfile);
|
|
pszProfile = "AUTO";
|
|
}
|
|
|
|
bProfile1 = TRUE;
|
|
const char* pszReq21OrEmpty = (bInspireTG) ? " (TG requirement 21)" : "";
|
|
if( (nBlockXSize != nXSize || nBlockYSize != nYSize) &&
|
|
(nBlockXSize != nBlockYSize || nBlockXSize > 1024 || nBlockYSize > 1024 ) )
|
|
{
|
|
bProfile1 = FALSE;
|
|
if( bInspireTG || EQUAL(pszProfile, "PROFILE_1") )
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Tile dimensions incompatible with PROFILE_1%s. "
|
|
"Should be whole image or square with dimension <= 1024.",
|
|
pszReq21OrEmpty);
|
|
return NULL;
|
|
}
|
|
}
|
|
if( (nMaxTileDim >> (nNumResolutions-1)) > 128 )
|
|
{
|
|
bProfile1 = FALSE;
|
|
if( bInspireTG || EQUAL(pszProfile, "PROFILE_1") )
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Number of resolutions incompatible with PROFILE_1%s. "
|
|
"Should be at least %d.",
|
|
pszReq21OrEmpty,
|
|
nMinProfile1Resolutions);
|
|
return NULL;
|
|
}
|
|
}
|
|
if( nCblockW > 64 || nCblockH > 64 )
|
|
{
|
|
bProfile1 = FALSE;
|
|
if( bInspireTG || EQUAL(pszProfile, "PROFILE_1") )
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"Codeblock width incompatible with PROFILE_1%s. "
|
|
"Codeblock width or height should be <= 64.",
|
|
pszReq21OrEmpty);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Work out the precision. */
|
|
/* -------------------------------------------------------------------- */
|
|
int nBits;
|
|
if( CSLFetchNameValue( papszOptions, "NBITS" ) != NULL )
|
|
{
|
|
nBits = atoi(CSLFetchNameValue(papszOptions,"NBITS"));
|
|
if( bInspireTG &&
|
|
!(nBits == 1 || nBits == 8 || nBits == 16 || nBits == 32) )
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported,
|
|
"INSPIRE_TG=YES mandates NBITS=1,8,16 or 32 (TG requirement 24)");
|
|
return NULL;
|
|
}
|
|
}
|
|
else if( poSrcDS->GetRasterBand(1)->GetMetadataItem( "NBITS", "IMAGE_STRUCTURE" )
|
|
!= NULL )
|
|
{
|
|
nBits = atoi(poSrcDS->GetRasterBand(1)->GetMetadataItem( "NBITS",
|
|
"IMAGE_STRUCTURE" ));
|
|
if( bInspireTG &&
|
|
!(nBits == 1 || nBits == 8 || nBits == 16 || nBits == 32) )
|
|
{
|
|
/* Implements "NOTE If the original data do not satisfy this "
|
|
"requirement, they will be converted in a representation using "
|
|
"the next higher power of 2" */
|
|
nBits = GDALGetDataTypeSize(eDataType);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nBits = GDALGetDataTypeSize(eDataType);
|
|
}
|
|
|
|
if( (GDALGetDataTypeSize(eDataType) == 8 && nBits > 8) ||
|
|
(GDALGetDataTypeSize(eDataType) == 16 && (nBits <= 8 || nBits > 16)) ||
|
|
(GDALGetDataTypeSize(eDataType) == 32 && (nBits <= 16 || nBits > 32)) )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Inconsistent NBITS value with data type. Using %d",
|
|
GDALGetDataTypeSize(eDataType));
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Georeferencing options */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
int bGMLJP2Option = CSLFetchBoolean( papszOptions, "GMLJP2", TRUE );
|
|
int nGMLJP2Version = 1;
|
|
const char* pszGMLJP2V2Def = CSLFetchNameValue( papszOptions, "GMLJP2V2_DEF" );
|
|
if( pszGMLJP2V2Def != NULL )
|
|
{
|
|
bGMLJP2Option = TRUE;
|
|
nGMLJP2Version = 2;
|
|
if( bInspireTG )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"INSPIRE_TG=YES is only compatible with GMLJP2 v1");
|
|
return NULL;
|
|
}
|
|
}
|
|
int bGeoJP2Option = CSLFetchBoolean( papszOptions, "GeoJP2", TRUE );
|
|
|
|
GDALJP2Metadata oJP2MD;
|
|
|
|
int bGeoreferencingCompatOfGeoJP2 = FALSE;
|
|
int bGeoreferencingCompatOfGMLJP2 = FALSE;
|
|
if( eCodecFormat == OPJ_CODEC_JP2 && (bGMLJP2Option || bGeoJP2Option) )
|
|
{
|
|
if( poSrcDS->GetGCPCount() > 0 )
|
|
{
|
|
bGeoreferencingCompatOfGeoJP2 = TRUE;
|
|
oJP2MD.SetGCPs( poSrcDS->GetGCPCount(),
|
|
poSrcDS->GetGCPs() );
|
|
oJP2MD.SetProjection( poSrcDS->GetGCPProjection() );
|
|
}
|
|
else
|
|
{
|
|
const char* pszWKT = poSrcDS->GetProjectionRef();
|
|
if( pszWKT != NULL && pszWKT[0] != '\0' )
|
|
{
|
|
bGeoreferencingCompatOfGeoJP2 = TRUE;
|
|
oJP2MD.SetProjection( pszWKT );
|
|
}
|
|
double adfGeoTransform[6];
|
|
if( poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None )
|
|
{
|
|
bGeoreferencingCompatOfGeoJP2 = TRUE;
|
|
oJP2MD.SetGeoTransform( adfGeoTransform );
|
|
}
|
|
bGeoreferencingCompatOfGMLJP2 =
|
|
( pszWKT != NULL && pszWKT[0] != '\0' ) &&
|
|
poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None;
|
|
}
|
|
if( poSrcDS->GetMetadata("RPC") != NULL )
|
|
{
|
|
oJP2MD.SetRPCMD( poSrcDS->GetMetadata("RPC") );
|
|
bGeoreferencingCompatOfGeoJP2 = TRUE;
|
|
}
|
|
|
|
const char* pszAreaOrPoint = poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
|
|
oJP2MD.bPixelIsPoint = pszAreaOrPoint != NULL && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT);
|
|
|
|
if( bGMLJP2Option && CPLGetConfigOption("GMLJP2OVERRIDE", NULL) != NULL )
|
|
bGeoreferencingCompatOfGMLJP2 = TRUE;
|
|
}
|
|
|
|
if( CSLFetchNameValue( papszOptions, "GMLJP2" ) != NULL && bGMLJP2Option &&
|
|
!bGeoreferencingCompatOfGMLJP2 && nGMLJP2Version == 1 )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"GMLJP2 box was explicitly required but cannot be written due "
|
|
"to lack of georeferencing and/or unsupported georeferencing for GMLJP2");
|
|
}
|
|
|
|
if( CSLFetchNameValue( papszOptions, "GeoJP2" ) != NULL && bGeoJP2Option &&
|
|
!bGeoreferencingCompatOfGeoJP2 )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"GeoJP2 box was explicitly required but cannot be written due "
|
|
"to lack of georeferencing");
|
|
}
|
|
int bGeoBoxesAfter = CSLFetchBoolean(papszOptions, "GEOBOXES_AFTER_JP2C",
|
|
bInspireTG);
|
|
GDALJP2Box* poGMLJP2Box = NULL;
|
|
if( eCodecFormat == OPJ_CODEC_JP2 && bGMLJP2Option && bGeoreferencingCompatOfGMLJP2 )
|
|
{
|
|
if( nGMLJP2Version == 1)
|
|
poGMLJP2Box = oJP2MD.CreateGMLJP2(nXSize,nYSize);
|
|
else
|
|
poGMLJP2Box = oJP2MD.CreateGMLJP2V2(nXSize,nYSize,pszGMLJP2V2Def,poSrcDS);
|
|
if( poGMLJP2Box == NULL )
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Setup encoder */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
opj_cparameters_t parameters;
|
|
opj_set_default_encoder_parameters(¶meters);
|
|
if (bSOP)
|
|
parameters.csty |= 0x02;
|
|
if (bEPH)
|
|
parameters.csty |= 0x04;
|
|
parameters.cp_disto_alloc = 1;
|
|
parameters.tcp_numlayers = (int)adfRates.size();
|
|
for(int i=0;i<(int)adfRates.size();i++)
|
|
parameters.tcp_rates[i] = (float) adfRates[i];
|
|
parameters.cp_tx0 = 0;
|
|
parameters.cp_ty0 = 0;
|
|
parameters.tile_size_on = TRUE;
|
|
parameters.cp_tdx = nBlockXSize;
|
|
parameters.cp_tdy = nBlockYSize;
|
|
parameters.irreversible = bIsIrreversible;
|
|
parameters.numresolution = nNumResolutions;
|
|
parameters.prog_order = eProgOrder;
|
|
parameters.tcp_mct = bYCC;
|
|
parameters.cblockw_init = nCblockW;
|
|
parameters.cblockh_init = nCblockH;
|
|
|
|
/* Add precincts */
|
|
const char* pszPrecincts = CSLFetchNameValueDef(papszOptions, "PRECINCTS",
|
|
"{512,512},{256,512},{128,512},{64,512},{32,512},{16,512},{8,512},{4,512},{2,512}");
|
|
char **papszTokens = CSLTokenizeStringComplex( pszPrecincts, "{},", FALSE, FALSE );
|
|
int nPrecincts = CSLCount(papszTokens) / 2;
|
|
for(int i=0;i<nPrecincts && i < OPJ_J2K_MAXRLVLS;i++)
|
|
{
|
|
int nPCRW = atoi(papszTokens[2*i]);
|
|
int nPCRH = atoi(papszTokens[2*i+1]);
|
|
if( nPCRW < 1 || nPCRH < 1 )
|
|
break;
|
|
parameters.csty |= 0x01;
|
|
parameters.res_spec ++;
|
|
parameters.prcw_init[i] = nPCRW;
|
|
parameters.prch_init[i] = nPCRH;
|
|
}
|
|
CSLDestroy(papszTokens);
|
|
|
|
/* Add tileparts setting */
|
|
const char* pszTileParts = CSLFetchNameValueDef(papszOptions, "TILEPARTS", "DISABLED");
|
|
if( EQUAL(pszTileParts, "RESOLUTIONS") )
|
|
{
|
|
parameters.tp_on = 1;
|
|
parameters.tp_flag = 'R';
|
|
}
|
|
else if( EQUAL(pszTileParts, "LAYERS") )
|
|
{
|
|
if( parameters.tcp_numlayers == 1 )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"TILEPARTS=LAYERS has no real interest with single-layer codestream");
|
|
}
|
|
parameters.tp_on = 1;
|
|
parameters.tp_flag = 'L';
|
|
}
|
|
else if( EQUAL(pszTileParts, "COMPONENTS") )
|
|
{
|
|
parameters.tp_on = 1;
|
|
parameters.tp_flag = 'C';
|
|
}
|
|
else if( !EQUAL(pszTileParts, "DISABLED") )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Invalid value for TILEPARTS");
|
|
}
|
|
|
|
if( bProfile1 )
|
|
{
|
|
#if defined(OPENJPEG_VERSION) && OPENJPEG_VERSION >= 20100
|
|
parameters.rsiz = OPJ_PROFILE_1;
|
|
#else
|
|
/* This is a hack but this works */
|
|
parameters.cp_rsiz = (OPJ_RSIZ_CAPABILITIES) 2; /* Profile 1 */
|
|
#endif
|
|
}
|
|
|
|
opj_image_cmptparm_t* pasBandParams =
|
|
(opj_image_cmptparm_t*)CPLMalloc(nBands * sizeof(opj_image_cmptparm_t));
|
|
int iBand;
|
|
int bSamePrecision = TRUE;
|
|
int b1BitAlpha = FALSE;
|
|
for(iBand=0;iBand<nBands;iBand++)
|
|
{
|
|
pasBandParams[iBand].x0 = 0;
|
|
pasBandParams[iBand].y0 = 0;
|
|
if (bYCBCR420 && (iBand == 1 || iBand == 2))
|
|
{
|
|
pasBandParams[iBand].dx = 2;
|
|
pasBandParams[iBand].dy = 2;
|
|
pasBandParams[iBand].w = nXSize / 2;
|
|
pasBandParams[iBand].h = nYSize / 2;
|
|
}
|
|
else
|
|
{
|
|
pasBandParams[iBand].dx = 1;
|
|
pasBandParams[iBand].dy = 1;
|
|
pasBandParams[iBand].w = nXSize;
|
|
pasBandParams[iBand].h = nYSize;
|
|
}
|
|
|
|
pasBandParams[iBand].sgnd = (eDataType == GDT_Int16 || eDataType == GDT_Int32);
|
|
pasBandParams[iBand].prec = nBits;
|
|
|
|
const char* pszNBits = poSrcDS->GetRasterBand(iBand+1)->GetMetadataItem(
|
|
"NBITS", "IMAGE_STRUCTURE");
|
|
/* Recommendation 38 In the case of an opacity channel, the bit depth should be 1-bit. */
|
|
if( iBand == nAlphaBandIndex &&
|
|
((pszNBits != NULL && EQUAL(pszNBits, "1")) ||
|
|
CSLFetchBoolean(papszOptions, "1BIT_ALPHA", bInspireTG)) )
|
|
{
|
|
if( iBand != nBands - 1 && nBits != 1 )
|
|
{
|
|
/* Might be a bug in openjpeg, but it seems that if the alpha */
|
|
/* band is the first one, it would select 1-bit for all channels... */
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Cannot output 1-bit alpha channel if it is not the last one");
|
|
}
|
|
else
|
|
{
|
|
CPLDebug("OPENJPEG", "Using 1-bit alpha channel");
|
|
pasBandParams[iBand].sgnd = 0;
|
|
pasBandParams[iBand].prec = 1;
|
|
bSamePrecision = FALSE;
|
|
b1BitAlpha = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bInspireTG && nAlphaBandIndex >= 0 && !b1BitAlpha )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"INSPIRE_TG=YES recommends 1BIT_ALPHA=YES (Recommendation 38)");
|
|
}
|
|
|
|
/* Always ask OpenJPEG to do codestream only. We will take care */
|
|
/* of JP2 boxes */
|
|
opj_codec_t* pCodec = opj_create_compress(OPJ_CODEC_J2K);
|
|
if (pCodec == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"opj_create_compress() failed");
|
|
CPLFree(pasBandParams);
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
|
|
opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback,NULL);
|
|
opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback,NULL);
|
|
opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback,NULL);
|
|
|
|
OPJ_COLOR_SPACE eColorSpace = OPJ_CLRSPC_GRAY;
|
|
|
|
if( bYCBCR420 )
|
|
{
|
|
eColorSpace = OPJ_CLRSPC_SYCC;
|
|
}
|
|
else if( (nBands == 3 || nBands == 4) &&
|
|
nRedBandIndex >= 0 && nGreenBandIndex >= 0 && nBlueBandIndex >= 0 )
|
|
{
|
|
eColorSpace = OPJ_CLRSPC_SRGB;
|
|
}
|
|
else if (poCT != NULL)
|
|
{
|
|
eColorSpace = OPJ_CLRSPC_SRGB;
|
|
}
|
|
|
|
opj_image_t* psImage = opj_image_tile_create(nBands,pasBandParams,
|
|
eColorSpace);
|
|
|
|
if (psImage == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"opj_image_tile_create() failed");
|
|
opj_destroy_codec(pCodec);
|
|
CPLFree(pasBandParams);
|
|
pasBandParams = NULL;
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
|
|
psImage->x0 = 0;
|
|
psImage->y0 = 0;
|
|
psImage->x1 = nXSize;
|
|
psImage->y1 = nYSize;
|
|
psImage->color_space = eColorSpace;
|
|
psImage->numcomps = nBands;
|
|
|
|
if (!opj_setup_encoder(pCodec,¶meters,psImage))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"opj_setup_encoder() failed");
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
CPLFree(pasBandParams);
|
|
pasBandParams = NULL;
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create the dataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
const char* pszAccess = EQUALN(pszFilename, "/vsisubfile/", 12) ? "r+b" : "w+b";
|
|
VSILFILE* fp = VSIFOpenL(pszFilename, pszAccess);
|
|
if (fp == NULL)
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined, "Cannot create file");
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
CPLFree(pasBandParams);
|
|
pasBandParams = NULL;
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Add JP2 boxes. */
|
|
/* -------------------------------------------------------------------- */
|
|
vsi_l_offset nStartJP2C = 0;
|
|
int bUseXLBoxes = FALSE;
|
|
|
|
if( eCodecFormat == OPJ_CODEC_JP2 )
|
|
{
|
|
GDALJP2Box jPBox(fp);
|
|
jPBox.SetType("jP ");
|
|
jPBox.AppendWritableData(4, "\x0D\x0A\x87\x0A");
|
|
WriteBox(fp, &jPBox);
|
|
|
|
GDALJP2Box ftypBox(fp);
|
|
ftypBox.SetType("ftyp");
|
|
ftypBox.AppendWritableData(4, "jp2 "); /* Branding */
|
|
ftypBox.AppendUInt32(0); /* minimum version */
|
|
ftypBox.AppendWritableData(4, "jp2 "); /* Compatibility list: first value */
|
|
|
|
int bJPXOption = CSLFetchBoolean( papszOptions, "JPX", TRUE );
|
|
if( bInspireTG && poGMLJP2Box != NULL && !bJPXOption )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"INSPIRE_TG=YES implies following GMLJP2 specification which "
|
|
"recommends advertize reader requirement 67 feature, and thus JPX capability");
|
|
}
|
|
else if( poGMLJP2Box != NULL && bJPXOption )
|
|
{
|
|
/* GMLJP2 uses lbl and asoc boxes, which are JPEG2000 Part II spec */
|
|
/* advertizing jpx is required per 8.1 of 05-047r3 GMLJP2 */
|
|
ftypBox.AppendWritableData(4, "jpx "); /* Compatibility list: second value */
|
|
}
|
|
WriteBox(fp, &ftypBox);
|
|
|
|
int bIPR = poSrcDS->GetMetadata("xml:IPR") != NULL &&
|
|
CSLFetchBoolean(papszOptions, "WRITE_METADATA", FALSE);
|
|
|
|
/* Reader requirement box */
|
|
if( poGMLJP2Box != NULL && bJPXOption )
|
|
{
|
|
GDALJP2Box rreqBox(fp);
|
|
rreqBox.SetType("rreq");
|
|
rreqBox.AppendUInt8(1); /* ML = 1 byte for mask length */
|
|
|
|
rreqBox.AppendUInt8(0x80 | 0x40 | (bIPR ? 0x20 : 0)); /* FUAM */
|
|
rreqBox.AppendUInt8(0x80); /* DCM */
|
|
|
|
rreqBox.AppendUInt16(2 + bIPR); /* NSF: Number of standard features */
|
|
|
|
rreqBox.AppendUInt16((bProfile1) ? 4 : 5); /* SF0 : PROFILE 1 or PROFILE 2 */
|
|
rreqBox.AppendUInt8(0x80); /* SM0 */
|
|
|
|
rreqBox.AppendUInt16(67); /* SF1 : GMLJP2 box */
|
|
rreqBox.AppendUInt8(0x40); /* SM1 */
|
|
|
|
if( bIPR )
|
|
{
|
|
rreqBox.AppendUInt16(35); /* SF2 : IPR metadata */
|
|
rreqBox.AppendUInt8(0x20); /* SM2 */
|
|
}
|
|
rreqBox.AppendUInt16(0); /* NVF */
|
|
WriteBox(fp, &rreqBox);
|
|
}
|
|
|
|
GDALJP2Box ihdrBox(fp);
|
|
ihdrBox.SetType("ihdr");
|
|
ihdrBox.AppendUInt32(nYSize);
|
|
ihdrBox.AppendUInt32(nXSize);
|
|
ihdrBox.AppendUInt16(nBands);
|
|
GByte BPC;
|
|
if( bSamePrecision )
|
|
BPC = (pasBandParams[0].prec-1) | (pasBandParams[0].sgnd << 7);
|
|
else
|
|
BPC = 255;
|
|
ihdrBox.AppendUInt8(BPC);
|
|
ihdrBox.AppendUInt8(7); /* C=Compression type: fixed value */
|
|
ihdrBox.AppendUInt8(0); /* UnkC: 0= colourspace of the image is known */
|
|
/*and correctly specified in the Colourspace Specification boxes within the file */
|
|
ihdrBox.AppendUInt8(bIPR); /* IPR: 0=no intellectual property, 1=IPR box */
|
|
|
|
GDALJP2Box bpccBox(fp);
|
|
if( !bSamePrecision )
|
|
{
|
|
bpccBox.SetType("bpcc");
|
|
for(int i=0;i<nBands;i++)
|
|
bpccBox.AppendUInt8((pasBandParams[i].prec-1) | (pasBandParams[i].sgnd << 7));
|
|
}
|
|
|
|
GDALJP2Box colrBox(fp);
|
|
colrBox.SetType("colr");
|
|
colrBox.AppendUInt8(1); /* METHOD: 1=Enumerated Colourspace */
|
|
colrBox.AppendUInt8(0); /* PREC: Precedence. 0=(field reserved for ISO use) */
|
|
colrBox.AppendUInt8(0); /* APPROX: Colourspace approximation. */
|
|
GUInt32 enumcs = 16;
|
|
if( eColorSpace == OPJ_CLRSPC_SRGB )
|
|
enumcs = 16;
|
|
else if( eColorSpace == OPJ_CLRSPC_GRAY )
|
|
enumcs = 17;
|
|
else if( eColorSpace == OPJ_CLRSPC_SYCC )
|
|
enumcs = 18;
|
|
colrBox.AppendUInt32(enumcs); /* EnumCS: Enumerated colourspace */
|
|
|
|
GDALJP2Box pclrBox(fp);
|
|
GDALJP2Box cmapBox(fp);
|
|
int nCTComponentCount = 0;
|
|
if (poCT != NULL)
|
|
{
|
|
pclrBox.SetType("pclr");
|
|
int nEntries = MIN(256, poCT->GetColorEntryCount());
|
|
nCTComponentCount = atoi(CSLFetchNameValueDef(papszOptions, "CT_COMPONENTS", "0"));
|
|
if( bInspireTG )
|
|
{
|
|
if( nCTComponentCount != 0 && nCTComponentCount != 3 )
|
|
CPLError(CE_Warning, CPLE_AppDefined, "Inspire TG mandates 3 components for color table");
|
|
else
|
|
nCTComponentCount = 3;
|
|
}
|
|
else if( nCTComponentCount != 3 && nCTComponentCount != 4 )
|
|
{
|
|
nCTComponentCount = 3;
|
|
for(int i=0;i<nEntries;i++)
|
|
{
|
|
const GDALColorEntry* psEntry = poCT->GetColorEntry(i);
|
|
if( psEntry->c4 != 255 )
|
|
{
|
|
CPLDebug("OPENJPEG", "Color table has at least one non-opaque value. "
|
|
"This may cause compatibility problems with some readers. "
|
|
"In which case use CT_COMPONENTS=3 creation option");
|
|
nCTComponentCount = 4;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
nRedBandIndex = 0;
|
|
nGreenBandIndex = 1;
|
|
nBlueBandIndex = 2;
|
|
nAlphaBandIndex = (nCTComponentCount == 4) ? 3 : -1;
|
|
|
|
pclrBox.AppendUInt16(nEntries);
|
|
pclrBox.AppendUInt8(nCTComponentCount); /* NPC: Number of components */
|
|
for(int i=0;i<nCTComponentCount;i++)
|
|
{
|
|
pclrBox.AppendUInt8(7); /* Bi: unsigned 8 bits */
|
|
}
|
|
for(int i=0;i<nEntries;i++)
|
|
{
|
|
const GDALColorEntry* psEntry = poCT->GetColorEntry(i);
|
|
pclrBox.AppendUInt8((GByte)psEntry->c1);
|
|
pclrBox.AppendUInt8((GByte)psEntry->c2);
|
|
pclrBox.AppendUInt8((GByte)psEntry->c3);
|
|
if( nCTComponentCount == 4 )
|
|
pclrBox.AppendUInt8((GByte)psEntry->c4);
|
|
}
|
|
|
|
cmapBox.SetType("cmap");
|
|
for(int i=0;i<nCTComponentCount;i++)
|
|
{
|
|
cmapBox.AppendUInt16(0); /* CMPi: code stream component index */
|
|
cmapBox.AppendUInt8(1); /* MYTPi: 1=palette mapping */
|
|
cmapBox.AppendUInt8(i); /* PCOLi: index component from the map */
|
|
}
|
|
}
|
|
|
|
GDALJP2Box cdefBox(fp);
|
|
if( ((nBands == 3 || nBands == 4) &&
|
|
(eColorSpace == OPJ_CLRSPC_SRGB || eColorSpace == OPJ_CLRSPC_SYCC) &&
|
|
(nRedBandIndex != 0 || nGreenBandIndex != 1 || nBlueBandIndex != 2)) ||
|
|
nAlphaBandIndex >= 0)
|
|
{
|
|
cdefBox.SetType("cdef");
|
|
int nComponents = (nCTComponentCount == 4) ? 4 : nBands;
|
|
cdefBox.AppendUInt16(nComponents);
|
|
for(int i=0;i<nComponents;i++)
|
|
{
|
|
cdefBox.AppendUInt16(i); /* Component number */
|
|
if( i != nAlphaBandIndex )
|
|
{
|
|
cdefBox.AppendUInt16(0); /* Signification: This channel is the colour image data for the associated colour */
|
|
if( eColorSpace == OPJ_CLRSPC_GRAY && nComponents == 2)
|
|
cdefBox.AppendUInt16(1); /* Colour of the component: associated with a particular colour */
|
|
else if ((eColorSpace == OPJ_CLRSPC_SRGB ||
|
|
eColorSpace == OPJ_CLRSPC_SYCC) &&
|
|
(nComponents == 3 || nComponents == 4) )
|
|
{
|
|
if( i == nRedBandIndex )
|
|
cdefBox.AppendUInt16(1);
|
|
else if( i == nGreenBandIndex )
|
|
cdefBox.AppendUInt16(2);
|
|
else if( i == nBlueBandIndex )
|
|
cdefBox.AppendUInt16(3);
|
|
else
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Could not associate band %d with a red/green/blue channel",
|
|
i+1);
|
|
cdefBox.AppendUInt16(65535);
|
|
}
|
|
}
|
|
else
|
|
cdefBox.AppendUInt16(65535); /* Colour of the component: not associated with any particular colour */
|
|
}
|
|
else
|
|
{
|
|
cdefBox.AppendUInt16(1); /* Signification: Non pre-multiplied alpha */
|
|
cdefBox.AppendUInt16(0); /* Colour of the component: This channel is associated as the image as a whole */
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add res box if needed
|
|
double dfXRes = 0, dfYRes = 0;
|
|
int nResUnit = 0;
|
|
GDALJP2Box* poRes = NULL;
|
|
if( poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION") != NULL
|
|
&& poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION") != NULL
|
|
&& poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT") != NULL )
|
|
{
|
|
dfXRes =
|
|
CPLAtof(poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION"));
|
|
dfYRes =
|
|
CPLAtof(poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION"));
|
|
nResUnit = atoi(poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT"));
|
|
#define PIXELS_PER_INCH 2
|
|
#define PIXELS_PER_CM 3
|
|
|
|
if( nResUnit == PIXELS_PER_INCH )
|
|
{
|
|
// convert pixels per inch to pixels per cm.
|
|
dfXRes = dfXRes * 39.37 / 100.0;
|
|
dfYRes = dfYRes * 39.37 / 100.0;
|
|
nResUnit = PIXELS_PER_CM;
|
|
}
|
|
|
|
if( nResUnit == PIXELS_PER_CM &&
|
|
dfXRes > 0 && dfYRes > 0 &&
|
|
dfXRes < 65535 && dfYRes < 65535 )
|
|
{
|
|
/* Format a resd box and embed it inside a res box */
|
|
GDALJP2Box oResd;
|
|
oResd.SetType("resd");
|
|
|
|
int nYDenom = 1;
|
|
while (nYDenom < 32767 && dfYRes < 32767)
|
|
{
|
|
dfYRes *= 2;
|
|
nYDenom *= 2;
|
|
}
|
|
int nXDenom = 1;
|
|
while (nXDenom < 32767 && dfXRes < 32767)
|
|
{
|
|
dfXRes *= 2;
|
|
nXDenom *= 2;
|
|
}
|
|
|
|
oResd.AppendUInt16((GUInt16)dfYRes);
|
|
oResd.AppendUInt16((GUInt16)nYDenom);
|
|
oResd.AppendUInt16((GUInt16)dfXRes);
|
|
oResd.AppendUInt16((GUInt16)nXDenom);
|
|
oResd.AppendUInt8(2); /* vertical exponent */
|
|
oResd.AppendUInt8(2); /* horizontal exponent */
|
|
|
|
GDALJP2Box* poResd = &oResd;
|
|
poRes = GDALJP2Box::CreateAsocBox( 1, &poResd );
|
|
poRes->SetType("res ");
|
|
}
|
|
}
|
|
|
|
/* Build and write jp2h super box now */
|
|
GDALJP2Box* apoBoxes[7];
|
|
int nBoxes = 1;
|
|
apoBoxes[0] = &ihdrBox;
|
|
if( bpccBox.GetDataLength() )
|
|
apoBoxes[nBoxes++] = &bpccBox;
|
|
apoBoxes[nBoxes++] = &colrBox;
|
|
if( pclrBox.GetDataLength() )
|
|
apoBoxes[nBoxes++] = &pclrBox;
|
|
if( cmapBox.GetDataLength() )
|
|
apoBoxes[nBoxes++] = &cmapBox;
|
|
if( cdefBox.GetDataLength() )
|
|
apoBoxes[nBoxes++] = &cdefBox;
|
|
if( poRes )
|
|
apoBoxes[nBoxes++] = poRes;
|
|
GDALJP2Box* psJP2HBox = GDALJP2Box::CreateSuperBox( "jp2h",
|
|
nBoxes,
|
|
apoBoxes );
|
|
WriteBox(fp, psJP2HBox);
|
|
delete psJP2HBox;
|
|
delete poRes;
|
|
|
|
if( !bGeoBoxesAfter )
|
|
{
|
|
if( bGeoJP2Option && bGeoreferencingCompatOfGeoJP2 )
|
|
{
|
|
GDALJP2Box* poBox = oJP2MD.CreateJP2GeoTIFF();
|
|
WriteBox(fp, poBox);
|
|
delete poBox;
|
|
}
|
|
|
|
if( CSLFetchBoolean(papszOptions, "WRITE_METADATA", FALSE) &&
|
|
!CSLFetchBoolean(papszOptions, "MAIN_MD_DOMAIN_ONLY", FALSE) )
|
|
{
|
|
WriteXMPBox(fp, poSrcDS, papszOptions);
|
|
}
|
|
|
|
if( CSLFetchBoolean(papszOptions, "WRITE_METADATA", FALSE) )
|
|
{
|
|
if( !CSLFetchBoolean(papszOptions, "MAIN_MD_DOMAIN_ONLY", FALSE) )
|
|
WriteXMLBoxes(fp, poSrcDS, papszOptions);
|
|
WriteGDALMetadataBox(fp, poSrcDS, papszOptions);
|
|
}
|
|
|
|
if( poGMLJP2Box != NULL )
|
|
{
|
|
WriteBox(fp, poGMLJP2Box);
|
|
}
|
|
}
|
|
}
|
|
CPLFree(pasBandParams);
|
|
pasBandParams = NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Try lossless reuse of an existing JPEG2000 codestream */
|
|
/* -------------------------------------------------------------------- */
|
|
vsi_l_offset nCodeStreamLength = 0;
|
|
vsi_l_offset nCodeStreamStart = 0;
|
|
VSILFILE* fpSrc = NULL;
|
|
if( CSLFetchBoolean(papszOptions, "USE_SRC_CODESTREAM", FALSE) )
|
|
{
|
|
CPLString osSrcFilename( poSrcDS->GetDescription() );
|
|
if( poSrcDS->GetDriver() != NULL &&
|
|
poSrcDS->GetDriver() == GDALGetDriverByName("VRT") )
|
|
{
|
|
VRTDataset* poVRTDS = (VRTDataset* )poSrcDS;
|
|
GDALDataset* poSimpleSourceDS = poVRTDS->GetSingleSimpleSource();
|
|
if( poSimpleSourceDS )
|
|
osSrcFilename = poSimpleSourceDS->GetDescription();
|
|
}
|
|
|
|
fpSrc = VSIFOpenL( osSrcFilename, "rb" );
|
|
if( fpSrc )
|
|
{
|
|
nCodeStreamStart = JP2OpenJPEGFindCodeStream(fpSrc,
|
|
&nCodeStreamLength);
|
|
}
|
|
if( nCodeStreamLength == 0 )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"USE_SRC_CODESTREAM=YES specified, but no codestream found");
|
|
}
|
|
}
|
|
|
|
if( eCodecFormat == OPJ_CODEC_JP2 )
|
|
{
|
|
// Start codestream box
|
|
nStartJP2C = VSIFTellL(fp);
|
|
if( nCodeStreamLength )
|
|
bUseXLBoxes = ((vsi_l_offset)(GUInt32)nCodeStreamLength != nCodeStreamLength);
|
|
else
|
|
bUseXLBoxes = CSLFetchBoolean(papszOptions, "JP2C_XLBOX", FALSE) || /* For debugging */
|
|
(GIntBig)nXSize * nYSize * nBands * nDataTypeSize / adfRates[adfRates.size()-1] > 4e9;
|
|
GUInt32 nLBox = (bUseXLBoxes) ? 1 : 0;
|
|
CPL_MSBPTR32(&nLBox);
|
|
VSIFWriteL(&nLBox, 1, 4, fp);
|
|
VSIFWriteL("jp2c", 1, 4, fp);
|
|
if( bUseXLBoxes )
|
|
{
|
|
GUIntBig nXLBox = 0;
|
|
VSIFWriteL(&nXLBox, 1, 8, fp);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do lossless reuse of an existing JPEG2000 codestream */
|
|
/* -------------------------------------------------------------------- */
|
|
if( fpSrc )
|
|
{
|
|
const char* apszIgnoredOptions[] = {
|
|
"BLOCKXSIZE", "BLOCKYSIZE", "QUALITY", "REVERSIBLE",
|
|
"RESOLUTIONS", "PROGRESSION", "SOP", "EPH",
|
|
"YCBCR420", "YCC", "NBITS", "1BIT_ALPHA", "PRECINCTS",
|
|
"TILEPARTS", "CODEBLOCK_WIDTH", "CODEBLOCK_HEIGHT", NULL };
|
|
for( int i = 0; apszIgnoredOptions[i]; i ++)
|
|
{
|
|
if( CSLFetchNameValue(papszOptions, apszIgnoredOptions[i]) )
|
|
{
|
|
CPLError(CE_Warning, CPLE_NotSupported,
|
|
"Option %s ignored when USE_SRC_CODESTREAM=YES",
|
|
apszIgnoredOptions[i]);
|
|
}
|
|
}
|
|
GByte abyBuffer[4096];
|
|
VSIFSeekL( fpSrc, nCodeStreamStart, SEEK_SET );
|
|
vsi_l_offset nRead = 0;
|
|
while( nRead < nCodeStreamLength )
|
|
{
|
|
int nToRead = ( nCodeStreamLength-nRead > 4096 ) ? 4049 :
|
|
(int)(nCodeStreamLength-nRead);
|
|
if( (int)VSIFReadL(abyBuffer, 1, nToRead, fpSrc) != nToRead )
|
|
{
|
|
VSIFCloseL(fp);
|
|
VSIFCloseL(fpSrc);
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
if( nRead == 0 && (pszProfile || bInspireTG) &&
|
|
abyBuffer[2] == 0xFF && abyBuffer[3] == 0x51 )
|
|
{
|
|
if( EQUAL(pszProfile, "UNRESTRICTED") )
|
|
{
|
|
abyBuffer[6] = 0;
|
|
abyBuffer[7] = 0;
|
|
}
|
|
else if( EQUAL(pszProfile, "PROFILE_1") || bInspireTG )
|
|
{
|
|
// TODO: ultimately we should check that we can really set Profile 1
|
|
abyBuffer[6] = 0;
|
|
abyBuffer[7] = 2;
|
|
}
|
|
}
|
|
if( (int)VSIFWriteL(abyBuffer, 1, nToRead, fp) != nToRead ||
|
|
!pfnProgress( (nRead + nToRead) * 1.0 / nCodeStreamLength,
|
|
NULL, pProgressData ) )
|
|
{
|
|
VSIFCloseL(fp);
|
|
VSIFCloseL(fpSrc);
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
nRead += nToRead;
|
|
}
|
|
|
|
VSIFCloseL(fpSrc);
|
|
}
|
|
else
|
|
{
|
|
opj_stream_t * pStream;
|
|
JP2OpenJPEGFile sJP2OpenJPEGFile;
|
|
sJP2OpenJPEGFile.fp = fp;
|
|
sJP2OpenJPEGFile.nBaseOffset = VSIFTellL(fp);
|
|
pStream = opj_stream_create(1024*1024, FALSE);
|
|
opj_stream_set_write_function(pStream, JP2OpenJPEGDataset_Write);
|
|
opj_stream_set_seek_function(pStream, JP2OpenJPEGDataset_Seek);
|
|
opj_stream_set_skip_function(pStream, JP2OpenJPEGDataset_Skip);
|
|
#if defined(OPENJPEG_VERSION) && OPENJPEG_VERSION >= 20100
|
|
opj_stream_set_user_data(pStream, &sJP2OpenJPEGFile, NULL);
|
|
#else
|
|
opj_stream_set_user_data(pStream, &sJP2OpenJPEGFile);
|
|
#endif
|
|
|
|
if (!opj_start_compress(pCodec,psImage,pStream))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"opj_start_compress() failed");
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
VSIFCloseL(fp);
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
|
|
int nTilesX = (nXSize + nBlockXSize - 1) / nBlockXSize;
|
|
int nTilesY = (nYSize + nBlockYSize - 1) / nBlockYSize;
|
|
|
|
GUIntBig nTileSize = (GUIntBig)nBlockXSize * nBlockYSize * nBands * nDataTypeSize;
|
|
GByte* pTempBuffer;
|
|
if( nTileSize != (GUIntBig)(GUInt32)nTileSize )
|
|
{
|
|
CPLError(CE_Failure, CPLE_NotSupported, "Tile size exceeds 4GB");
|
|
pTempBuffer = NULL;
|
|
}
|
|
else
|
|
{
|
|
pTempBuffer = (GByte*)VSIMalloc((size_t)nTileSize);
|
|
}
|
|
if (pTempBuffer == NULL)
|
|
{
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
VSIFCloseL(fp);
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
|
|
GByte* pYUV420Buffer = NULL;
|
|
if (bYCBCR420)
|
|
{
|
|
pYUV420Buffer =(GByte*)VSIMalloc(3 * nBlockXSize * nBlockYSize / 2 +
|
|
((nBands == 4) ? nBlockXSize * nBlockYSize : 0));
|
|
if (pYUV420Buffer == NULL)
|
|
{
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
CPLFree(pTempBuffer);
|
|
VSIFCloseL(fp);
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Iterate over the tiles */
|
|
/* -------------------------------------------------------------------- */
|
|
pfnProgress( 0.0, NULL, pProgressData );
|
|
|
|
CPLErr eErr = CE_None;
|
|
int nBlockXOff, nBlockYOff;
|
|
int iTile = 0;
|
|
for(nBlockYOff=0;eErr == CE_None && nBlockYOff<nTilesY;nBlockYOff++)
|
|
{
|
|
for(nBlockXOff=0;eErr == CE_None && nBlockXOff<nTilesX;nBlockXOff++)
|
|
{
|
|
int nWidthToRead = MIN(nBlockXSize, nXSize - nBlockXOff * nBlockXSize);
|
|
int nHeightToRead = MIN(nBlockYSize, nYSize - nBlockYOff * nBlockYSize);
|
|
eErr = poSrcDS->RasterIO(GF_Read,
|
|
nBlockXOff * nBlockXSize,
|
|
nBlockYOff * nBlockYSize,
|
|
nWidthToRead, nHeightToRead,
|
|
pTempBuffer, nWidthToRead, nHeightToRead,
|
|
eDataType,
|
|
nBands, NULL,
|
|
0,0,0,NULL);
|
|
if( b1BitAlpha )
|
|
{
|
|
for(int i=0;i<nWidthToRead*nHeightToRead;i++)
|
|
{
|
|
if( pTempBuffer[nAlphaBandIndex*nWidthToRead*nHeightToRead+i] )
|
|
pTempBuffer[nAlphaBandIndex*nWidthToRead*nHeightToRead+i] = 1;
|
|
else
|
|
pTempBuffer[nAlphaBandIndex*nWidthToRead*nHeightToRead+i] = 0;
|
|
}
|
|
}
|
|
if (eErr == CE_None)
|
|
{
|
|
if (bYCBCR420)
|
|
{
|
|
int j, i;
|
|
for(j=0;j<nHeightToRead;j++)
|
|
{
|
|
for(i=0;i<nWidthToRead;i++)
|
|
{
|
|
int R = pTempBuffer[j*nWidthToRead+i];
|
|
int G = pTempBuffer[nHeightToRead*nWidthToRead + j*nWidthToRead+i];
|
|
int B = pTempBuffer[2*nHeightToRead*nWidthToRead + j*nWidthToRead+i];
|
|
int Y = (int) (0.299 * R + 0.587 * G + 0.114 * B);
|
|
int Cb = CLAMP_0_255((int) (-0.1687 * R - 0.3313 * G + 0.5 * B + 128));
|
|
int Cr = CLAMP_0_255((int) (0.5 * R - 0.4187 * G - 0.0813 * B + 128));
|
|
pYUV420Buffer[j*nWidthToRead+i] = (GByte) Y;
|
|
pYUV420Buffer[nHeightToRead * nWidthToRead + ((j/2) * ((nWidthToRead)/2) + i/2) ] = (GByte) Cb;
|
|
pYUV420Buffer[5 * nHeightToRead * nWidthToRead / 4 + ((j/2) * ((nWidthToRead)/2) + i/2) ] = (GByte) Cr;
|
|
if( nBands == 4 )
|
|
{
|
|
pYUV420Buffer[3 * nHeightToRead * nWidthToRead / 2 + j*nWidthToRead+i ] =
|
|
(GByte) pTempBuffer[3*nHeightToRead*nWidthToRead + j*nWidthToRead+i];
|
|
}
|
|
}
|
|
}
|
|
|
|
int nBytesToWrite = 3 * nWidthToRead * nHeightToRead / 2;
|
|
if (nBands == 4)
|
|
nBytesToWrite += nBlockXSize * nBlockYSize;
|
|
|
|
if (!opj_write_tile(pCodec,
|
|
iTile,
|
|
pYUV420Buffer,
|
|
nBytesToWrite,
|
|
pStream))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"opj_write_tile() failed");
|
|
eErr = CE_Failure;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!opj_write_tile(pCodec,
|
|
iTile,
|
|
pTempBuffer,
|
|
nWidthToRead * nHeightToRead * nBands * nDataTypeSize,
|
|
pStream))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"opj_write_tile() failed");
|
|
eErr = CE_Failure;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !pfnProgress( (iTile + 1) * 1.0 / (nTilesX * nTilesY), NULL, pProgressData ) )
|
|
eErr = CE_Failure;
|
|
|
|
iTile ++;
|
|
}
|
|
}
|
|
|
|
VSIFree(pTempBuffer);
|
|
VSIFree(pYUV420Buffer);
|
|
|
|
if (eErr != CE_None)
|
|
{
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
VSIFCloseL(fp);
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
|
|
if (!opj_end_compress(pCodec,pStream))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"opj_end_compress() failed");
|
|
opj_stream_destroy(pStream);
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
VSIFCloseL(fp);
|
|
delete poGMLJP2Box;
|
|
return NULL;
|
|
}
|
|
opj_stream_destroy(pStream);
|
|
}
|
|
|
|
opj_image_destroy(psImage);
|
|
opj_destroy_codec(pCodec);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Patch JP2C box length and add trailing JP2 boxes */
|
|
/* -------------------------------------------------------------------- */
|
|
if( eCodecFormat == OPJ_CODEC_JP2 &&
|
|
!CSLFetchBoolean(papszOptions, "JP2C_LENGTH_ZERO", FALSE) /* debug option */ )
|
|
{
|
|
vsi_l_offset nEndJP2C = VSIFTellL(fp);
|
|
GUIntBig nBoxSize = nEndJP2C -nStartJP2C;
|
|
if( bUseXLBoxes )
|
|
{
|
|
VSIFSeekL(fp, nStartJP2C + 8, SEEK_SET);
|
|
CPL_MSBPTR64(&nBoxSize);
|
|
VSIFWriteL(&nBoxSize, 8, 1, fp);
|
|
}
|
|
else
|
|
{
|
|
GUInt32 nBoxSize32 = (GUInt32)nBoxSize;
|
|
if( (vsi_l_offset)nBoxSize32 != nBoxSize )
|
|
{
|
|
/* Shouldn't happen hopefully */
|
|
if( (bGeoreferencingCompatOfGeoJP2 || poGMLJP2Box) && bGeoBoxesAfter )
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"Cannot write GMLJP2/GeoJP2 boxes as codestream is unexpectedly > 4GB");
|
|
bGeoreferencingCompatOfGeoJP2 = FALSE;
|
|
delete poGMLJP2Box;
|
|
poGMLJP2Box = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VSIFSeekL(fp, nStartJP2C, SEEK_SET);
|
|
CPL_MSBPTR32(&nBoxSize32);
|
|
VSIFWriteL(&nBoxSize32, 4, 1, fp);
|
|
}
|
|
}
|
|
VSIFSeekL(fp, 0, SEEK_END);
|
|
|
|
if( CSLFetchBoolean(papszOptions, "WRITE_METADATA", FALSE) )
|
|
{
|
|
WriteIPRBox(fp, poSrcDS, papszOptions);
|
|
}
|
|
|
|
if( bGeoBoxesAfter )
|
|
{
|
|
if( poGMLJP2Box != NULL )
|
|
{
|
|
WriteBox(fp, poGMLJP2Box);
|
|
}
|
|
|
|
if( CSLFetchBoolean(papszOptions, "WRITE_METADATA", FALSE) )
|
|
{
|
|
if( !CSLFetchBoolean(papszOptions, "MAIN_MD_DOMAIN_ONLY", FALSE) )
|
|
WriteXMLBoxes(fp, poSrcDS, papszOptions);
|
|
WriteGDALMetadataBox(fp, poSrcDS, papszOptions);
|
|
}
|
|
|
|
if( bGeoJP2Option && bGeoreferencingCompatOfGeoJP2 )
|
|
{
|
|
GDALJP2Box* poBox = oJP2MD.CreateJP2GeoTIFF();
|
|
WriteBox(fp, poBox);
|
|
delete poBox;
|
|
}
|
|
|
|
if( CSLFetchBoolean(papszOptions, "WRITE_METADATA", FALSE) &&
|
|
!CSLFetchBoolean(papszOptions, "MAIN_MD_DOMAIN_ONLY", FALSE) )
|
|
{
|
|
WriteXMPBox(fp, poSrcDS, papszOptions);
|
|
}
|
|
}
|
|
}
|
|
|
|
VSIFCloseL(fp);
|
|
delete poGMLJP2Box;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Re-open dataset, and copy any auxiliary pam information. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
|
|
JP2OpenJPEGDataset *poDS = (JP2OpenJPEGDataset*) JP2OpenJPEGDataset::Open(&oOpenInfo);
|
|
|
|
if( poDS )
|
|
{
|
|
poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT & (~GCIF_METADATA) );
|
|
|
|
/* Only write relevant metadata to PAM, and if needed */
|
|
if( !CSLFetchBoolean(papszOptions, "WRITE_METADATA", FALSE) )
|
|
{
|
|
char** papszSrcMD = CSLDuplicate(poSrcDS->GetMetadata());
|
|
papszSrcMD = CSLSetNameValue(papszSrcMD, GDALMD_AREA_OR_POINT, NULL);
|
|
papszSrcMD = CSLSetNameValue(papszSrcMD, "Corder", NULL);
|
|
for(char** papszSrcMDIter = papszSrcMD;
|
|
papszSrcMDIter && *papszSrcMDIter; )
|
|
{
|
|
/* Remove entries like KEY= (without value) */
|
|
if( (*papszSrcMDIter)[0] &&
|
|
(*papszSrcMDIter)[strlen((*papszSrcMDIter))-1] == '=' )
|
|
{
|
|
CPLFree(*papszSrcMDIter);
|
|
memmove(papszSrcMDIter, papszSrcMDIter + 1,
|
|
sizeof(char*) * (CSLCount(papszSrcMDIter + 1) + 1));
|
|
}
|
|
else
|
|
++papszSrcMDIter;
|
|
}
|
|
char** papszMD = CSLDuplicate(poDS->GetMetadata());
|
|
papszMD = CSLSetNameValue(papszMD, GDALMD_AREA_OR_POINT, NULL);
|
|
if( papszSrcMD && papszSrcMD[0] != NULL &&
|
|
CSLCount(papszSrcMD) != CSLCount(papszMD) )
|
|
{
|
|
poDS->SetMetadata(papszSrcMD);
|
|
}
|
|
CSLDestroy(papszSrcMD);
|
|
CSLDestroy(papszMD);
|
|
}
|
|
}
|
|
|
|
return poDS;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GDALRegister_JP2OpenJPEG() */
|
|
/************************************************************************/
|
|
|
|
void GDALRegister_JP2OpenJPEG()
|
|
|
|
{
|
|
GDALDriver *poDriver;
|
|
|
|
if (! GDAL_CHECK_VERSION("JP2OpenJPEG driver"))
|
|
return;
|
|
|
|
if( GDALGetDriverByName( "JP2OpenJPEG" ) == NULL )
|
|
{
|
|
poDriver = new GDALDriver();
|
|
|
|
poDriver->SetDescription( "JP2OpenJPEG" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VECTOR, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
|
|
"JPEG-2000 driver based on OpenJPEG library" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
|
|
"frmt_jp2openjpeg.html" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/jp2" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "jp2" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSIONS, "jp2 j2k" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
|
|
"Byte Int16 UInt16 Int32 UInt32" );
|
|
|
|
poDriver->SetMetadataItem( GDAL_DMD_OPENOPTIONLIST,
|
|
"<OpenOptionList>"
|
|
" <Option name='1BIT_ALPHA_PROMOTION' type='boolean' description='Whether a 1-bit alpha channel should be promoted to 8-bit' default='YES'/>"
|
|
" <Option name='OPEN_REMOTE_GML' type='boolean' description='Whether to load remote vector layers referenced by a link in a GMLJP2 v2 box' default='NO'/>"
|
|
"</OpenOptionList>" );
|
|
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
|
|
"<CreationOptionList>"
|
|
" <Option name='CODEC' type='string-select' default='according to file extension. If unknown, default to J2K'>"
|
|
" <Value>JP2</Value>"
|
|
" <Value>J2K</Value>"
|
|
" </Option>"
|
|
" <Option name='GeoJP2' type='boolean' description='Whether to emit a GeoJP2 box' default='YES'/>"
|
|
" <Option name='GMLJP2' type='boolean' description='Whether to emit a GMLJP2 v1 box' default='YES'/>"
|
|
" <Option name='GMLJP2V2_DEF' type='string' description='Definition file to describe how a GMLJP2 v2 box should be generated. If set to YES, a minimal instance will be created'/>"
|
|
" <Option name='QUALITY' type='string' description='Single quality value or comma separated list of increasing quality values for several layers, each in the 0-100 range' default='25'/>"
|
|
" <Option name='REVERSIBLE' type='boolean' description='True if the compression is reversible' default='false'/>"
|
|
" <Option name='RESOLUTIONS' type='int' description='Number of resolutions.' min='1' max='30'/>"
|
|
" <Option name='BLOCKXSIZE' type='int' description='Tile Width' default='1024'/>"
|
|
" <Option name='BLOCKYSIZE' type='int' description='Tile Height' default='1024'/>"
|
|
" <Option name='PROGRESSION' type='string-select' default='LRCP'>"
|
|
" <Value>LRCP</Value>"
|
|
" <Value>RLCP</Value>"
|
|
" <Value>RPCL</Value>"
|
|
" <Value>PCRL</Value>"
|
|
" <Value>CPRL</Value>"
|
|
" </Option>"
|
|
" <Option name='SOP' type='boolean' description='True to insert SOP markers' default='false'/>"
|
|
" <Option name='EPH' type='boolean' description='True to insert EPH markers' default='false'/>"
|
|
" <Option name='YCBCR420' type='boolean' description='if RGB must be resampled to YCbCr 4:2:0' default='false'/>"
|
|
" <Option name='YCC' type='boolean' description='if RGB must be transformed to YCC color space (lossless MCT transform)' default='YES'/>"
|
|
" <Option name='NBITS' type='int' description='Bits (precision) for sub-byte files (1-7), sub-uint16 (9-15), sub-uint32 (17-31)'/>"
|
|
" <Option name='1BIT_ALPHA' type='boolean' description='Whether to encode the alpha channel as a 1-bit channel' default='NO'/>"
|
|
" <Option name='ALPHA' type='boolean' description='Whether to force encoding last channel as alpha channel' default='NO'/>"
|
|
" <Option name='PROFILE' type='string-select' description='Which codestream profile to use' default='AUTO'>"
|
|
" <Value>AUTO</Value>"
|
|
" <Value>UNRESTRICTED</Value>"
|
|
" <Value>PROFILE_1</Value>"
|
|
" </Option>"
|
|
" <Option name='INSPIRE_TG' type='boolean' description='Whether to use features that comply with Inspire Orthoimagery Technical Guidelines' default='NO'/>"
|
|
" <Option name='JPX' type='boolean' description='Whether to advertize JPX features when a GMLJP2 box is written' default='YES'/>"
|
|
" <Option name='GEOBOXES_AFTER_JP2C' type='boolean' description='Whether to place GeoJP2/GMLJP2 boxes after the code-stream' default='NO'/>"
|
|
" <Option name='PRECINCTS' type='string' description='Precincts size as a string of the form {w,h},{w,h},... with power-of-two values'/>"
|
|
" <Option name='TILEPARTS' type='string-select' description='Whether to generate tile-parts and according to which criterion' default='DISABLED'>"
|
|
" <Value>DISABLED</Value>"
|
|
" <Value>RESOLUTIONS</Value>"
|
|
" <Value>LAYERS</Value>"
|
|
" <Value>COMPONENTS</Value>"
|
|
" </Option>"
|
|
" <Option name='CODEBLOCK_WIDTH' type='int' description='Codeblock width' default='64' min='4' max='1024'/>"
|
|
" <Option name='CODEBLOCK_HEIGHT' type='int' description='Codeblock height' default='64' min='4' max='1024'/>"
|
|
" <Option name='CT_COMPONENTS' type='int' min='3' max='4' description='If there is one color table, number of color table components to write. Autodetected if not specified.'/>"
|
|
" <Option name='WRITE_METADATA' type='boolean' description='Whether metadata should be written, in a dedicated JP2 XML box' default='NO'/>"
|
|
" <Option name='MAIN_MD_DOMAIN_ONLY' type='boolean' description='(Only if WRITE_METADATA=YES) Whether only metadata from the main domain should be written' default='NO'/>"
|
|
" <Option name='USE_SRC_CODESTREAM' type='boolean' description='When source dataset is JPEG2000, whether to reuse the codestream of the source dataset unmodified' default='NO'/>"
|
|
"</CreationOptionList>" );
|
|
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
|
|
|
|
poDriver->pfnIdentify = JP2OpenJPEGDataset::Identify;
|
|
poDriver->pfnOpen = JP2OpenJPEGDataset::Open;
|
|
poDriver->pfnCreateCopy = JP2OpenJPEGDataset::CreateCopy;
|
|
|
|
GetGDALDriverManager()->RegisterDriver( poDriver );
|
|
}
|
|
}
|