1 : /******************************************************************************
2 : * $Id: openjpegdataset.cpp 24247 2012-04-15 16:42:20Z rouault $
3 : *
4 : * Project: JPEG2000 driver based on OpenJPEG library
5 : * Purpose: JPEG2000 driver based on OpenJPEG library
6 : * Author: Even Rouault, <even dot rouault at mines dash paris dot org>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2010, Even Rouault, <even dot rouault at mines dash paris dot org>
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : /* Necessary for opj_setup_decoder() */
31 : #define USE_OPJ_DEPRECATED
32 :
33 : /* A bit of explanation for this ugly "#define bool int"... */
34 : /* openjpeg.h contains itself a "#define bool int" when it is included from a C file */
35 : /* The openjpeg library is written in C, so every reference to bool within the library */
36 : /* assumes that bool is a int. So we have also to reinforce this when including the library */
37 : /* and when calling openjpeg API from the driver, we have to replace bool by int also */
38 : #define bool int
39 : #define GDAL_OPENJPEG_BOOL int
40 : #include <openjpeg.h>
41 : #undef bool /* undef now, so that later includes are happy */
42 :
43 : #include "gdal_pam.h"
44 : #include "cpl_string.h"
45 : #include "gdaljp2metadata.h"
46 :
47 : CPL_CVSID("$Id: openjpegdataset.cpp 24247 2012-04-15 16:42:20Z rouault $");
48 :
49 : /************************************************************************/
50 : /* JP2OpenJPEGDataset_ErrorCallback() */
51 : /************************************************************************/
52 :
53 12 : static void JP2OpenJPEGDataset_ErrorCallback(const char *pszMsg, void *unused)
54 : {
55 12 : CPLError(CE_Failure, CPLE_AppDefined, "%s", pszMsg);
56 12 : }
57 :
58 : /************************************************************************/
59 : /* JP2OpenJPEGDataset_WarningCallback() */
60 : /************************************************************************/
61 :
62 0 : static void JP2OpenJPEGDataset_WarningCallback(const char *pszMsg, void *unused)
63 : {
64 0 : CPLError(CE_Warning, CPLE_AppDefined, "%s", pszMsg);
65 0 : }
66 :
67 : /************************************************************************/
68 : /* JP2OpenJPEGDataset_InfoCallback() */
69 : /************************************************************************/
70 :
71 68 : static void JP2OpenJPEGDataset_InfoCallback(const char *pszMsg, void *unused)
72 : {
73 68 : CPLDebug("OPENJPEG", "info: %s", pszMsg);
74 68 : }
75 :
76 : /************************************************************************/
77 : /* JP2OpenJPEGDataset_Read() */
78 : /************************************************************************/
79 :
80 2956 : static OPJ_UINT32 JP2OpenJPEGDataset_Read(void* pBuffer, OPJ_UINT32 nBytes,
81 : void *pUserData)
82 : {
83 2956 : int nRet = VSIFReadL(pBuffer, 1, nBytes, (VSILFILE*)pUserData);
84 : #ifdef DEBUG
85 2956 : CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Read(%d) = %d", nBytes, nRet);
86 : #endif
87 2956 : if (nRet == 0)
88 60 : nRet = -1;
89 2956 : return nRet;
90 : }
91 :
92 : /************************************************************************/
93 : /* JP2OpenJPEGDataset_Write() */
94 : /************************************************************************/
95 :
96 20 : static OPJ_UINT32 JP2OpenJPEGDataset_Write(void* pBuffer, OPJ_UINT32 nBytes,
97 : void *pUserData)
98 : {
99 20 : int nRet = VSIFWriteL(pBuffer, 1, nBytes, (VSILFILE*)pUserData);
100 : #ifdef DEBUG
101 20 : CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Write(%d) = %d", nBytes, nRet);
102 : #endif
103 20 : return nRet;
104 : }
105 :
106 : /************************************************************************/
107 : /* JP2OpenJPEGDataset_Seek() */
108 : /************************************************************************/
109 :
110 12 : static GDAL_OPENJPEG_BOOL JP2OpenJPEGDataset_Seek(OPJ_SIZE_T nBytes, void * pUserData)
111 : {
112 : #ifdef DEBUG
113 12 : CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Seek(%d)", nBytes);
114 : #endif
115 12 : return VSIFSeekL((VSILFILE*)pUserData, nBytes, SEEK_SET) == 0;
116 : }
117 :
118 : /************************************************************************/
119 : /* JP2OpenJPEGDataset_Skip() */
120 : /************************************************************************/
121 :
122 2468 : static OPJ_SIZE_T JP2OpenJPEGDataset_Skip(OPJ_SIZE_T nBytes, void * pUserData)
123 : {
124 2468 : vsi_l_offset nOffset = VSIFTellL((VSILFILE*)pUserData);
125 2468 : nOffset += nBytes;
126 : #ifdef DEBUG
127 : CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Skip(%d -> " CPL_FRMT_GUIB ")",
128 2468 : nBytes, (GUIntBig)nOffset);
129 : #endif
130 2468 : VSIFSeekL((VSILFILE*)pUserData, nOffset, SEEK_SET);
131 2468 : return nBytes;
132 : }
133 :
134 : /************************************************************************/
135 : /* ==================================================================== */
136 : /* JP2OpenJPEGDataset */
137 : /* ==================================================================== */
138 : /************************************************************************/
139 :
140 : class JP2OpenJPEGDataset : public GDALPamDataset
141 : {
142 : friend class JP2OpenJPEGRasterBand;
143 :
144 : VSILFILE *fp; /* Large FILE API */
145 :
146 : char *pszProjection;
147 : int bGeoTransformValid;
148 : double adfGeoTransform[6];
149 : int nGCPCount;
150 : GDAL_GCP *pasGCPList;
151 :
152 : OPJ_CODEC_FORMAT eCodecFormat;
153 : OPJ_COLOR_SPACE eColorSpace;
154 :
155 : int bLoadingOtherBands;
156 : int bIs420;
157 : OPJ_BYTE * pFullBuffer;
158 :
159 : public:
160 : JP2OpenJPEGDataset();
161 : ~JP2OpenJPEGDataset();
162 :
163 : static int Identify( GDALOpenInfo * poOpenInfo );
164 : static GDALDataset *Open( GDALOpenInfo * );
165 : static GDALDataset *CreateCopy( const char * pszFilename,
166 : GDALDataset *poSrcDS,
167 : int bStrict, char ** papszOptions,
168 : GDALProgressFunc pfnProgress,
169 : void * pProgressData );
170 : CPLErr GetGeoTransform( double* );
171 : virtual const char *GetProjectionRef(void);
172 : virtual int GetGCPCount();
173 : virtual const char *GetGCPProjection();
174 : virtual const GDAL_GCP *GetGCPs();
175 :
176 : static void WriteBox(VSILFILE* fp, GDALJP2Box* poBox);
177 : };
178 :
179 : /************************************************************************/
180 : /* ==================================================================== */
181 : /* JP2OpenJPEGRasterBand */
182 : /* ==================================================================== */
183 : /************************************************************************/
184 :
185 : class JP2OpenJPEGRasterBand : public GDALPamRasterBand
186 : {
187 : friend class JP2OpenJPEGDataset;
188 :
189 : public:
190 :
191 : JP2OpenJPEGRasterBand( JP2OpenJPEGDataset * poDS, int nBand,
192 : GDALDataType eDataType,
193 : int nBlockXSize, int nBlockYSize);
194 : ~JP2OpenJPEGRasterBand();
195 :
196 : virtual CPLErr IReadBlock( int, int, void * );
197 : virtual GDALColorInterp GetColorInterpretation();
198 : };
199 :
200 :
201 : /************************************************************************/
202 : /* JP2OpenJPEGRasterBand() */
203 : /************************************************************************/
204 :
205 82 : JP2OpenJPEGRasterBand::JP2OpenJPEGRasterBand( JP2OpenJPEGDataset *poDS, int nBand,
206 : GDALDataType eDataType,
207 82 : int nBlockXSize, int nBlockYSize)
208 :
209 : {
210 82 : this->poDS = poDS;
211 82 : this->nBand = nBand;
212 82 : this->eDataType = eDataType;
213 82 : this->nBlockXSize = nBlockXSize;
214 82 : this->nBlockYSize = nBlockYSize;
215 82 : }
216 :
217 : /************************************************************************/
218 : /* ~JP2OpenJPEGRasterBand() */
219 : /************************************************************************/
220 :
221 82 : JP2OpenJPEGRasterBand::~JP2OpenJPEGRasterBand()
222 : {
223 82 : }
224 :
225 : /************************************************************************/
226 : /* CopySrcToDst() */
227 : /************************************************************************/
228 :
229 1843200 : static CPL_INLINE GByte CLAMP_0_255(int val)
230 : {
231 1843200 : if (val < 0)
232 4104 : return 0;
233 1839096 : else if (val > 255)
234 4032 : return 255;
235 : else
236 1835064 : return (GByte)val;
237 : }
238 :
239 360 : static void CopySrcToDst(int nWidthToRead, int nHeightToRead,
240 : GByte* pTempBuffer,
241 : int nBlockXSize, int nBlockYSize, int nDataTypeSize,
242 : void* pImage, int nBand, int bIs420)
243 : {
244 : int i, j;
245 360 : if (bIs420)
246 : {
247 6 : GByte* pSrc = (GByte*)pTempBuffer;
248 6 : GByte* pDst = (GByte*)pImage;
249 3846 : for(j=0;j<nHeightToRead;j++)
250 : {
251 1847040 : for(i=0;i<nWidthToRead;i++)
252 : {
253 1843200 : int Y = pSrc[j * nWidthToRead + i];
254 1843200 : int Cb = pSrc[nHeightToRead * nWidthToRead + ((j/2) * (nWidthToRead/2) + i/2) ];
255 1843200 : int Cr = pSrc[5 * nHeightToRead * nWidthToRead / 4 + ((j/2) * (nWidthToRead/2) + i/2) ];
256 1843200 : if (nBand == 1)
257 614400 : pDst[j * nBlockXSize + i] = CLAMP_0_255((int)(Y + 1.402 * (Cr - 128)));
258 1228800 : else if (nBand == 2)
259 614400 : pDst[j * nBlockXSize + i] = CLAMP_0_255((int)(Y - 0.34414 * (Cb - 128) - 0.71414 * (Cr - 128)));
260 : else
261 614400 : pDst[j * nBlockXSize + i] = CLAMP_0_255((int)(Y + 1.772 * (Cb - 128)));
262 : }
263 : }
264 : }
265 : else
266 : {
267 134186 : for(j=0;j<nHeightToRead;j++)
268 : {
269 : memcpy(((GByte*)pImage) + j*nBlockXSize * nDataTypeSize,
270 : pTempBuffer + (j*nWidthToRead + (nBand-1) * nHeightToRead * nWidthToRead) * nDataTypeSize,
271 133832 : nWidthToRead * nDataTypeSize);
272 : }
273 : }
274 360 : }
275 :
276 : /************************************************************************/
277 : /* IReadBlock() */
278 : /************************************************************************/
279 :
280 140 : CPLErr JP2OpenJPEGRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
281 : void * pImage )
282 : {
283 140 : JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *) poDS;
284 140 : opj_codec_t* pCodec = NULL;
285 140 : int nDataTypeSize = (GDALGetDataTypeSize(eDataType) / 8);
286 :
287 : CPLDebug("OPENJPEG", "xoff=%d yoff=%d band=%d",
288 140 : nBlockXOff, nBlockYOff, nBand);
289 :
290 140 : int nWidthToRead = MIN(nBlockXSize, poGDS->nRasterXSize - nBlockXOff * nBlockXSize);
291 140 : int nHeightToRead = MIN(nBlockYSize, poGDS->nRasterYSize - nBlockYOff * nBlockYSize);
292 :
293 140 : if (poGDS->pFullBuffer)
294 : {
295 : CopySrcToDst(nWidthToRead, nHeightToRead, poGDS->pFullBuffer,
296 : nBlockXSize, nBlockYSize, nDataTypeSize, pImage,
297 0 : nBand, poGDS->bIs420);
298 0 : return CE_None;
299 : }
300 :
301 140 : if (nWidthToRead != nBlockXSize || nHeightToRead != nBlockYSize)
302 : {
303 36 : memset(pImage, 0, nBlockXSize * nBlockYSize * nDataTypeSize);
304 : }
305 :
306 : /* FIXME ? Well, this is pretty inefficient as for each block we recreate */
307 : /* a new decoding session. But currently there's no way to call opj_set_decode_area() */
308 : /* twice on the same codec instance... */
309 :
310 140 : pCodec = opj_create_decompress(poGDS->eCodecFormat);
311 :
312 140 : opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback,NULL);
313 140 : opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback,NULL);
314 140 : opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback,NULL);
315 :
316 : opj_dparameters_t parameters;
317 140 : opj_set_default_decoder_parameters(¶meters);
318 :
319 140 : if (! opj_setup_decoder(pCodec,¶meters))
320 : {
321 0 : CPLError(CE_Failure, CPLE_AppDefined, "opj_setup_decoder() failed");
322 0 : opj_destroy_codec(pCodec);
323 0 : return CE_Failure;
324 : }
325 :
326 : /* Reseek to file beginning */
327 140 : VSIFSeekL(poGDS->fp, 0, SEEK_SET);
328 :
329 : opj_stream_t * pStream;
330 140 : pStream = opj_stream_create(1024, TRUE); // Default 1MB is way too big for some datasets
331 140 : opj_stream_set_read_function(pStream, JP2OpenJPEGDataset_Read);
332 140 : opj_stream_set_seek_function(pStream, JP2OpenJPEGDataset_Seek);
333 140 : opj_stream_set_skip_function(pStream, JP2OpenJPEGDataset_Skip);
334 140 : opj_stream_set_user_data(pStream, poGDS->fp);
335 :
336 140 : opj_image_t * psImage = NULL;
337 : OPJ_INT32 nX0,nY0;
338 : OPJ_UINT32 nTileW,nTileH,nTilesX,nTilesY;
339 140 : if(!opj_read_header(pCodec, &psImage, &nX0, &nY0, &nTileW, &nTileH,
340 : &nTilesX, &nTilesY, pStream))
341 : {
342 0 : CPLError(CE_Failure, CPLE_AppDefined, "opj_read_header() failed");
343 0 : opj_destroy_codec(pCodec);
344 0 : opj_stream_destroy(pStream);
345 0 : return CE_Failure;
346 : }
347 :
348 140 : if (!opj_set_decode_area(pCodec,
349 : nBlockXOff * nBlockXSize,
350 : nBlockYOff * nBlockYSize,
351 : nBlockXOff * nBlockXSize + nWidthToRead,
352 : nBlockYOff * nBlockYSize + nHeightToRead))
353 : {
354 0 : CPLError(CE_Failure, CPLE_AppDefined, "opj_set_decode_area() failed");
355 0 : opj_destroy_codec(pCodec);
356 0 : opj_stream_destroy(pStream);
357 0 : opj_image_destroy(psImage);
358 0 : return CE_Failure;
359 : }
360 :
361 : GDAL_OPENJPEG_BOOL bDataToUncompress;
362 : OPJ_UINT32 nTileIndex,nCompCount;
363 : OPJ_INT32 nTileX0,nTileY0,nTileX1,nTileY1;
364 : OPJ_UINT32 nRequiredSize;
365 :
366 : int nAllocatedSize;
367 140 : if (poGDS->bIs420)
368 2 : nAllocatedSize = 3 * nWidthToRead * nHeightToRead * nDataTypeSize / 2;
369 : else
370 138 : nAllocatedSize = poGDS->nBands * nWidthToRead * nHeightToRead * nDataTypeSize;
371 140 : OPJ_BYTE *pTempBuffer = (OPJ_BYTE *)VSIMalloc(nAllocatedSize);
372 140 : if (pTempBuffer == NULL)
373 : {
374 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot allocate temp buffer");
375 0 : opj_destroy_codec(pCodec);
376 0 : opj_stream_destroy(pStream);
377 0 : opj_image_destroy(psImage);
378 0 : return CE_Failure;
379 : }
380 :
381 280 : do
382 : {
383 280 : if (!opj_read_tile_header(pCodec, &nTileIndex, &nRequiredSize,
384 : &nTileX0, &nTileY0, &nTileX1, &nTileY1,
385 : &nCompCount, &bDataToUncompress, pStream))
386 : {
387 0 : CPLError(CE_Failure, CPLE_AppDefined, "opj_read_tile_header() failed");
388 0 : CPLFree(pTempBuffer);
389 0 : opj_destroy_codec(pCodec);
390 0 : opj_stream_destroy(pStream);
391 0 : opj_image_destroy(psImage);
392 0 : return CE_Failure;
393 : }
394 :
395 : /* A few sanity checks */
396 280 : if (nTileX0 != nBlockXOff * nBlockXSize ||
397 : nTileY0 != nBlockYOff * nBlockYSize ||
398 : nTileX1 != nBlockXOff * nBlockXSize + nWidthToRead ||
399 : nTileY1 != nBlockYOff * nBlockYSize + nHeightToRead ||
400 : (int)nRequiredSize != nAllocatedSize ||
401 : (int)nCompCount != poGDS->nBands)
402 : {
403 : CPLDebug("OPENJPEG",
404 : "bDataToUncompress=%d nTileIndex=%d nRequiredSize=%d nCompCount=%d",
405 0 : bDataToUncompress, nTileIndex, nRequiredSize, nCompCount);
406 : CPLDebug("OPENJPEG",
407 : "nTileX0=%d nTileY0=%d nTileX1=%d nTileY1=%d",
408 0 : nTileX0, nTileY0, nTileX1, nTileY1);
409 : CPLError(CE_Failure, CPLE_AppDefined,
410 0 : "opj_read_tile_header() returned unexpected parameters");
411 0 : CPLFree(pTempBuffer);
412 0 : opj_destroy_codec(pCodec);
413 0 : opj_stream_destroy(pStream);
414 0 : opj_image_destroy(psImage);
415 0 : return CE_Failure;
416 : }
417 :
418 280 : if (bDataToUncompress)
419 : {
420 140 : if (!opj_decode_tile_data(pCodec,nTileIndex,pTempBuffer,
421 : nRequiredSize,pStream))
422 : {
423 0 : CPLError(CE_Failure, CPLE_AppDefined, "opj_decode_tile_data() failed");
424 0 : CPLFree(pTempBuffer);
425 0 : opj_destroy_codec(pCodec);
426 0 : opj_stream_destroy(pStream);
427 0 : opj_image_destroy(psImage);
428 0 : return CE_Failure;
429 : }
430 : }
431 : } while(bDataToUncompress);
432 :
433 : CopySrcToDst(nWidthToRead, nHeightToRead, pTempBuffer,
434 : nBlockXSize, nBlockYSize, nDataTypeSize, pImage,
435 140 : nBand, poGDS->bIs420);
436 :
437 : /* Let's cache other bands */
438 140 : if( poGDS->nBands != 1 && !poGDS->bLoadingOtherBands &&
439 : poGDS->nBands * nWidthToRead * nHeightToRead * nDataTypeSize <= GDALGetCacheMax64())
440 : {
441 : int iOtherBand;
442 :
443 110 : poGDS->bLoadingOtherBands = TRUE;
444 :
445 440 : for( iOtherBand = 1; iOtherBand <= poGDS->nBands; iOtherBand++ )
446 : {
447 330 : if( iOtherBand == nBand )
448 110 : continue;
449 :
450 : GDALRasterBlock *poBlock;
451 :
452 : poBlock = poGDS->GetRasterBand(iOtherBand)->
453 220 : GetLockedBlockRef(nBlockXOff,nBlockYOff, TRUE);
454 220 : if (poBlock == NULL)
455 : {
456 0 : break;
457 : }
458 :
459 220 : void* pData = poBlock->GetDataRef();
460 220 : if (pData)
461 : {
462 : CopySrcToDst(nWidthToRead, nHeightToRead, pTempBuffer,
463 : nBlockXSize, nBlockYSize, nDataTypeSize, pData,
464 220 : iOtherBand, poGDS->bIs420);
465 : }
466 :
467 220 : poBlock->DropLock();
468 : }
469 :
470 110 : poGDS->bLoadingOtherBands = FALSE;
471 : }
472 :
473 140 : if (nBlockXSize == nRasterXSize && nBlockYSize == nRasterYSize &&
474 : poGDS->nBands * nWidthToRead * nHeightToRead * nDataTypeSize > GDALGetCacheMax64())
475 : {
476 0 : poGDS->pFullBuffer = pTempBuffer;
477 : }
478 : else
479 : {
480 140 : CPLFree(pTempBuffer);
481 : }
482 :
483 140 : opj_end_decompress(pCodec,pStream);
484 140 : opj_stream_destroy(pStream);
485 140 : opj_destroy_codec(pCodec);
486 140 : opj_image_destroy(psImage);
487 :
488 140 : return CE_None;
489 : }
490 :
491 : /************************************************************************/
492 : /* GetColorInterpretation() */
493 : /************************************************************************/
494 :
495 10 : GDALColorInterp JP2OpenJPEGRasterBand::GetColorInterpretation()
496 : {
497 10 : JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *) poDS;
498 :
499 10 : if (poGDS->eColorSpace == CLRSPC_GRAY)
500 0 : return GCI_GrayIndex;
501 10 : else if (poGDS->nBands == 3 || poGDS->nBands == 4)
502 : {
503 0 : switch(nBand)
504 : {
505 : case 1:
506 0 : return GCI_RedBand;
507 : case 2:
508 0 : return GCI_GreenBand;
509 : case 3:
510 0 : return GCI_BlueBand;
511 : case 4:
512 0 : return GCI_AlphaBand;
513 : default:
514 0 : return GCI_Undefined;
515 : }
516 : }
517 :
518 10 : return GCI_Undefined;
519 : }
520 :
521 : /************************************************************************/
522 : /* ==================================================================== */
523 : /* JP2OpenJPEGDataset */
524 : /* ==================================================================== */
525 : /************************************************************************/
526 :
527 : /************************************************************************/
528 : /* JP2OpenJPEGDataset() */
529 : /************************************************************************/
530 :
531 54 : JP2OpenJPEGDataset::JP2OpenJPEGDataset()
532 : {
533 54 : fp = NULL;
534 54 : nBands = 0;
535 54 : pszProjection = CPLStrdup("");
536 54 : nGCPCount = 0;
537 54 : pasGCPList = NULL;
538 54 : bGeoTransformValid = FALSE;
539 54 : adfGeoTransform[0] = 0.0;
540 54 : adfGeoTransform[1] = 1.0;
541 54 : adfGeoTransform[2] = 0.0;
542 54 : adfGeoTransform[3] = 0.0;
543 54 : adfGeoTransform[4] = 0.0;
544 54 : adfGeoTransform[5] = 1.0;
545 54 : bLoadingOtherBands = FALSE;
546 54 : eCodecFormat = CODEC_UNKNOWN;
547 54 : eColorSpace = CLRSPC_UNKNOWN;
548 54 : bIs420 = FALSE;
549 54 : pFullBuffer = NULL;
550 54 : }
551 :
552 : /************************************************************************/
553 : /* ~JP2OpenJPEGDataset() */
554 : /************************************************************************/
555 :
556 54 : JP2OpenJPEGDataset::~JP2OpenJPEGDataset()
557 :
558 : {
559 54 : FlushCache();
560 :
561 54 : if ( pszProjection )
562 54 : CPLFree( pszProjection );
563 54 : if( nGCPCount > 0 )
564 : {
565 4 : GDALDeinitGCPs( nGCPCount, pasGCPList );
566 4 : CPLFree( pasGCPList );
567 : }
568 54 : if( fp != NULL )
569 54 : VSIFCloseL( fp );
570 54 : VSIFree(pFullBuffer);
571 54 : }
572 :
573 : /************************************************************************/
574 : /* GetProjectionRef() */
575 : /************************************************************************/
576 :
577 34 : const char *JP2OpenJPEGDataset::GetProjectionRef()
578 :
579 : {
580 34 : if ( pszProjection && pszProjection[0] != 0 )
581 30 : return( pszProjection );
582 : else
583 4 : return GDALPamDataset::GetProjectionRef();
584 : }
585 :
586 : /************************************************************************/
587 : /* GetGeoTransform() */
588 : /************************************************************************/
589 :
590 26 : CPLErr JP2OpenJPEGDataset::GetGeoTransform( double * padfTransform )
591 : {
592 26 : if( bGeoTransformValid )
593 : {
594 24 : memcpy( padfTransform, adfGeoTransform, sizeof(adfGeoTransform[0]) * 6 );
595 24 : return CE_None;
596 : }
597 : else
598 2 : return GDALPamDataset::GetGeoTransform(padfTransform);
599 : }
600 :
601 : /************************************************************************/
602 : /* GetGCPCount() */
603 : /************************************************************************/
604 :
605 8 : int JP2OpenJPEGDataset::GetGCPCount()
606 :
607 : {
608 8 : if( nGCPCount > 0 )
609 2 : return nGCPCount;
610 : else
611 6 : return GDALPamDataset::GetGCPCount();
612 : }
613 :
614 : /************************************************************************/
615 : /* GetGCPProjection() */
616 : /************************************************************************/
617 :
618 2 : const char *JP2OpenJPEGDataset::GetGCPProjection()
619 :
620 : {
621 2 : if( nGCPCount > 0 )
622 2 : return pszProjection;
623 : else
624 0 : return GDALPamDataset::GetGCPProjection();
625 : }
626 :
627 : /************************************************************************/
628 : /* GetGCP() */
629 : /************************************************************************/
630 :
631 2 : const GDAL_GCP *JP2OpenJPEGDataset::GetGCPs()
632 :
633 : {
634 2 : if( nGCPCount > 0 )
635 2 : return pasGCPList;
636 : else
637 0 : return GDALPamDataset::GetGCPs();
638 : }
639 :
640 : /************************************************************************/
641 : /* Identify() */
642 : /************************************************************************/
643 :
644 22632 : int JP2OpenJPEGDataset::Identify( GDALOpenInfo * poOpenInfo )
645 :
646 : {
647 : static const unsigned char jpc_header[] = {0xff,0x4f};
648 : static const unsigned char jp2_box_jp[] = {0x6a,0x50,0x20,0x20}; /* 'jP ' */
649 :
650 22632 : if( poOpenInfo->nHeaderBytes >= 16
651 : && (memcmp( poOpenInfo->pabyHeader, jpc_header,
652 : sizeof(jpc_header) ) == 0
653 : || memcmp( poOpenInfo->pabyHeader + 4, jp2_box_jp,
654 : sizeof(jp2_box_jp) ) == 0
655 : ) )
656 56 : return TRUE;
657 :
658 : else
659 22576 : return FALSE;
660 : }
661 : /************************************************************************/
662 : /* Open() */
663 : /************************************************************************/
664 :
665 3772 : GDALDataset *JP2OpenJPEGDataset::Open( GDALOpenInfo * poOpenInfo )
666 :
667 : {
668 3772 : if (!Identify(poOpenInfo))
669 3716 : return NULL;
670 :
671 56 : VSILFILE* fp = VSIFOpenL(poOpenInfo->pszFilename, "rb");
672 56 : if (!fp)
673 0 : return NULL;
674 :
675 : OPJ_CODEC_FORMAT eCodecFormat;
676 :
677 : /* Detect which codec to use : J2K or JP2 ? */
678 : static const unsigned char jpc_header[] = {0xff,0x4f};
679 56 : if (memcmp( poOpenInfo->pabyHeader, jpc_header,
680 : sizeof(jpc_header) ) == 0)
681 20 : eCodecFormat = CODEC_J2K;
682 : else
683 36 : eCodecFormat = CODEC_JP2;
684 :
685 56 : opj_codec_t* pCodec = opj_create_decompress(eCodecFormat);
686 :
687 56 : opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback,NULL);
688 56 : opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback,NULL);
689 56 : opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback,NULL);
690 :
691 : opj_dparameters_t parameters;
692 56 : opj_set_default_decoder_parameters(¶meters);
693 :
694 56 : if (! opj_setup_decoder(pCodec,¶meters))
695 : {
696 0 : VSIFCloseL(fp);
697 0 : return NULL;
698 : }
699 :
700 : opj_stream_t * pStream;
701 56 : pStream = opj_stream_create(1024, TRUE); // Default 1MB is way too big for some datasets
702 56 : opj_stream_set_read_function(pStream, JP2OpenJPEGDataset_Read);
703 56 : opj_stream_set_seek_function(pStream, JP2OpenJPEGDataset_Seek);
704 56 : opj_stream_set_skip_function(pStream, JP2OpenJPEGDataset_Skip);
705 56 : opj_stream_set_user_data(pStream, fp);
706 :
707 56 : opj_image_t * psImage = NULL;
708 : OPJ_INT32 nX0,nY0;
709 : OPJ_UINT32 nTileW,nTileH,nTilesX,nTilesY;
710 56 : if(!opj_read_header(pCodec, &psImage, &nX0, &nY0, &nTileW, &nTileH,
711 : &nTilesX, &nTilesY, pStream))
712 : {
713 0 : CPLError(CE_Failure, CPLE_AppDefined, "opj_read_header() failed");
714 0 : opj_destroy_codec(pCodec);
715 0 : opj_stream_destroy(pStream);
716 0 : opj_image_destroy(psImage);
717 0 : VSIFCloseL(fp);
718 0 : return NULL;
719 : }
720 :
721 56 : if (psImage == NULL)
722 : {
723 0 : opj_destroy_codec(pCodec);
724 0 : opj_stream_destroy(pStream);
725 0 : opj_image_destroy(psImage);
726 0 : VSIFCloseL(fp);
727 0 : return NULL;
728 : }
729 :
730 : #ifdef DEBUG
731 : int i;
732 56 : CPLDebug("OPENJPEG", "nX0 = %d", nX0);
733 56 : CPLDebug("OPENJPEG", "nY0 = %d", nY0);
734 56 : CPLDebug("OPENJPEG", "nTileW = %d", nTileW);
735 56 : CPLDebug("OPENJPEG", "nTileH = %d", nTileH);
736 56 : CPLDebug("OPENJPEG", "psImage->x0 = %d", psImage->x0);
737 56 : CPLDebug("OPENJPEG", "psImage->y0 = %d", psImage->y0);
738 56 : CPLDebug("OPENJPEG", "psImage->x1 = %d", psImage->x1);
739 56 : CPLDebug("OPENJPEG", "psImage->y1 = %d", psImage->y1);
740 56 : CPLDebug("OPENJPEG", "psImage->numcomps = %d", psImage->numcomps);
741 56 : CPLDebug("OPENJPEG", "psImage->color_space = %d", psImage->color_space);
742 146 : for(i=0;i<(int)psImage->numcomps;i++)
743 : {
744 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].dx = %d", i, psImage->comps[i].dx);
745 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].dy = %d", i, psImage->comps[i].dy);
746 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].x0 = %d", i, psImage->comps[i].x0);
747 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].y0 = %d", i, psImage->comps[i].y0);
748 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].w = %d", i, psImage->comps[i].w);
749 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].h = %d", i, psImage->comps[i].h);
750 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].factor = %d", i, psImage->comps[i].factor);
751 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].prec = %d", i, psImage->comps[i].prec);
752 90 : CPLDebug("OPENJPEG", "psImage->comps[%d].sgnd = %d", i, psImage->comps[i].sgnd);
753 : }
754 : #endif
755 :
756 168 : if (psImage->x1 - psImage->x0 <= 0 ||
757 : psImage->y1 - psImage->y0 <= 0 ||
758 : psImage->numcomps == 0 ||
759 56 : (int)psImage->comps[0].w != psImage->x1 - psImage->x0 ||
760 56 : (int)psImage->comps[0].h != psImage->y1 - psImage->y0)
761 : {
762 0 : opj_destroy_codec(pCodec);
763 0 : opj_stream_destroy(pStream);
764 0 : opj_image_destroy(psImage);
765 0 : VSIFCloseL(fp);
766 0 : return NULL;
767 : }
768 :
769 56 : GDALDataType eDataType = GDT_Byte;
770 56 : if (psImage->comps[0].prec > 16)
771 : {
772 0 : if (psImage->comps[0].sgnd)
773 0 : eDataType = GDT_Int32;
774 : else
775 0 : eDataType = GDT_UInt32;
776 : }
777 56 : else if (psImage->comps[0].prec > 8)
778 : {
779 16 : if (psImage->comps[0].sgnd)
780 10 : eDataType = GDT_Int16;
781 : else
782 6 : eDataType = GDT_UInt16;
783 : }
784 :
785 : int bIs420 = (psImage->color_space != CLRSPC_SRGB &&
786 : eDataType == GDT_Byte &&
787 : psImage->numcomps == 3 &&
788 28 : psImage->comps[1].w == psImage->comps[0].w / 2 &&
789 4 : psImage->comps[1].h == psImage->comps[0].h / 2 &&
790 4 : psImage->comps[2].w == psImage->comps[0].w / 2 &&
791 92 : psImage->comps[2].h == psImage->comps[0].h / 2);
792 :
793 56 : if (bIs420)
794 : {
795 2 : CPLDebug("OPENJPEG", "420 format");
796 : }
797 : else
798 : {
799 : int iBand;
800 82 : for(iBand = 2; iBand <= (int)psImage->numcomps; iBand ++)
801 : {
802 150 : if (psImage->comps[iBand-1].w != psImage->comps[0].w ||
803 60 : psImage->comps[iBand-1].h != psImage->comps[0].h ||
804 60 : psImage->comps[iBand-1].prec != psImage->comps[0].prec)
805 : {
806 2 : opj_destroy_codec(pCodec);
807 2 : opj_stream_destroy(pStream);
808 2 : opj_image_destroy(psImage);
809 2 : VSIFCloseL(fp);
810 2 : return NULL;
811 : }
812 : }
813 : }
814 :
815 :
816 : /* -------------------------------------------------------------------- */
817 : /* Create a corresponding GDALDataset. */
818 : /* -------------------------------------------------------------------- */
819 : JP2OpenJPEGDataset *poDS;
820 : int iBand;
821 :
822 54 : poDS = new JP2OpenJPEGDataset();
823 54 : poDS->eCodecFormat = eCodecFormat;
824 54 : poDS->eColorSpace = psImage->color_space;
825 54 : poDS->nRasterXSize = psImage->x1 - psImage->x0;
826 54 : poDS->nRasterYSize = psImage->y1 - psImage->y0;
827 54 : poDS->nBands = psImage->numcomps;
828 54 : poDS->fp = fp;
829 54 : poDS->bIs420 = bIs420;
830 :
831 54 : opj_end_decompress(pCodec,pStream);
832 54 : opj_stream_destroy(pStream);
833 54 : opj_destroy_codec(pCodec);
834 54 : opj_image_destroy(psImage);
835 :
836 : /* -------------------------------------------------------------------- */
837 : /* Create band information objects. */
838 : /* -------------------------------------------------------------------- */
839 272 : for( iBand = 1; iBand <= poDS->nBands; iBand++ )
840 : {
841 : poDS->SetBand( iBand, new JP2OpenJPEGRasterBand( poDS, iBand, eDataType,
842 82 : nTileW, nTileH) );
843 : }
844 :
845 : /* -------------------------------------------------------------------- */
846 : /* More metadata. */
847 : /* -------------------------------------------------------------------- */
848 54 : if( poDS->nBands > 1 )
849 : {
850 14 : poDS->SetMetadataItem( "INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE" );
851 : }
852 :
853 : /* -------------------------------------------------------------------- */
854 : /* Check for georeferencing information. */
855 : /* -------------------------------------------------------------------- */
856 54 : GDALJP2Metadata oJP2Geo;
857 :
858 54 : if( oJP2Geo.ReadAndParse( poOpenInfo->pszFilename ) )
859 : {
860 32 : if ( poDS->pszProjection )
861 32 : CPLFree( poDS->pszProjection );
862 32 : poDS->pszProjection = CPLStrdup(oJP2Geo.pszProjection);
863 32 : poDS->bGeoTransformValid = oJP2Geo.bHaveGeoTransform;
864 : memcpy( poDS->adfGeoTransform, oJP2Geo.adfGeoTransform,
865 32 : sizeof(double) * 6 );
866 32 : poDS->nGCPCount = oJP2Geo.nGCPCount;
867 : poDS->pasGCPList =
868 32 : GDALDuplicateGCPs( oJP2Geo.nGCPCount, oJP2Geo.pasGCPList );
869 : }
870 :
871 54 : if (oJP2Geo.pszXMPMetadata)
872 : {
873 : char *apszMDList[2];
874 2 : apszMDList[0] = (char *) oJP2Geo.pszXMPMetadata;
875 2 : apszMDList[1] = NULL;
876 2 : poDS->SetMetadata(apszMDList, "xml:XMP");
877 : }
878 :
879 : /* -------------------------------------------------------------------- */
880 : /* Do we have other misc metadata? */
881 : /* -------------------------------------------------------------------- */
882 54 : if( oJP2Geo.papszMetadata != NULL )
883 : {
884 8 : char **papszMD = CSLDuplicate(poDS->GDALPamDataset::GetMetadata());
885 :
886 8 : papszMD = CSLMerge( papszMD, oJP2Geo.papszMetadata );
887 8 : poDS->GDALPamDataset::SetMetadata( papszMD );
888 :
889 8 : CSLDestroy( papszMD );
890 : }
891 :
892 : /* -------------------------------------------------------------------- */
893 : /* Initialize any PAM information. */
894 : /* -------------------------------------------------------------------- */
895 54 : poDS->SetDescription( poOpenInfo->pszFilename );
896 54 : poDS->TryLoadXML();
897 :
898 : /* -------------------------------------------------------------------- */
899 : /* Check for overviews. */
900 : /* -------------------------------------------------------------------- */
901 54 : poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
902 :
903 54 : return( poDS );
904 : }
905 :
906 : /************************************************************************/
907 : /* WriteBox() */
908 : /************************************************************************/
909 :
910 16 : void JP2OpenJPEGDataset::WriteBox(VSILFILE* fp, GDALJP2Box* poBox)
911 : {
912 : GUInt32 nLBox;
913 : GUInt32 nTBox;
914 :
915 16 : nLBox = (int) poBox->GetDataLength() + 8;
916 16 : nLBox = CPL_MSBWORD32( nLBox );
917 :
918 16 : memcpy(&nTBox, poBox->GetType(), 4);
919 :
920 16 : VSIFWriteL( &nLBox, 4, 1, fp );
921 16 : VSIFWriteL( &nTBox, 4, 1, fp );
922 16 : VSIFWriteL(poBox->GetWritableData(), 1, (int) poBox->GetDataLength(), fp);
923 16 : }
924 :
925 : /************************************************************************/
926 : /* CreateCopy() */
927 : /************************************************************************/
928 :
929 44 : GDALDataset * JP2OpenJPEGDataset::CreateCopy( const char * pszFilename,
930 : GDALDataset *poSrcDS,
931 : int bStrict, char ** papszOptions,
932 : GDALProgressFunc pfnProgress,
933 : void * pProgressData )
934 :
935 : {
936 44 : int nBands = poSrcDS->GetRasterCount();
937 44 : int nXSize = poSrcDS->GetRasterXSize();
938 44 : int nYSize = poSrcDS->GetRasterYSize();
939 :
940 44 : if( nBands != 1 && nBands != 3 )
941 : {
942 : CPLError( CE_Failure, CPLE_NotSupported,
943 8 : "Unable to export files with %d bands.", nBands );
944 8 : return NULL;
945 : }
946 :
947 36 : if (poSrcDS->GetRasterBand(1)->GetColorTable() != NULL)
948 : {
949 : CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
950 : "JP2OpenJPEG driver ignores color table. "
951 : "The source raster band will be considered as grey level.\n"
952 0 : "Consider using color table expansion (-expand option in gdal_translate)\n");
953 0 : if (bStrict)
954 0 : return NULL;
955 : }
956 :
957 36 : GDALDataType eDataType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
958 36 : int nDataTypeSize = (GDALGetDataTypeSize(eDataType) / 8);
959 36 : if (eDataType != GDT_Byte && eDataType != GDT_Int16 && eDataType != GDT_UInt16
960 : && eDataType != GDT_Int32 && eDataType != GDT_UInt32)
961 : {
962 : CPLError( CE_Failure, CPLE_NotSupported,
963 12 : "JP2OpenJPEG driver only supports creating Byte, GDT_Int16, GDT_UInt16, GDT_Int32, GDT_UInt32");
964 12 : return NULL;
965 : }
966 :
967 : /* -------------------------------------------------------------------- */
968 : /* Analyze creation options. */
969 : /* -------------------------------------------------------------------- */
970 24 : OPJ_CODEC_FORMAT eCodecFormat = CODEC_J2K;
971 24 : const char* pszCodec = CSLFetchNameValueDef(papszOptions, "CODEC", NULL);
972 24 : if (pszCodec)
973 : {
974 2 : if (EQUAL(pszCodec, "JP2"))
975 0 : eCodecFormat = CODEC_JP2;
976 2 : else if (EQUAL(pszCodec, "J2K"))
977 2 : eCodecFormat = CODEC_J2K;
978 : else
979 : {
980 : CPLError(CE_Warning, CPLE_NotSupported,
981 : "Unsupported value for CODEC : %s. Defaulting to J2K",
982 0 : pszCodec);
983 : }
984 : }
985 : else
986 : {
987 22 : if (strlen(pszFilename) > 4 &&
988 : EQUAL(pszFilename + strlen(pszFilename) - 4, ".JP2"))
989 : {
990 6 : eCodecFormat = CODEC_JP2;
991 : }
992 : }
993 :
994 : int nBlockXSize =
995 24 : atoi(CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", "1024"));
996 : int nBlockYSize =
997 24 : atoi(CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", "1024"));
998 24 : if (nBlockXSize < 32 || nBlockYSize < 32)
999 : {
1000 0 : CPLError(CE_Failure, CPLE_NotSupported, "Invalid block size");
1001 0 : return NULL;
1002 : }
1003 :
1004 24 : if (nXSize < nBlockXSize)
1005 20 : nBlockXSize = nXSize;
1006 24 : if (nYSize < nBlockYSize)
1007 20 : nBlockYSize = nYSize;
1008 :
1009 24 : OPJ_PROG_ORDER eProgOrder = LRCP;
1010 : const char* pszPROGORDER =
1011 24 : CSLFetchNameValueDef(papszOptions, "PROGRESSION", "LRCP");
1012 24 : if (EQUAL(pszPROGORDER, "LRCP"))
1013 24 : eProgOrder = LRCP;
1014 0 : else if (EQUAL(pszPROGORDER, "RLCP"))
1015 0 : eProgOrder = RLCP;
1016 0 : else if (EQUAL(pszPROGORDER, "RPCL"))
1017 0 : eProgOrder = RPCL;
1018 0 : else if (EQUAL(pszPROGORDER, "PCRL"))
1019 0 : eProgOrder = PCRL;
1020 0 : else if (EQUAL(pszPROGORDER, "CPRL"))
1021 0 : eProgOrder = CPRL;
1022 : else
1023 : {
1024 : CPLError(CE_Warning, CPLE_NotSupported,
1025 : "Unsupported value for PROGRESSION : %s. Defaulting to LRCP",
1026 0 : pszPROGORDER);
1027 : }
1028 :
1029 : int bIsIrreversible =
1030 24 : ! (CSLTestBoolean(CSLFetchNameValueDef(papszOptions, "REVERSIBLE", "NO")));
1031 :
1032 24 : double dfRate = 100. / 25;
1033 24 : const char* pszQuality = CSLFetchNameValueDef(papszOptions, "QUALITY", NULL);
1034 24 : if (pszQuality)
1035 : {
1036 6 : double dfQuality = atof(pszQuality);
1037 12 : if (dfQuality > 0 && dfQuality <= 100)
1038 : {
1039 6 : dfRate = 100 / dfQuality;
1040 : }
1041 : else
1042 : {
1043 : CPLError(CE_Warning, CPLE_NotSupported,
1044 : "Unsupported value for QUALITY : %s. Defaulting to 25",
1045 0 : pszQuality);
1046 : }
1047 : }
1048 :
1049 24 : int nNumResolutions = 6;
1050 24 : const char* pszResolutions = CSLFetchNameValueDef(papszOptions, "RESOLUTIONS", NULL);
1051 24 : if (pszResolutions)
1052 : {
1053 2 : nNumResolutions = atoi(pszResolutions);
1054 2 : if (nNumResolutions < 1 || nNumResolutions > 7)
1055 : {
1056 0 : nNumResolutions = 6;
1057 : CPLError(CE_Warning, CPLE_NotSupported,
1058 : "Unsupported value for RESOLUTIONS : %s. Defaulting to 6",
1059 0 : pszResolutions);
1060 : }
1061 : }
1062 :
1063 24 : int bSOP = CSLTestBoolean(CSLFetchNameValueDef(papszOptions, "SOP", "FALSE"));
1064 24 : int bEPH = CSLTestBoolean(CSLFetchNameValueDef(papszOptions, "EPH", "FALSE"));
1065 :
1066 : int bResample = nBands == 3 && eDataType == GDT_Byte &&
1067 24 : CSLTestBoolean(CSLFetchNameValueDef(papszOptions, "YCBCR420", "FALSE"));
1068 24 : if (bResample && !((nXSize % 2) == 0 && (nYSize % 2) == 0 && (nBlockXSize % 2) == 0 && (nBlockYSize % 2) == 0))
1069 : {
1070 : CPLError(CE_Warning, CPLE_NotSupported,
1071 0 : "YCBCR420 unsupported when image size and/or tile size are not multiple of 2");
1072 0 : bResample = FALSE;
1073 : }
1074 :
1075 : /* -------------------------------------------------------------------- */
1076 : /* Setup encoder */
1077 : /* -------------------------------------------------------------------- */
1078 :
1079 : opj_cparameters_t parameters;
1080 24 : opj_set_default_encoder_parameters(¶meters);
1081 24 : if (bSOP)
1082 0 : parameters.csty |= 0x02;
1083 24 : if (bEPH)
1084 0 : parameters.csty |= 0x04;
1085 24 : parameters.cp_disto_alloc = 1;
1086 24 : parameters.tcp_numlayers = 1;
1087 24 : parameters.tcp_rates[0] = (float) dfRate;
1088 24 : parameters.cp_tx0 = 0;
1089 24 : parameters.cp_ty0 = 0;
1090 24 : parameters.tile_size_on = TRUE;
1091 24 : parameters.cp_tdx = nBlockXSize;
1092 24 : parameters.cp_tdy = nBlockYSize;
1093 24 : parameters.irreversible = bIsIrreversible;
1094 24 : parameters.numresolution = nNumResolutions;
1095 24 : parameters.prog_order = eProgOrder;
1096 :
1097 : opj_image_cmptparm_t* pasBandParams =
1098 24 : (opj_image_cmptparm_t*)CPLMalloc(nBands * sizeof(opj_image_cmptparm_t));
1099 : int iBand;
1100 52 : for(iBand=0;iBand<nBands;iBand++)
1101 : {
1102 28 : pasBandParams[iBand].x0 = 0;
1103 28 : pasBandParams[iBand].y0 = 0;
1104 28 : if (bResample && iBand > 0)
1105 : {
1106 0 : pasBandParams[iBand].dx = 2;
1107 0 : pasBandParams[iBand].dy = 2;
1108 0 : pasBandParams[iBand].w = nXSize / 2;
1109 0 : pasBandParams[iBand].h = nYSize / 2;
1110 : }
1111 : else
1112 : {
1113 28 : pasBandParams[iBand].dx = 1;
1114 28 : pasBandParams[iBand].dy = 1;
1115 28 : pasBandParams[iBand].w = nXSize;
1116 28 : pasBandParams[iBand].h = nYSize;
1117 : }
1118 28 : pasBandParams[iBand].sgnd = (eDataType == GDT_Int16 || eDataType == GDT_Int32);
1119 28 : pasBandParams[iBand].prec = 8 * nDataTypeSize;
1120 : }
1121 :
1122 24 : opj_codec_t* pCodec = opj_create_compress(eCodecFormat);
1123 24 : if (pCodec == NULL)
1124 : {
1125 : CPLError(CE_Failure, CPLE_AppDefined,
1126 0 : "opj_create_compress() failed");
1127 0 : CPLFree(pasBandParams);
1128 0 : return NULL;
1129 : }
1130 :
1131 24 : opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback,NULL);
1132 24 : opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback,NULL);
1133 24 : opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback,NULL);
1134 :
1135 24 : OPJ_COLOR_SPACE eColorSpace = (bResample) ? CLRSPC_SYCC : (nBands == 3) ? CLRSPC_SRGB : CLRSPC_GRAY;
1136 : opj_image_t* psImage = opj_image_tile_create(nBands,pasBandParams,
1137 24 : eColorSpace);
1138 24 : CPLFree(pasBandParams);
1139 24 : pasBandParams = NULL;
1140 24 : if (psImage == NULL)
1141 : {
1142 : CPLError(CE_Failure, CPLE_AppDefined,
1143 0 : "opj_image_tile_create() failed");
1144 0 : opj_destroy_codec(pCodec);
1145 0 : return NULL;
1146 : }
1147 :
1148 24 : psImage->x0 = 0;
1149 24 : psImage->y0 = 0;
1150 24 : psImage->x1 = nXSize;
1151 24 : psImage->y1 = nYSize;
1152 24 : psImage->color_space = eColorSpace;
1153 24 : psImage->numcomps = nBands;
1154 :
1155 24 : if (!opj_setup_encoder(pCodec,¶meters,psImage))
1156 : {
1157 : CPLError(CE_Failure, CPLE_AppDefined,
1158 0 : "opj_setup_encoder() failed");
1159 0 : opj_image_destroy(psImage);
1160 0 : opj_destroy_codec(pCodec);
1161 0 : return NULL;
1162 : }
1163 :
1164 : /* -------------------------------------------------------------------- */
1165 : /* Create the dataset. */
1166 : /* -------------------------------------------------------------------- */
1167 :
1168 24 : const char* pszAccess = EQUALN(pszFilename, "/vsisubfile/", 12) ? "r+b" : "w+b";
1169 24 : VSILFILE* fp = VSIFOpenL(pszFilename, pszAccess);
1170 24 : if (fp == NULL)
1171 : {
1172 4 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot create file");
1173 4 : opj_image_destroy(psImage);
1174 4 : opj_destroy_codec(pCodec);
1175 4 : return NULL;
1176 : }
1177 :
1178 : opj_stream_t * pStream;
1179 20 : pStream = opj_stream_create(1024*1024, FALSE);
1180 20 : opj_stream_set_write_function(pStream, JP2OpenJPEGDataset_Write);
1181 20 : opj_stream_set_seek_function(pStream, JP2OpenJPEGDataset_Seek);
1182 20 : opj_stream_set_skip_function(pStream, JP2OpenJPEGDataset_Skip);
1183 20 : opj_stream_set_user_data(pStream, fp);
1184 :
1185 20 : if (!opj_start_compress(pCodec,psImage,pStream))
1186 : {
1187 : CPLError(CE_Failure, CPLE_AppDefined,
1188 12 : "opj_start_compress() failed");
1189 12 : opj_stream_destroy(pStream);
1190 12 : opj_image_destroy(psImage);
1191 12 : opj_destroy_codec(pCodec);
1192 12 : VSIFCloseL(fp);
1193 12 : return NULL;
1194 : }
1195 :
1196 8 : int nTilesX = (nXSize + nBlockXSize - 1) / nBlockXSize;
1197 8 : int nTilesY = (nYSize + nBlockYSize - 1) / nBlockYSize;
1198 :
1199 : GByte* pTempBuffer =(GByte*)VSIMalloc(nBlockXSize * nBlockYSize *
1200 8 : nBands * nDataTypeSize);
1201 8 : if (pTempBuffer == NULL)
1202 : {
1203 0 : opj_stream_destroy(pStream);
1204 0 : opj_image_destroy(psImage);
1205 0 : opj_destroy_codec(pCodec);
1206 0 : VSIFCloseL(fp);
1207 0 : return NULL;
1208 : }
1209 :
1210 8 : GByte* pYUV420Buffer = NULL;
1211 8 : if (bResample)
1212 : {
1213 0 : pYUV420Buffer =(GByte*)VSIMalloc(3 * nBlockXSize * nBlockYSize / 2);
1214 0 : if (pYUV420Buffer == NULL)
1215 : {
1216 0 : opj_stream_destroy(pStream);
1217 0 : opj_image_destroy(psImage);
1218 0 : opj_destroy_codec(pCodec);
1219 0 : CPLFree(pTempBuffer);
1220 0 : VSIFCloseL(fp);
1221 0 : return NULL;
1222 : }
1223 : }
1224 :
1225 : /* -------------------------------------------------------------------- */
1226 : /* Setup GML and GeoTIFF information. */
1227 : /* -------------------------------------------------------------------- */
1228 8 : GDALJP2Metadata oJP2MD;
1229 :
1230 8 : int bWriteExtraBoxes = FALSE;
1231 8 : if( eCodecFormat == CODEC_JP2 &&
1232 : (CSLFetchBoolean( papszOptions, "GMLJP2", TRUE ) ||
1233 : CSLFetchBoolean( papszOptions, "GeoJP2", TRUE )) )
1234 : {
1235 6 : const char* pszWKT = poSrcDS->GetProjectionRef();
1236 6 : if( pszWKT != NULL && pszWKT[0] != '\0' )
1237 : {
1238 6 : bWriteExtraBoxes = TRUE;
1239 6 : oJP2MD.SetProjection( pszWKT );
1240 : }
1241 : double adfGeoTransform[6];
1242 6 : if( poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None )
1243 : {
1244 6 : bWriteExtraBoxes = TRUE;
1245 6 : oJP2MD.SetGeoTransform( adfGeoTransform );
1246 : }
1247 : }
1248 :
1249 : #define PIXELS_PER_INCH 2
1250 : #define PIXELS_PER_CM 3
1251 :
1252 : // Resolution
1253 8 : double dfXRes = 0, dfYRes = 0;
1254 8 : int nResUnit = 0;
1255 22 : if( eCodecFormat == CODEC_JP2
1256 6 : && poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION") != NULL
1257 4 : && poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION") != NULL
1258 4 : && poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT") != NULL )
1259 : {
1260 : dfXRes =
1261 4 : CPLAtof(poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION"));
1262 : dfYRes =
1263 4 : CPLAtof(poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION"));
1264 4 : nResUnit = atoi(poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT"));
1265 :
1266 4 : if( nResUnit == PIXELS_PER_INCH )
1267 : {
1268 : // convert pixels per inch to pixels per cm.
1269 0 : dfXRes = dfXRes * 39.37 / 100.0;
1270 0 : dfYRes = dfYRes * 39.37 / 100.0;
1271 0 : nResUnit = PIXELS_PER_CM;
1272 : }
1273 :
1274 4 : if( nResUnit == PIXELS_PER_CM &&
1275 : dfXRes > 0 && dfYRes > 0 &&
1276 : dfXRes < 65535 && dfYRes < 65535 )
1277 : {
1278 4 : bWriteExtraBoxes = TRUE;
1279 : }
1280 : }
1281 :
1282 : /* The file pointer should have been set 8 bytes after the */
1283 : /* last written bytes, because openjpeg has reserved it */
1284 : /* for the jp2c header, but still not written. */
1285 8 : vsi_l_offset nPosOriginalJP2C = 0;
1286 8 : vsi_l_offset nPosRealJP2C = 0;
1287 : GByte abyBackupWhatShouldHaveBeenTheJP2CBoxHeader[8];
1288 :
1289 8 : if( bWriteExtraBoxes )
1290 : {
1291 6 : nPosOriginalJP2C = VSIFTellL(fp) - 8;
1292 :
1293 : char szBoxName[4+1];
1294 : int nLBoxJP2H;
1295 :
1296 : /* If we must write a Res/Resd box, */
1297 : /* read the box header at offset 32 */
1298 6 : if ( nResUnit == PIXELS_PER_CM )
1299 : {
1300 4 : VSIFSeekL(fp, 32, SEEK_SET);
1301 4 : VSIFReadL(&nLBoxJP2H, 1, 4, fp);
1302 4 : nLBoxJP2H = CPL_MSBWORD32( nLBoxJP2H );
1303 4 : VSIFReadL(szBoxName, 1, 4, fp);
1304 4 : szBoxName[4] = '\0';
1305 : }
1306 :
1307 6 : VSIFSeekL(fp, nPosOriginalJP2C, SEEK_SET);
1308 :
1309 : /* And check that it is the jp2h box before */
1310 : /* writing the res box */
1311 6 : if ( nResUnit == PIXELS_PER_CM && EQUAL(szBoxName, "jp2h") )
1312 : {
1313 : /* Format a resd box and embed it inside a res box */
1314 4 : GDALJP2Box oResd;
1315 4 : oResd.SetType("resd");
1316 : GByte aby[10];
1317 :
1318 4 : int nYDenom = 1;
1319 40 : while (nYDenom < 32767 && dfYRes < 32767)
1320 : {
1321 32 : dfYRes *= 2;
1322 32 : nYDenom *= 2;
1323 : }
1324 4 : int nXDenom = 1;
1325 36 : while (nXDenom < 32767 && dfXRes < 32767)
1326 : {
1327 28 : dfXRes *= 2;
1328 28 : nXDenom *= 2;
1329 : }
1330 :
1331 4 : aby[0] = ((int)dfYRes) / 256;
1332 4 : aby[1] = ((int)dfYRes) % 256;
1333 4 : aby[2] = nYDenom / 256;
1334 4 : aby[3] = nYDenom % 256;
1335 4 : aby[4] = ((int)dfXRes) / 256;
1336 4 : aby[5] = ((int)dfXRes) % 256;
1337 4 : aby[6] = nXDenom / 256;
1338 4 : aby[7] = nXDenom % 256;
1339 4 : aby[8] = 2;
1340 4 : aby[9] = 2;
1341 4 : oResd.SetWritableData(10, aby);
1342 4 : GDALJP2Box* poResd = &oResd;
1343 4 : GDALJP2Box* poRes = GDALJP2Box::CreateAsocBox( 1, &poResd );
1344 4 : poRes->SetType("res ");
1345 :
1346 : /* Now let's extend the jp2c box header so that the */
1347 : /* res box becomes a sub-box of it */
1348 4 : nLBoxJP2H += poRes->GetDataLength() + 8;
1349 4 : nLBoxJP2H = CPL_MSBWORD32( nLBoxJP2H );
1350 4 : VSIFSeekL(fp, 32, SEEK_SET);
1351 4 : VSIFWriteL(&nLBoxJP2H, 1, 4, fp);
1352 :
1353 : /* Write the box at the end of the file */
1354 4 : VSIFSeekL(fp, nPosOriginalJP2C, SEEK_SET);
1355 4 : WriteBox(fp, poRes);
1356 :
1357 4 : delete poRes;
1358 : }
1359 :
1360 6 : if( CSLFetchBoolean( papszOptions, "GMLJP2", TRUE ) )
1361 : {
1362 6 : GDALJP2Box* poBox = oJP2MD.CreateGMLJP2(nXSize,nYSize);
1363 6 : WriteBox(fp, poBox);
1364 6 : delete poBox;
1365 : }
1366 6 : if( CSLFetchBoolean( papszOptions, "GeoJP2", TRUE ) )
1367 : {
1368 6 : GDALJP2Box* poBox = oJP2MD.CreateJP2GeoTIFF();
1369 6 : WriteBox(fp, poBox);
1370 6 : delete poBox;
1371 : }
1372 :
1373 6 : nPosRealJP2C = VSIFTellL(fp);
1374 :
1375 : /* Backup the res, GMLJP2 or GeoJP2 box header */
1376 : /* that will be overwritten by opj_end_compress() */
1377 6 : VSIFSeekL(fp, nPosOriginalJP2C, SEEK_SET);
1378 6 : VSIFReadL(abyBackupWhatShouldHaveBeenTheJP2CBoxHeader, 1, 8, fp);
1379 :
1380 6 : VSIFSeekL(fp, nPosRealJP2C + 8, SEEK_SET);
1381 : }
1382 :
1383 : /* -------------------------------------------------------------------- */
1384 : /* Iterate over the tiles */
1385 : /* -------------------------------------------------------------------- */
1386 8 : pfnProgress( 0.0, NULL, pProgressData );
1387 :
1388 8 : CPLErr eErr = CE_None;
1389 : int nBlockXOff, nBlockYOff;
1390 8 : int iTile = 0;
1391 16 : for(nBlockYOff=0;eErr == CE_None && nBlockYOff<nTilesY;nBlockYOff++)
1392 : {
1393 16 : for(nBlockXOff=0;eErr == CE_None && nBlockXOff<nTilesX;nBlockXOff++)
1394 : {
1395 8 : int nWidthToRead = MIN(nBlockXSize, nXSize - nBlockXOff * nBlockXSize);
1396 8 : int nHeightToRead = MIN(nBlockYSize, nYSize - nBlockYOff * nBlockYSize);
1397 : eErr = poSrcDS->RasterIO(GF_Read,
1398 : nBlockXOff * nBlockXSize,
1399 : nBlockYOff * nBlockYSize,
1400 : nWidthToRead, nHeightToRead,
1401 : pTempBuffer, nWidthToRead, nHeightToRead,
1402 : eDataType,
1403 : nBands, NULL,
1404 8 : 0,0,0);
1405 8 : if (eErr == CE_None)
1406 : {
1407 8 : if (bResample)
1408 : {
1409 : int j, i;
1410 0 : for(j=0;j<nHeightToRead;j++)
1411 : {
1412 0 : for(i=0;i<nWidthToRead;i++)
1413 : {
1414 0 : int R = pTempBuffer[j*nWidthToRead+i];
1415 0 : int G = pTempBuffer[nHeightToRead*nWidthToRead + j*nWidthToRead+i];
1416 0 : int B = pTempBuffer[2*nHeightToRead*nWidthToRead + j*nWidthToRead+i];
1417 0 : int Y = (int) (0.299 * R + 0.587 * G + 0.114 * B);
1418 0 : int Cb = CLAMP_0_255((int) (-0.1687 * R - 0.3313 * G + 0.5 * B + 128));
1419 0 : int Cr = CLAMP_0_255((int) (0.5 * R - 0.4187 * G - 0.0813 * B + 128));
1420 0 : pYUV420Buffer[j*nWidthToRead+i] = (GByte) Y;
1421 0 : pYUV420Buffer[nHeightToRead * nWidthToRead + ((j/2) * ((nWidthToRead)/2) + i/2) ] = (GByte) Cb;
1422 0 : pYUV420Buffer[5 * nHeightToRead * nWidthToRead / 4 + ((j/2) * ((nWidthToRead)/2) + i/2) ] = (GByte) Cr;
1423 : }
1424 : }
1425 :
1426 0 : if (!opj_write_tile(pCodec,
1427 : iTile,
1428 : pYUV420Buffer,
1429 : 3 * nWidthToRead * nHeightToRead / 2,
1430 : pStream))
1431 : {
1432 : CPLError(CE_Failure, CPLE_AppDefined,
1433 0 : "opj_write_tile() failed");
1434 0 : eErr = CE_Failure;
1435 : }
1436 : }
1437 : else
1438 : {
1439 8 : if (!opj_write_tile(pCodec,
1440 : iTile,
1441 : pTempBuffer,
1442 : nWidthToRead * nHeightToRead * nBands * nDataTypeSize,
1443 : pStream))
1444 : {
1445 : CPLError(CE_Failure, CPLE_AppDefined,
1446 0 : "opj_write_tile() failed");
1447 0 : eErr = CE_Failure;
1448 : }
1449 : }
1450 : }
1451 :
1452 8 : if( !pfnProgress( (iTile + 1) * 1.0 / (nTilesX * nTilesY), NULL, pProgressData ) )
1453 0 : eErr = CE_Failure;
1454 :
1455 8 : iTile ++;
1456 : }
1457 : }
1458 :
1459 8 : VSIFree(pTempBuffer);
1460 8 : VSIFree(pYUV420Buffer);
1461 :
1462 8 : if (eErr != CE_None)
1463 : {
1464 0 : opj_stream_destroy(pStream);
1465 0 : opj_image_destroy(psImage);
1466 0 : opj_destroy_codec(pCodec);
1467 0 : VSIFCloseL(fp);
1468 0 : return NULL;
1469 : }
1470 :
1471 8 : if (!opj_end_compress(pCodec,pStream))
1472 : {
1473 : CPLError(CE_Failure, CPLE_AppDefined,
1474 0 : "opj_end_compress() failed");
1475 0 : opj_stream_destroy(pStream);
1476 0 : opj_image_destroy(psImage);
1477 0 : opj_destroy_codec(pCodec);
1478 0 : VSIFCloseL(fp);
1479 0 : return NULL;
1480 : }
1481 :
1482 8 : opj_stream_destroy(pStream);
1483 8 : opj_image_destroy(psImage);
1484 8 : opj_destroy_codec(pCodec);
1485 :
1486 : /* Move the jp2c box header at its real position */
1487 : /* and restore the res, GMLJP2 or GeoJP2 box header that */
1488 : /* has been overwritten */
1489 8 : if( bWriteExtraBoxes )
1490 : {
1491 : GByte abyJP2CHeader[8];
1492 :
1493 6 : VSIFSeekL(fp, nPosOriginalJP2C, SEEK_SET);
1494 6 : VSIFReadL(abyJP2CHeader, 1, 8, fp);
1495 :
1496 6 : VSIFSeekL(fp, nPosOriginalJP2C, SEEK_SET);
1497 6 : VSIFWriteL(abyBackupWhatShouldHaveBeenTheJP2CBoxHeader, 1, 8, fp);
1498 :
1499 6 : VSIFSeekL(fp, nPosRealJP2C, SEEK_SET);
1500 6 : VSIFWriteL(abyJP2CHeader, 1, 8, fp);
1501 : }
1502 :
1503 8 : VSIFCloseL(fp);
1504 : /* -------------------------------------------------------------------- */
1505 : /* Re-open dataset, and copy any auxilary pam information. */
1506 : /* -------------------------------------------------------------------- */
1507 :
1508 8 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
1509 8 : JP2OpenJPEGDataset *poDS = (JP2OpenJPEGDataset*) JP2OpenJPEGDataset::Open(&oOpenInfo);
1510 :
1511 8 : if( poDS )
1512 8 : poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT );
1513 :
1514 8 : return poDS;
1515 : }
1516 :
1517 : /************************************************************************/
1518 : /* GDALRegister_JP2OpenJPEG() */
1519 : /************************************************************************/
1520 :
1521 1135 : void GDALRegister_JP2OpenJPEG()
1522 :
1523 : {
1524 : GDALDriver *poDriver;
1525 :
1526 1135 : if (! GDAL_CHECK_VERSION("JP2OpenJPEG driver"))
1527 0 : return;
1528 :
1529 1135 : if( GDALGetDriverByName( "JP2OpenJPEG" ) == NULL )
1530 : {
1531 1093 : poDriver = new GDALDriver();
1532 :
1533 1093 : poDriver->SetDescription( "JP2OpenJPEG" );
1534 : poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
1535 1093 : "JPEG-2000 driver based on OpenJPEG library" );
1536 : poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
1537 1093 : "frmt_jp2openjpeg.html" );
1538 1093 : poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/jp2" );
1539 1093 : poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "jp2" );
1540 : poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
1541 1093 : "Byte Int16 UInt16 Int32 UInt32" );
1542 :
1543 : poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
1544 : "<CreationOptionList>"
1545 : " <Option name='CODEC' type='string-select' default='according to file extension. If unknown, default to J2K'>"
1546 : " <Value>JP2</Value>"
1547 : " <Value>J2K</Value>"
1548 : " </Option>"
1549 : " <Option name='GeoJP2' type='boolean' description='defaults to ON'/>"
1550 : " <Option name='GMLJP2' type='boolean' description='defaults to ON'/>"
1551 : " <Option name='QUALITY' type='float' description='Quality. 0-100' default='25'/>"
1552 : " <Option name='REVERSIBLE' type='boolean' description='True if the compression is reversible' default='false'/>"
1553 : " <Option name='RESOLUTIONS' type='int' description='Number of resolutions. 1-7' default='6'/>"
1554 : " <Option name='BLOCKXSIZE' type='int' description='Tile Width' default='1024'/>"
1555 : " <Option name='BLOCKYSIZE' type='int' description='Tile Height' default='1024'/>"
1556 : " <Option name='PROGRESSION' type='string-select' default='LRCP'>"
1557 : " <Value>LRCP</Value>"
1558 : " <Value>RLCP</Value>"
1559 : " <Value>RPCL</Value>"
1560 : " <Value>PCRL</Value>"
1561 : " <Value>CPRL</Value>"
1562 : " </Option>"
1563 : " <Option name='SOP' type='boolean' description='True to insert SOP markers' default='false'/>"
1564 : " <Option name='EPH' type='boolean' description='True to insert EPH markers' default='false'/>"
1565 : " <Option name='YCBCR420' type='boolean' description='if RGB must be resampled to YCbCr 4:2:0' default='false'/>"
1566 1093 : "</CreationOptionList>" );
1567 :
1568 1093 : poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
1569 :
1570 1093 : poDriver->pfnIdentify = JP2OpenJPEGDataset::Identify;
1571 1093 : poDriver->pfnOpen = JP2OpenJPEGDataset::Open;
1572 1093 : poDriver->pfnCreateCopy = JP2OpenJPEGDataset::CreateCopy;
1573 :
1574 1093 : GetGDALDriverManager()->RegisterDriver( poDriver );
1575 : }
1576 : }
1577 :
|