mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-19 14:15:21 -06:00
1418 lines
52 KiB
C++
1418 lines
52 KiB
C++
/******************************************************************************
|
|
* $Id: jpeg2000dataset.cpp 29171 2015-05-07 19:49:07Z rouault $
|
|
*
|
|
* Project: JPEG-2000
|
|
* Purpose: Partial implementation of the ISO/IEC 15444-1 standard
|
|
* Author: Andrey Kiselev, dron@ak4719.spb.edu
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2002, Andrey Kiselev <dron@ak4719.spb.edu>
|
|
* Copyright (c) 2007-2013, Even Rouault <even dot rouault at mines-paris dot org>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "gdaljp2abstractdataset.h"
|
|
#include "gdaljp2metadata.h"
|
|
#include "cpl_string.h"
|
|
|
|
#include <jasper/jasper.h>
|
|
#include "jpeg2000_vsil_io.h"
|
|
|
|
CPL_CVSID("$Id: jpeg2000dataset.cpp 29171 2015-05-07 19:49:07Z rouault $");
|
|
|
|
CPL_C_START
|
|
void GDALRegister_JPEG2000(void);
|
|
CPL_C_END
|
|
|
|
// XXX: Part of code below extracted from the JasPer internal headers and
|
|
// must be in sync with JasPer version (this one works with JasPer 1.900.1)
|
|
#define JP2_FTYP_MAXCOMPATCODES 32
|
|
#define JP2_BOX_IHDR 0x69686472 /* Image Header */
|
|
#define JP2_BOX_BPCC 0x62706363 /* Bits Per Component */
|
|
#define JP2_BOX_PCLR 0x70636c72 /* Palette */
|
|
#define JP2_BOX_UUID 0x75756964 /* UUID */
|
|
extern "C" {
|
|
typedef struct {
|
|
uint_fast32_t magic;
|
|
} jp2_jp_t;
|
|
typedef struct {
|
|
uint_fast32_t majver;
|
|
uint_fast32_t minver;
|
|
uint_fast32_t numcompatcodes;
|
|
uint_fast32_t compatcodes[JP2_FTYP_MAXCOMPATCODES];
|
|
} jp2_ftyp_t;
|
|
typedef struct {
|
|
uint_fast32_t width;
|
|
uint_fast32_t height;
|
|
uint_fast16_t numcmpts;
|
|
uint_fast8_t bpc;
|
|
uint_fast8_t comptype;
|
|
uint_fast8_t csunk;
|
|
uint_fast8_t ipr;
|
|
} jp2_ihdr_t;
|
|
typedef struct {
|
|
uint_fast16_t numcmpts;
|
|
uint_fast8_t *bpcs;
|
|
} jp2_bpcc_t;
|
|
typedef struct {
|
|
uint_fast8_t method;
|
|
uint_fast8_t pri;
|
|
uint_fast8_t approx;
|
|
uint_fast32_t csid;
|
|
uint_fast8_t *iccp;
|
|
int iccplen;
|
|
} jp2_colr_t;
|
|
typedef struct {
|
|
uint_fast16_t numlutents;
|
|
uint_fast8_t numchans;
|
|
int_fast32_t *lutdata;
|
|
uint_fast8_t *bpc;
|
|
} jp2_pclr_t;
|
|
typedef struct {
|
|
uint_fast16_t channo;
|
|
uint_fast16_t type;
|
|
uint_fast16_t assoc;
|
|
} jp2_cdefchan_t;
|
|
typedef struct {
|
|
uint_fast16_t numchans;
|
|
jp2_cdefchan_t *ents;
|
|
} jp2_cdef_t;
|
|
typedef struct {
|
|
uint_fast16_t cmptno;
|
|
uint_fast8_t map;
|
|
uint_fast8_t pcol;
|
|
} jp2_cmapent_t;
|
|
|
|
typedef struct {
|
|
uint_fast16_t numchans;
|
|
jp2_cmapent_t *ents;
|
|
} jp2_cmap_t;
|
|
|
|
#ifdef HAVE_JASPER_UUID
|
|
typedef struct {
|
|
uint_fast32_t datalen;
|
|
uint_fast8_t uuid[16];
|
|
uint_fast8_t *data;
|
|
} jp2_uuid_t;
|
|
#endif
|
|
|
|
struct jp2_boxops_s;
|
|
typedef struct {
|
|
|
|
struct jp2_boxops_s *ops;
|
|
struct jp2_boxinfo_s *info;
|
|
|
|
uint_fast32_t type;
|
|
|
|
/* The length of the box including the (variable-length) header. */
|
|
uint_fast32_t len;
|
|
|
|
/* The length of the box data. */
|
|
uint_fast32_t datalen;
|
|
|
|
union {
|
|
jp2_jp_t jp;
|
|
jp2_ftyp_t ftyp;
|
|
jp2_ihdr_t ihdr;
|
|
jp2_bpcc_t bpcc;
|
|
jp2_colr_t colr;
|
|
jp2_pclr_t pclr;
|
|
jp2_cdef_t cdef;
|
|
jp2_cmap_t cmap;
|
|
#ifdef HAVE_JASPER_UUID
|
|
jp2_uuid_t uuid;
|
|
#endif
|
|
} data;
|
|
|
|
} jp2_box_t;
|
|
|
|
typedef struct jp2_boxops_s {
|
|
void (*init)(jp2_box_t *box);
|
|
void (*destroy)(jp2_box_t *box);
|
|
int (*getdata)(jp2_box_t *box, jas_stream_t *in);
|
|
int (*putdata)(jp2_box_t *box, jas_stream_t *out);
|
|
void (*dumpdata)(jp2_box_t *box, FILE *out);
|
|
} jp2_boxops_t;
|
|
|
|
extern jp2_box_t *jp2_box_create(int type);
|
|
extern void jp2_box_destroy(jp2_box_t *box);
|
|
extern jp2_box_t *jp2_box_get(jas_stream_t *in);
|
|
extern int jp2_box_put(jp2_box_t *box, jas_stream_t *out);
|
|
#ifdef HAVE_JASPER_UUID
|
|
int jp2_encode_uuid(jas_image_t *image, jas_stream_t *out,
|
|
char *optstr, jp2_box_t *uuid);
|
|
#endif
|
|
}
|
|
// XXX: End of JasPer header.
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* JPEG2000Dataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class JPEG2000Dataset : public GDALJP2AbstractDataset
|
|
{
|
|
friend class JPEG2000RasterBand;
|
|
|
|
jas_stream_t *psStream;
|
|
jas_image_t *psImage;
|
|
int iFormat;
|
|
int bPromoteTo8Bit;
|
|
|
|
int bAlreadyDecoded;
|
|
int DecodeImage();
|
|
|
|
public:
|
|
JPEG2000Dataset();
|
|
~JPEG2000Dataset();
|
|
|
|
static int Identify( GDALOpenInfo * );
|
|
static GDALDataset *Open( GDALOpenInfo * );
|
|
};
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* JPEG2000RasterBand */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
class JPEG2000RasterBand : public GDALPamRasterBand
|
|
{
|
|
friend class JPEG2000Dataset;
|
|
|
|
// NOTE: poDS may be altered for NITF/JPEG2000 files!
|
|
JPEG2000Dataset *poGDS;
|
|
|
|
jas_matrix_t *psMatrix;
|
|
|
|
int iDepth;
|
|
int bSignedness;
|
|
|
|
public:
|
|
|
|
JPEG2000RasterBand( JPEG2000Dataset *, int, int, int );
|
|
~JPEG2000RasterBand();
|
|
|
|
virtual CPLErr IReadBlock( int, int, void * );
|
|
virtual GDALColorInterp GetColorInterpretation();
|
|
};
|
|
|
|
|
|
/************************************************************************/
|
|
/* JPEG2000RasterBand() */
|
|
/************************************************************************/
|
|
|
|
JPEG2000RasterBand::JPEG2000RasterBand( JPEG2000Dataset *poDS, int nBand,
|
|
int iDepth, int bSignedness )
|
|
|
|
{
|
|
this->poDS = poDS;
|
|
poGDS = poDS;
|
|
this->nBand = nBand;
|
|
this->iDepth = iDepth;
|
|
this->bSignedness = bSignedness;
|
|
|
|
// XXX: JasPer can't handle data with depth > 32 bits
|
|
// Maximum possible depth for JPEG2000 is 38!
|
|
switch ( bSignedness )
|
|
{
|
|
case 1: // Signed component
|
|
if (iDepth <= 8)
|
|
this->eDataType = GDT_Byte; // FIXME: should be signed,
|
|
// but we haven't signed byte
|
|
// data type in GDAL
|
|
else if (iDepth <= 16)
|
|
this->eDataType = GDT_Int16;
|
|
else if (iDepth <= 32)
|
|
this->eDataType = GDT_Int32;
|
|
break;
|
|
case 0: // Unsigned component
|
|
default:
|
|
if (iDepth <= 8)
|
|
this->eDataType = GDT_Byte;
|
|
else if (iDepth <= 16)
|
|
this->eDataType = GDT_UInt16;
|
|
else if (iDepth <= 32)
|
|
this->eDataType = GDT_UInt32;
|
|
break;
|
|
}
|
|
// FIXME: Figure out optimal block size!
|
|
// Should the block size be fixed or determined dynamically?
|
|
nBlockXSize = MIN(256, poDS->nRasterXSize);
|
|
nBlockYSize = MIN(256, poDS->nRasterYSize);
|
|
psMatrix = jas_matrix_create(nBlockYSize, nBlockXSize);
|
|
|
|
if( iDepth % 8 != 0 && !poDS->bPromoteTo8Bit )
|
|
{
|
|
SetMetadataItem( "NBITS",
|
|
CPLString().Printf("%d",iDepth),
|
|
"IMAGE_STRUCTURE" );
|
|
}
|
|
SetMetadataItem( "COMPRESSION", "JP2000", "IMAGE_STRUCTURE" );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~JPEG2000RasterBand() */
|
|
/************************************************************************/
|
|
|
|
JPEG2000RasterBand::~JPEG2000RasterBand()
|
|
{
|
|
if ( psMatrix )
|
|
jas_matrix_destroy( psMatrix );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* IReadBlock() */
|
|
/************************************************************************/
|
|
|
|
CPLErr JPEG2000RasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
|
|
void * pImage )
|
|
{
|
|
int i, j;
|
|
|
|
// Decode image from the stream, if not yet
|
|
if ( !poGDS->DecodeImage() )
|
|
{
|
|
return CE_Failure;
|
|
}
|
|
|
|
// Now we can calculate the pixel offset of the top left by multiplying
|
|
// block offset with the block size.
|
|
|
|
/* In case the dimensions of the image are not multiple of the block dimensions */
|
|
/* take care of not requesting more pixels than available for the blocks at the */
|
|
/* right or bottom of the image */
|
|
int nWidthToRead = MIN(nBlockXSize, poGDS->nRasterXSize - nBlockXOff * nBlockXSize);
|
|
int nHeightToRead = MIN(nBlockYSize, poGDS->nRasterYSize - nBlockYOff * nBlockYSize);
|
|
|
|
jas_image_readcmpt( poGDS->psImage, nBand - 1,
|
|
nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
|
|
nWidthToRead, nHeightToRead, psMatrix );
|
|
|
|
int nWordSize = GDALGetDataTypeSize(eDataType) / 8;
|
|
int nLineSize = nBlockXSize * nWordSize;
|
|
GByte* ptr = (GByte*)pImage;
|
|
|
|
/* Pad incomplete blocks at the right or bottom of the image */
|
|
if (nWidthToRead != nBlockXSize || nHeightToRead != nBlockYSize)
|
|
memset(pImage, 0, nLineSize * nBlockYSize);
|
|
|
|
for( i = 0; i < nHeightToRead; i++, ptr += nLineSize )
|
|
{
|
|
for( j = 0; j < nWidthToRead; j++ )
|
|
{
|
|
// XXX: We need casting because matrix element always
|
|
// has 32 bit depth in JasPer
|
|
// FIXME: what about float values?
|
|
switch( eDataType )
|
|
{
|
|
case GDT_Int16:
|
|
{
|
|
((GInt16*)ptr)[j] = (GInt16)jas_matrix_get(psMatrix, i, j);
|
|
}
|
|
break;
|
|
case GDT_Int32:
|
|
{
|
|
((GInt32*)ptr)[j] = (GInt32)jas_matrix_get(psMatrix, i, j);
|
|
}
|
|
break;
|
|
case GDT_UInt16:
|
|
{
|
|
((GUInt16*)ptr)[j] = (GUInt16)jas_matrix_get(psMatrix, i, j);
|
|
}
|
|
break;
|
|
case GDT_UInt32:
|
|
{
|
|
((GUInt32*)ptr)[j] = (GUInt32)jas_matrix_get(psMatrix, i, j);
|
|
}
|
|
break;
|
|
case GDT_Byte:
|
|
default:
|
|
{
|
|
((GByte*)ptr)[j] = (GByte)jas_matrix_get(psMatrix, i, j);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( poGDS->bPromoteTo8Bit && nBand == 4 )
|
|
{
|
|
ptr = (GByte*)pImage;
|
|
for( i = 0; i < nHeightToRead; i++, ptr += nLineSize )
|
|
{
|
|
for( j = 0; j < nWidthToRead; j++ )
|
|
{
|
|
((GByte*)ptr)[j] *= 255;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CE_None;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* GetColorInterpretation() */
|
|
/************************************************************************/
|
|
|
|
GDALColorInterp JPEG2000RasterBand::GetColorInterpretation()
|
|
{
|
|
// Decode image from the stream, if not yet
|
|
if ( !poGDS->DecodeImage() )
|
|
{
|
|
return GCI_Undefined;
|
|
}
|
|
|
|
if ( jas_clrspc_fam( jas_image_clrspc( poGDS->psImage ) ) ==
|
|
JAS_CLRSPC_FAM_GRAY )
|
|
return GCI_GrayIndex;
|
|
else if ( jas_clrspc_fam( jas_image_clrspc( poGDS->psImage ) ) ==
|
|
JAS_CLRSPC_FAM_RGB )
|
|
{
|
|
switch ( jas_image_cmpttype( poGDS->psImage, nBand - 1 ) )
|
|
{
|
|
case JAS_IMAGE_CT_RGB_R:
|
|
return GCI_RedBand;
|
|
case JAS_IMAGE_CT_RGB_G:
|
|
return GCI_GreenBand;
|
|
case JAS_IMAGE_CT_RGB_B:
|
|
return GCI_BlueBand;
|
|
case JAS_IMAGE_CT_OPACITY:
|
|
return GCI_AlphaBand;
|
|
default:
|
|
return GCI_Undefined;
|
|
}
|
|
}
|
|
else
|
|
return GCI_Undefined;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ==================================================================== */
|
|
/* JPEG2000Dataset */
|
|
/* ==================================================================== */
|
|
/************************************************************************/
|
|
|
|
/************************************************************************/
|
|
/* JPEG2000Dataset() */
|
|
/************************************************************************/
|
|
|
|
JPEG2000Dataset::JPEG2000Dataset()
|
|
{
|
|
psStream = NULL;
|
|
psImage = NULL;
|
|
nBands = 0;
|
|
bAlreadyDecoded = FALSE;
|
|
bPromoteTo8Bit = FALSE;
|
|
|
|
poDriver = (GDALDriver *)GDALGetDriverByName("JPEG2000");
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* ~JPEG2000Dataset() */
|
|
/************************************************************************/
|
|
|
|
JPEG2000Dataset::~JPEG2000Dataset()
|
|
|
|
{
|
|
FlushCache();
|
|
|
|
if ( psStream )
|
|
jas_stream_close( psStream );
|
|
if ( psImage )
|
|
jas_image_destroy( psImage );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* DecodeImage() */
|
|
/************************************************************************/
|
|
int JPEG2000Dataset::DecodeImage()
|
|
{
|
|
if (bAlreadyDecoded)
|
|
return psImage != NULL;
|
|
|
|
bAlreadyDecoded = TRUE;
|
|
if ( !( psImage = jas_image_decode(psStream, iFormat, 0) ) )
|
|
{
|
|
CPLDebug( "JPEG2000", "Unable to decode image. Format: %s, %d",
|
|
jas_image_fmttostr( iFormat ), iFormat );
|
|
return FALSE;
|
|
}
|
|
|
|
/* Case of a JP2 image : check that the properties given by */
|
|
/* the JP2 boxes match the ones of the code stream */
|
|
if (nBands != 0)
|
|
{
|
|
if (nBands != jas_image_numcmpts( psImage ))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"The number of components indicated in the IHDR box (%d) mismatch "
|
|
"the value specified in the code stream (%d)",
|
|
nBands, jas_image_numcmpts( psImage ));
|
|
jas_image_destroy( psImage );
|
|
psImage = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
if (nRasterXSize != jas_image_cmptwidth( psImage, 0 ) ||
|
|
nRasterYSize != jas_image_cmptheight( psImage, 0 ) )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"The dimensions indicated in the IHDR box (%d x %d) mismatch "
|
|
"the value specified in the code stream (%d x %d)",
|
|
nRasterXSize, nRasterYSize,
|
|
(int)jas_image_cmptwidth( psImage, 0 ),
|
|
(int)jas_image_cmptheight( psImage, 0 ));
|
|
jas_image_destroy( psImage );
|
|
psImage = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
int iBand;
|
|
for ( iBand = 0; iBand < nBands; iBand++ )
|
|
{
|
|
JPEG2000RasterBand* poBand = (JPEG2000RasterBand*) GetRasterBand(iBand+1);
|
|
if (poBand->iDepth != jas_image_cmptprec( psImage, iBand ) ||
|
|
poBand->bSignedness != jas_image_cmptsgnd( psImage, iBand ))
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"The bit depth of band %d indicated in the IHDR box (%d) mismatch "
|
|
"the value specified in the code stream (%d)",
|
|
iBand + 1, poBand->iDepth, jas_image_cmptprec( psImage, iBand ));
|
|
jas_image_destroy( psImage );
|
|
psImage = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Ask for YCbCr -> RGB translation */
|
|
if ( jas_clrspc_fam( jas_image_clrspc( psImage ) ) ==
|
|
JAS_CLRSPC_FAM_YCBCR )
|
|
{
|
|
jas_image_t *psRGBImage;
|
|
jas_cmprof_t *psRGBProf;
|
|
CPLDebug( "JPEG2000", "forcing conversion to sRGB");
|
|
if (!(psRGBProf = jas_cmprof_createfromclrspc(JAS_CLRSPC_SRGB))) {
|
|
CPLDebug( "JPEG2000", "cannot create sRGB profile");
|
|
return TRUE;
|
|
}
|
|
if (!(psRGBImage = jas_image_chclrspc(psImage, psRGBProf, JAS_CMXFORM_INTENT_PER))) {
|
|
CPLDebug( "JPEG2000", "cannot convert to sRGB");
|
|
jas_cmprof_destroy(psRGBProf);
|
|
return TRUE;
|
|
}
|
|
jas_image_destroy(psImage);
|
|
jas_cmprof_destroy(psRGBProf);
|
|
psImage = psRGBImage;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void JPEG2000Init()
|
|
{
|
|
static int bHasInit = FALSE;
|
|
if (!bHasInit)
|
|
{
|
|
bHasInit = TRUE;
|
|
jas_init();
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Identify() */
|
|
/************************************************************************/
|
|
|
|
int JPEG2000Dataset::Identify( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
static const unsigned char jpc_header[] = {0xff,0x4f};
|
|
static const unsigned char jp2_box_jp[] = {0x6a,0x50,0x20,0x20}; /* 'jP ' */
|
|
|
|
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
|
|
/* PGX file*/
|
|
|| (memcmp( poOpenInfo->pabyHeader, "PG", 2) == 0 &&
|
|
(poOpenInfo->pabyHeader[2] == ' ' || poOpenInfo->pabyHeader[2] == '\t') &&
|
|
(memcmp( poOpenInfo->pabyHeader + 3, "ML", 2) == 0 ||
|
|
memcmp( poOpenInfo->pabyHeader + 3, "LM", 2) == 0))) )
|
|
return TRUE;
|
|
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* Open() */
|
|
/************************************************************************/
|
|
|
|
GDALDataset *JPEG2000Dataset::Open( GDALOpenInfo * poOpenInfo )
|
|
|
|
{
|
|
int iFormat;
|
|
char *pszFormatName = NULL;
|
|
jas_stream_t *sS;
|
|
|
|
if (!Identify(poOpenInfo))
|
|
return NULL;
|
|
|
|
JPEG2000Init();
|
|
if( !(sS = JPEG2000_VSIL_fopen( poOpenInfo->pszFilename, "rb" )) )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
iFormat = jas_image_getfmt( sS );
|
|
if ( !(pszFormatName = jas_image_fmttostr( iFormat )) )
|
|
{
|
|
jas_stream_close( sS );
|
|
return NULL;
|
|
}
|
|
if ( strlen( pszFormatName ) < 3 ||
|
|
(!EQUALN( pszFormatName, "jp2", 3 ) &&
|
|
!EQUALN( pszFormatName, "jpc", 3 ) &&
|
|
!EQUALN( pszFormatName, "pgx", 3 )) )
|
|
{
|
|
CPLDebug( "JPEG2000", "JasPer reports file is format type `%s'.",
|
|
pszFormatName );
|
|
jas_stream_close( sS );
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Confirm the requested access is supported. */
|
|
/* -------------------------------------------------------------------- */
|
|
if( poOpenInfo->eAccess == GA_Update )
|
|
{
|
|
jas_stream_close(sS);
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"The JPEG2000 driver does not support update access to existing"
|
|
" datasets.\n" );
|
|
return NULL;
|
|
}
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create a corresponding GDALDataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
JPEG2000Dataset *poDS;
|
|
int *paiDepth = NULL, *pabSignedness = NULL;
|
|
int iBand;
|
|
|
|
poDS = new JPEG2000Dataset();
|
|
|
|
poDS->psStream = sS;
|
|
poDS->iFormat = iFormat;
|
|
|
|
if ( EQUALN( pszFormatName, "jp2", 3 ) )
|
|
{
|
|
// XXX: Hack to read JP2 boxes from input file. JasPer hasn't public
|
|
// API call for such things, so we will use internal JasPer functions.
|
|
jp2_box_t *box;
|
|
box = 0;
|
|
while ( ( box = jp2_box_get(poDS->psStream) ) )
|
|
{
|
|
switch (box->type)
|
|
{
|
|
case JP2_BOX_IHDR:
|
|
poDS->nBands = box->data.ihdr.numcmpts;
|
|
poDS->nRasterXSize = box->data.ihdr.width;
|
|
poDS->nRasterYSize = box->data.ihdr.height;
|
|
CPLDebug( "JPEG2000",
|
|
"IHDR box found. Dump: "
|
|
"width=%d, height=%d, numcmpts=%d, bpp=%d",
|
|
(int)box->data.ihdr.width, (int)box->data.ihdr.height,
|
|
(int)box->data.ihdr.numcmpts, (box->data.ihdr.bpc & 0x7F) + 1 );
|
|
/* ISO/IEC 15444-1:2004 I.5.3.1 specifies that 255 means that all */
|
|
/* components have not the same bit depth and/or sign and that a */
|
|
/* BPCC box must then follow to specify them for each component */
|
|
if ( box->data.ihdr.bpc != 255 )
|
|
{
|
|
paiDepth = (int *)CPLMalloc(poDS->nBands * sizeof(int));
|
|
pabSignedness = (int *)CPLMalloc(poDS->nBands * sizeof(int));
|
|
for ( iBand = 0; iBand < poDS->nBands; iBand++ )
|
|
{
|
|
paiDepth[iBand] = (box->data.ihdr.bpc & 0x7F) + 1;
|
|
pabSignedness[iBand] = box->data.ihdr.bpc >> 7;
|
|
CPLDebug( "JPEG2000",
|
|
"Component %d: bpp=%d, signedness=%d",
|
|
iBand, paiDepth[iBand], pabSignedness[iBand] );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JP2_BOX_BPCC:
|
|
CPLDebug( "JPEG2000", "BPCC box found. Dump:" );
|
|
if ( !paiDepth && !pabSignedness )
|
|
{
|
|
paiDepth = (int *)
|
|
CPLMalloc( box->data.bpcc.numcmpts * sizeof(int) );
|
|
pabSignedness = (int *)
|
|
CPLMalloc( box->data.bpcc.numcmpts * sizeof(int) );
|
|
for( iBand = 0; iBand < (int)box->data.bpcc.numcmpts; iBand++ )
|
|
{
|
|
paiDepth[iBand] = (box->data.bpcc.bpcs[iBand] & 0x7F) + 1;
|
|
pabSignedness[iBand] = box->data.bpcc.bpcs[iBand] >> 7;
|
|
CPLDebug( "JPEG2000",
|
|
"Component %d: bpp=%d, signedness=%d",
|
|
iBand, paiDepth[iBand], pabSignedness[iBand] );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case JP2_BOX_PCLR:
|
|
CPLDebug( "JPEG2000",
|
|
"PCLR box found. Dump: number of LUT entries=%d, "
|
|
"number of resulting channels=%d",
|
|
(int)box->data.pclr.numlutents, box->data.pclr.numchans );
|
|
poDS->nBands = box->data.pclr.numchans;
|
|
if ( paiDepth )
|
|
CPLFree( paiDepth );
|
|
if ( pabSignedness )
|
|
CPLFree( pabSignedness );
|
|
paiDepth = (int *)
|
|
CPLMalloc( box->data.pclr.numchans * sizeof(int) );
|
|
pabSignedness = (int *)
|
|
CPLMalloc( box->data.pclr.numchans * sizeof(int) );
|
|
for( iBand = 0; iBand < (int)box->data.pclr.numchans; iBand++ )
|
|
{
|
|
paiDepth[iBand] = (box->data.pclr.bpc[iBand] & 0x7F) + 1;
|
|
pabSignedness[iBand] = box->data.pclr.bpc[iBand] >> 7;
|
|
CPLDebug( "JPEG2000",
|
|
"Component %d: bpp=%d, signedness=%d",
|
|
iBand, paiDepth[iBand], pabSignedness[iBand] );
|
|
}
|
|
break;
|
|
}
|
|
jp2_box_destroy( box );
|
|
box = 0;
|
|
}
|
|
if( !paiDepth || !pabSignedness )
|
|
{
|
|
delete poDS;
|
|
CPLDebug( "JPEG2000", "Unable to read JP2 header boxes.\n" );
|
|
return NULL;
|
|
}
|
|
if ( jas_stream_rewind( poDS->psStream ) < 0 )
|
|
{
|
|
delete poDS;
|
|
CPLDebug( "JPEG2000", "Unable to rewind input stream.\n" );
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !poDS->DecodeImage() )
|
|
{
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
poDS->nBands = jas_image_numcmpts( poDS->psImage );
|
|
poDS->nRasterXSize = jas_image_cmptwidth( poDS->psImage, 0 );
|
|
poDS->nRasterYSize = jas_image_cmptheight( poDS->psImage, 0 );
|
|
paiDepth = (int *)CPLMalloc( poDS->nBands * sizeof(int) );
|
|
pabSignedness = (int *)CPLMalloc( poDS->nBands * sizeof(int) );
|
|
for ( iBand = 0; iBand < poDS->nBands; iBand++ )
|
|
{
|
|
paiDepth[iBand] = jas_image_cmptprec( poDS->psImage, iBand );
|
|
pabSignedness[iBand] = jas_image_cmptsgnd( poDS->psImage, iBand );
|
|
}
|
|
}
|
|
|
|
if ( !GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
|
|
!GDALCheckBandCount(poDS->nBands, 0) )
|
|
{
|
|
CPLFree( paiDepth );
|
|
CPLFree( pabSignedness );
|
|
delete poDS;
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Should we promote alpha channel to 8 bits ? */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->bPromoteTo8Bit = (poDS->nBands == 4 &&
|
|
paiDepth[0] == 8 &&
|
|
paiDepth[1] == 8 &&
|
|
paiDepth[2] == 8 &&
|
|
paiDepth[3] == 1 &&
|
|
CSLFetchBoolean(poOpenInfo->papszOpenOptions, "1BIT_ALPHA_PROMOTION", TRUE));
|
|
if( poDS->bPromoteTo8Bit )
|
|
CPLDebug( "JPEG2000", "Fourth (alpha) band is promoted from 1 bit to 8 bit");
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* Create band information objects. */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
|
for( iBand = 1; iBand <= poDS->nBands; iBand++ )
|
|
{
|
|
poDS->SetBand( iBand, new JPEG2000RasterBand( poDS, iBand,
|
|
paiDepth[iBand - 1], pabSignedness[iBand - 1] ) );
|
|
|
|
}
|
|
|
|
if ( paiDepth )
|
|
CPLFree( paiDepth );
|
|
if ( pabSignedness )
|
|
CPLFree( pabSignedness );
|
|
|
|
poDS->LoadJP2Metadata(poOpenInfo);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Initialize any PAM information. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->SetDescription( poOpenInfo->pszFilename );
|
|
poDS->TryLoadXML();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Check for overviews. */
|
|
/* -------------------------------------------------------------------- */
|
|
poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
return( poDS );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* JPEG2000CreateCopy() */
|
|
/************************************************************************/
|
|
|
|
static GDALDataset *
|
|
JPEG2000CreateCopy( const char * pszFilename, GDALDataset *poSrcDS,
|
|
int bStrict, char ** papszOptions,
|
|
GDALProgressFunc pfnProgress, void * pProgressData )
|
|
|
|
{
|
|
int nBands = poSrcDS->GetRasterCount();
|
|
int nXSize = poSrcDS->GetRasterXSize();
|
|
int nYSize = poSrcDS->GetRasterYSize();
|
|
int iBand;
|
|
GDALRasterBand *poBand;
|
|
|
|
if( nBands == 0 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"Unable to export files with zero bands." );
|
|
return NULL;
|
|
}
|
|
|
|
if (poSrcDS->GetRasterBand(1)->GetColorTable() != NULL)
|
|
{
|
|
CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
|
|
"JPEG2000 driver ignores color table. "
|
|
"The source raster band will be considered as grey level.\n"
|
|
"Consider using color table expansion (-expand option in gdal_translate)\n");
|
|
if (bStrict)
|
|
return NULL;
|
|
}
|
|
|
|
for ( iBand = 0; iBand < nBands; iBand++ )
|
|
{
|
|
poBand = poSrcDS->GetRasterBand( iBand + 1);
|
|
|
|
switch ( poBand->GetRasterDataType() )
|
|
{
|
|
case GDT_Byte:
|
|
case GDT_Int16:
|
|
case GDT_UInt16:
|
|
break;
|
|
|
|
default:
|
|
if( !CSLTestBoolean(CPLGetConfigOption("JPEG2000_FORCE_CREATION", "NO")) )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"A band of the source dataset is of type %s, which might cause crashes in libjasper. "
|
|
"Set JPEG2000_FORCE_CREATION configuration option to YES to attempt the creation of the file.",
|
|
GDALGetDataTypeName(poBand->GetRasterDataType()));
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !pfnProgress( 0.0, NULL, pProgressData ) )
|
|
return NULL;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Create the dataset. */
|
|
/* -------------------------------------------------------------------- */
|
|
jas_stream_t *psStream;
|
|
jas_image_t *psImage;
|
|
|
|
JPEG2000Init();
|
|
const char* pszAccess = EQUALN(pszFilename, "/vsisubfile/", 12) ? "r+b" : "w+b";
|
|
if( !(psStream = JPEG2000_VSIL_fopen( pszFilename, pszAccess) ) )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO, "Unable to create file %s.\n",
|
|
pszFilename );
|
|
return NULL;
|
|
}
|
|
|
|
if ( !(psImage = jas_image_create0()) )
|
|
{
|
|
CPLError( CE_Failure, CPLE_OutOfMemory, "Unable to create image %s.\n",
|
|
pszFilename );
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Loop over image, copying image data. */
|
|
/* -------------------------------------------------------------------- */
|
|
GUInt32 *paiScanline;
|
|
int iLine, iPixel;
|
|
CPLErr eErr = CE_None;
|
|
jas_matrix_t *psMatrix;
|
|
jas_image_cmptparm_t *sComps; // Array of pointers to image components
|
|
|
|
sComps = (jas_image_cmptparm_t*)
|
|
CPLMalloc( nBands * sizeof(jas_image_cmptparm_t) );
|
|
|
|
if ( !(psMatrix = jas_matrix_create( 1, nXSize )) )
|
|
{
|
|
CPLError( CE_Failure, CPLE_OutOfMemory,
|
|
"Unable to create matrix with size %dx%d.\n", 1, nYSize );
|
|
CPLFree( sComps );
|
|
jas_image_destroy( psImage );
|
|
return NULL;
|
|
}
|
|
paiScanline = (GUInt32 *) CPLMalloc( nXSize *
|
|
GDALGetDataTypeSize(GDT_UInt32) / 8 );
|
|
|
|
for ( iBand = 0; iBand < nBands; iBand++ )
|
|
{
|
|
poBand = poSrcDS->GetRasterBand( iBand + 1);
|
|
|
|
sComps[iBand].tlx = sComps[iBand].tly = 0;
|
|
sComps[iBand].hstep = sComps[iBand].vstep = 1;
|
|
sComps[iBand].width = nXSize;
|
|
sComps[iBand].height = nYSize;
|
|
sComps[iBand].prec = GDALGetDataTypeSize( poBand->GetRasterDataType() );
|
|
switch ( poBand->GetRasterDataType() )
|
|
{
|
|
case GDT_Int16:
|
|
case GDT_Int32:
|
|
case GDT_Float32:
|
|
case GDT_Float64:
|
|
sComps[iBand].sgnd = 1;
|
|
break;
|
|
case GDT_Byte:
|
|
case GDT_UInt16:
|
|
case GDT_UInt32:
|
|
default:
|
|
sComps[iBand].sgnd = 0;
|
|
break;
|
|
}
|
|
jas_image_addcmpt(psImage, iBand, sComps);
|
|
|
|
for( iLine = 0; eErr == CE_None && iLine < nYSize; iLine++ )
|
|
{
|
|
eErr = poBand->RasterIO( GF_Read, 0, iLine, nXSize, 1,
|
|
paiScanline, nXSize, 1, GDT_UInt32,
|
|
sizeof(GUInt32), sizeof(GUInt32) * nXSize, NULL );
|
|
for ( iPixel = 0; iPixel < nXSize; iPixel++ )
|
|
jas_matrix_setv( psMatrix, iPixel, paiScanline[iPixel] );
|
|
|
|
if( (jas_image_writecmpt(psImage, iBand, 0, iLine,
|
|
nXSize, 1, psMatrix)) < 0 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Unable to write scanline %d of the component %d.\n",
|
|
iLine, iBand );
|
|
jas_matrix_destroy( psMatrix );
|
|
CPLFree( paiScanline );
|
|
CPLFree( sComps );
|
|
jas_image_destroy( psImage );
|
|
return NULL;
|
|
}
|
|
|
|
if( eErr == CE_None &&
|
|
!pfnProgress( ((iLine + 1) + iBand * nYSize) /
|
|
((double) nYSize * nBands),
|
|
NULL, pProgressData) )
|
|
{
|
|
eErr = CE_Failure;
|
|
CPLError( CE_Failure, CPLE_UserInterrupt,
|
|
"User terminated CreateCopy()" );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read compression parameters and encode the image. */
|
|
/* -------------------------------------------------------------------- */
|
|
int i, j;
|
|
const int OPTSMAX = 4096;
|
|
const char *pszFormatName;
|
|
char pszOptionBuf[OPTSMAX + 1];
|
|
|
|
const char *apszComprOptions[]=
|
|
{
|
|
"imgareatlx",
|
|
"imgareatly",
|
|
"tilegrdtlx",
|
|
"tilegrdtly",
|
|
"tilewidth",
|
|
"tileheight",
|
|
"prcwidth",
|
|
"prcheight",
|
|
"cblkwidth",
|
|
"cblkheight",
|
|
"mode",
|
|
"rate",
|
|
"ilyrrates",
|
|
"prg",
|
|
"numrlvls",
|
|
"sop",
|
|
"eph",
|
|
"lazy",
|
|
"termall",
|
|
"segsym",
|
|
"vcausal",
|
|
"pterm",
|
|
"resetprob",
|
|
"numgbits",
|
|
NULL
|
|
};
|
|
|
|
pszFormatName = CSLFetchNameValue( papszOptions, "FORMAT" );
|
|
if ( !pszFormatName ||
|
|
(!EQUALN( pszFormatName, "jp2", 3 ) &&
|
|
!EQUALN( pszFormatName, "jpc", 3 ) ) )
|
|
pszFormatName = "jp2";
|
|
|
|
pszOptionBuf[0] = '\0';
|
|
if ( papszOptions )
|
|
{
|
|
CPLDebug( "JPEG2000", "User supplied parameters:" );
|
|
for ( i = 0; papszOptions[i] != NULL; i++ )
|
|
{
|
|
CPLDebug( "JPEG2000", "%s\n", papszOptions[i] );
|
|
for ( j = 0; apszComprOptions[j] != NULL; j++ )
|
|
if( EQUALN( apszComprOptions[j], papszOptions[i],
|
|
strlen(apszComprOptions[j]) ) )
|
|
{
|
|
int m, n;
|
|
|
|
n = strlen( pszOptionBuf );
|
|
m = n + strlen( papszOptions[i] ) + 1;
|
|
if ( m > OPTSMAX )
|
|
break;
|
|
if ( n > 0 )
|
|
{
|
|
strcat( pszOptionBuf, "\n" );
|
|
}
|
|
strcat( pszOptionBuf, papszOptions[i] );
|
|
}
|
|
}
|
|
}
|
|
CPLDebug( "JPEG2000", "Parameters, delivered to the JasPer library:" );
|
|
CPLDebug( "JPEG2000", "%s", pszOptionBuf );
|
|
|
|
if ( nBands == 1 ) // Grayscale
|
|
{
|
|
jas_image_setclrspc( psImage, JAS_CLRSPC_SGRAY );
|
|
jas_image_setcmpttype( psImage, 0, JAS_IMAGE_CT_GRAY_Y );
|
|
}
|
|
else if ( nBands == 3 || nBands == 4 ) // Assume as RGB(A)
|
|
{
|
|
jas_image_setclrspc( psImage, JAS_CLRSPC_SRGB );
|
|
for ( iBand = 0; iBand < nBands; iBand++ )
|
|
{
|
|
poBand = poSrcDS->GetRasterBand( iBand + 1);
|
|
switch ( poBand->GetColorInterpretation() )
|
|
{
|
|
case GCI_RedBand:
|
|
jas_image_setcmpttype( psImage, iBand, JAS_IMAGE_CT_RGB_R );
|
|
break;
|
|
case GCI_GreenBand:
|
|
jas_image_setcmpttype( psImage, iBand, JAS_IMAGE_CT_RGB_G );
|
|
break;
|
|
case GCI_BlueBand:
|
|
jas_image_setcmpttype( psImage, iBand, JAS_IMAGE_CT_RGB_B );
|
|
break;
|
|
case GCI_AlphaBand:
|
|
jas_image_setcmpttype( psImage, iBand, JAS_IMAGE_CT_OPACITY );
|
|
break;
|
|
default:
|
|
jas_image_setcmpttype( psImage, iBand, JAS_IMAGE_CT_UNKNOWN );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else // Unknown
|
|
{
|
|
/* JAS_CLRSPC_UNKNOWN causes crashes in Jasper jp2_enc.c at line 231 */
|
|
/* iccprof = jas_iccprof_createfromcmprof(jas_image_cmprof(image)); */
|
|
/* but if we explictely set the cmprof, it does not work better */
|
|
/* since it would abort at line 281 later ... */
|
|
/* So the best option is to switch to gray colorspace */
|
|
/* And we need to switch at the band level too, otherwise Kakadu or */
|
|
/* JP2MrSID don't like it */
|
|
//jas_image_setclrspc( psImage, JAS_CLRSPC_UNKNOWN );
|
|
jas_image_setclrspc( psImage, JAS_CLRSPC_SGRAY );
|
|
for ( iBand = 0; iBand < nBands; iBand++ )
|
|
//jas_image_setcmpttype( psImage, iBand, JAS_IMAGE_CT_UNKNOWN );
|
|
jas_image_setcmpttype( psImage, iBand, JAS_IMAGE_CT_GRAY_Y );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Set the GeoTIFF box if georeferencing is available, and this */
|
|
/* is a JP2 file. */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( EQUALN( pszFormatName, "jp2", 3 ) )
|
|
{
|
|
#ifdef HAVE_JASPER_UUID
|
|
double adfGeoTransform[6];
|
|
if( CSLFetchBoolean( papszOptions, "GeoJP2", TRUE ) &&
|
|
((poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None
|
|
&& (adfGeoTransform[0] != 0.0
|
|
|| adfGeoTransform[1] != 1.0
|
|
|| adfGeoTransform[2] != 0.0
|
|
|| adfGeoTransform[3] != 0.0
|
|
|| adfGeoTransform[4] != 0.0
|
|
|| ABS(adfGeoTransform[5]) != 1.0))
|
|
|| poSrcDS->GetGCPCount() > 0
|
|
|| poSrcDS->GetMetadata("RPC") != NULL ) )
|
|
{
|
|
GDALJP2Metadata oJP2Geo;
|
|
|
|
if( poSrcDS->GetGCPCount() > 0 )
|
|
{
|
|
oJP2Geo.SetProjection( poSrcDS->GetGCPProjection() );
|
|
oJP2Geo.SetGCPs( poSrcDS->GetGCPCount(), poSrcDS->GetGCPs() );
|
|
}
|
|
else
|
|
{
|
|
oJP2Geo.SetProjection( poSrcDS->GetProjectionRef() );
|
|
oJP2Geo.SetGeoTransform( adfGeoTransform );
|
|
}
|
|
|
|
oJP2Geo.SetRPCMD( poSrcDS->GetMetadata("RPC") );
|
|
|
|
const char* pszAreaOrPoint = poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
|
|
oJP2Geo.bPixelIsPoint = pszAreaOrPoint != NULL && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT);
|
|
|
|
GDALJP2Box *poBox = oJP2Geo.CreateJP2GeoTIFF();
|
|
jp2_box_t *box = jp2_box_create( JP2_BOX_UUID );
|
|
memcpy( box->data.uuid.uuid, poBox->GetUUID(), 16 );
|
|
box->data.uuid.datalen = poBox->GetDataLength() - 16;
|
|
box->data.uuid.data =
|
|
(uint_fast8_t *)jas_malloc( poBox->GetDataLength() - 16 );
|
|
memcpy( box->data.uuid.data, poBox->GetWritableData() + 16,
|
|
poBox->GetDataLength() - 16 );
|
|
delete poBox;
|
|
poBox = NULL;
|
|
|
|
if ( jp2_encode_uuid( psImage, psStream, pszOptionBuf, box) < 0 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Unable to encode image %s.", pszFilename );
|
|
jp2_box_destroy( box );
|
|
jas_matrix_destroy( psMatrix );
|
|
CPLFree( paiScanline );
|
|
CPLFree( sComps );
|
|
jas_image_destroy( psImage );
|
|
return NULL;
|
|
}
|
|
jp2_box_destroy( box );
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
if ( jp2_encode( psImage, psStream, pszOptionBuf) < 0 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Unable to encode image %s.", pszFilename );
|
|
jas_matrix_destroy( psMatrix );
|
|
CPLFree( paiScanline );
|
|
CPLFree( sComps );
|
|
jas_image_destroy( psImage );
|
|
return NULL;
|
|
}
|
|
#ifdef HAVE_JASPER_UUID
|
|
}
|
|
#endif
|
|
}
|
|
else // Write JPC code stream
|
|
{
|
|
if ( jpc_encode(psImage, psStream, pszOptionBuf) < 0 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO,
|
|
"Unable to encode image %s.\n", pszFilename );
|
|
jas_matrix_destroy( psMatrix );
|
|
CPLFree( paiScanline );
|
|
CPLFree( sComps );
|
|
jas_image_destroy( psImage );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
jas_stream_flush( psStream );
|
|
|
|
jas_matrix_destroy( psMatrix );
|
|
CPLFree( paiScanline );
|
|
CPLFree( sComps );
|
|
jas_image_destroy( psImage );
|
|
if ( jas_stream_close( psStream ) )
|
|
{
|
|
CPLError( CE_Failure, CPLE_FileIO, "Unable to close file %s.\n",
|
|
pszFilename );
|
|
return NULL;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Add GMLJP2 box at end of file. */
|
|
/* -------------------------------------------------------------------- */
|
|
if ( EQUALN( pszFormatName, "jp2", 3 ) )
|
|
{
|
|
double adfGeoTransform[6];
|
|
if( CSLFetchBoolean( papszOptions, "GMLJP2", TRUE ) &&
|
|
poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None &&
|
|
poSrcDS->GetProjectionRef() != NULL &&
|
|
poSrcDS->GetProjectionRef()[0] != '\0' )
|
|
{
|
|
VSILFILE* fp = VSIFOpenL(pszFilename, "rb+");
|
|
if( fp )
|
|
{
|
|
// Look for jp2c box and patch its LBox to be the real box size
|
|
// instead of zero
|
|
int bOK = FALSE;
|
|
GUInt32 nLBox;
|
|
GUInt32 nTBox;
|
|
|
|
while(TRUE)
|
|
{
|
|
if( VSIFReadL(&nLBox, 4, 1, fp) != 1 ||
|
|
VSIFReadL(&nTBox, 4, 1, fp) != 1 )
|
|
break;
|
|
nLBox = CPL_MSBWORD32( nLBox );
|
|
if( memcmp(&nTBox, "jp2c", 4) == 0 )
|
|
{
|
|
if( nLBox >= 8 )
|
|
{
|
|
bOK = TRUE;
|
|
break;
|
|
}
|
|
if( nLBox == 0 )
|
|
{
|
|
vsi_l_offset nPos = VSIFTellL(fp);
|
|
VSIFSeekL(fp, 0, SEEK_END);
|
|
vsi_l_offset nEnd = VSIFTellL(fp);
|
|
VSIFSeekL(fp, nPos - 8, SEEK_SET);
|
|
nLBox = (GUInt32)(8 + nEnd - nPos);
|
|
if( nLBox == (vsi_l_offset)8 + nEnd - nPos )
|
|
{
|
|
nLBox = CPL_MSBWORD32( nLBox );
|
|
VSIFWriteL(&nLBox, 1, 4, fp);
|
|
bOK = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if( nLBox < 8 )
|
|
break;
|
|
VSIFSeekL(fp, nLBox - 8, SEEK_CUR);
|
|
}
|
|
|
|
// Can write GMLJP2 box
|
|
if( bOK )
|
|
{
|
|
GDALJP2Metadata oJP2MD;
|
|
oJP2MD.SetProjection( poSrcDS->GetProjectionRef() );
|
|
oJP2MD.SetGeoTransform( adfGeoTransform );
|
|
GDALJP2Box *poBox;
|
|
const char* pszGMLJP2V2Def = CSLFetchNameValue( papszOptions, "GMLJP2V2_DEF" );
|
|
if( pszGMLJP2V2Def != NULL )
|
|
poBox = oJP2MD.CreateGMLJP2V2(nXSize,nYSize,pszGMLJP2V2Def,poSrcDS);
|
|
else
|
|
poBox = oJP2MD.CreateGMLJP2(nXSize,nYSize);
|
|
|
|
nLBox = (int) poBox->GetDataLength() + 8;
|
|
nLBox = CPL_MSBWORD32( nLBox );
|
|
memcpy(&nTBox, poBox->GetType(), 4);
|
|
|
|
VSIFSeekL(fp, 0, SEEK_END);
|
|
VSIFWriteL( &nLBox, 4, 1, fp );
|
|
VSIFWriteL( &nTBox, 4, 1, fp );
|
|
VSIFWriteL(poBox->GetWritableData(), 1, (int) poBox->GetDataLength(), fp);
|
|
VSIFCloseL(fp);
|
|
|
|
delete poBox;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Do we need a world file? */
|
|
/* -------------------------------------------------------------------- */
|
|
if( CSLFetchBoolean( papszOptions, "WORLDFILE", FALSE ) )
|
|
{
|
|
double adfGeoTransform[6];
|
|
|
|
poSrcDS->GetGeoTransform( adfGeoTransform );
|
|
GDALWriteWorldFile( pszFilename, "wld", adfGeoTransform );
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Re-open dataset, and copy any auxiliary pam information. */
|
|
/* -------------------------------------------------------------------- */
|
|
GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
|
|
GDALPamDataset *poDS = (GDALPamDataset*) JPEG2000Dataset::Open(&oOpenInfo);
|
|
|
|
if( poDS )
|
|
{
|
|
poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT & (~GCIF_METADATA) );
|
|
|
|
/* Only write relevant metadata to PAM, and if needed */
|
|
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_JPEG2000() */
|
|
/************************************************************************/
|
|
|
|
void GDALRegister_JPEG2000()
|
|
|
|
{
|
|
GDALDriver *poDriver;
|
|
|
|
if (! GDAL_CHECK_VERSION("JPEG2000 driver"))
|
|
return;
|
|
|
|
if( GDALGetDriverByName( "JPEG2000" ) == NULL )
|
|
{
|
|
poDriver = new GDALDriver();
|
|
|
|
poDriver->SetDescription( "JPEG2000" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VECTOR, "YES" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
|
|
"JPEG-2000 part 1 (ISO/IEC 15444-1), based on Jasper library" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
|
|
"frmt_jpeg2000.html" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
|
|
"Byte Int16 UInt16 Int32 UInt32" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/jp2" );
|
|
poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "jp2" );
|
|
|
|
poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
|
|
|
|
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='FORMAT' type='string-select' default='according to file extension. If unknown, default to J2K'>"
|
|
" <Value>JP2</Value>"
|
|
" <Value>JPC</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='WORLDFILE' type='boolean' description='Whether to write a worldfile .wld' default='NO'/>"
|
|
" <Option name='imgareatlx' type='string' />"
|
|
" <Option name='imgareatly' type='string' />"
|
|
" <Option name='tilegrdtlx' type='string' />"
|
|
" <Option name='tilegrdtly' type='string' />"
|
|
" <Option name='tilewidth' type='string' />"
|
|
" <Option name='tileheight' type='string' />"
|
|
" <Option name='prcwidth' type='string' />"
|
|
" <Option name='prcheight' type='string' />"
|
|
" <Option name='cblkwidth' type='string' />"
|
|
" <Option name='cblkheight' type='string' />"
|
|
" <Option name='mode' type='string' />"
|
|
" <Option name='rate' type='string' />"
|
|
" <Option name='ilyrrates' type='string' />"
|
|
" <Option name='prg' type='string' />"
|
|
" <Option name='numrlvls' type='string' />"
|
|
" <Option name='sop' type='string' />"
|
|
" <Option name='eph' type='string' />"
|
|
" <Option name='lazy' type='string' />"
|
|
" <Option name='termall' type='string' />"
|
|
" <Option name='segsym' type='string' />"
|
|
" <Option name='vcausal' type='string' />"
|
|
" <Option name='pterm' type='string' />"
|
|
" <Option name='resetprob' type='string' />"
|
|
" <Option name='numgbits' type='string' />"
|
|
"</CreationOptionList>" );
|
|
|
|
poDriver->pfnIdentify = JPEG2000Dataset::Identify;
|
|
poDriver->pfnOpen = JPEG2000Dataset::Open;
|
|
poDriver->pfnCreateCopy = JPEG2000CreateCopy;
|
|
|
|
GetGDALDriverManager()->RegisterDriver( poDriver );
|
|
}
|
|
}
|
|
|