1 /****************************************************************************** 2 * 3 * Project: XPM Driver 4 * Purpose: Implement GDAL XPM Support 5 * Author: Frank Warmerdam, warmerdam@pobox.com 6 * 7 ****************************************************************************** 8 * Copyright (c) 2002, Frank Warmerdam 9 * Copyright (c) 2008-2010, Even Rouault <even dot rouault at spatialys.com> 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 #include "gdal_pam.h" 31 #include "cpl_string.h" 32 #include "memdataset.h" 33 #include "gdal_frmts.h" 34 35 #include <cstdlib> 36 #include <algorithm> 37 38 CPL_CVSID("$Id: xpmdataset.cpp f6099e5ed704166bf5cc113a053dd1b2725cb391 2020-03-22 11:20:10 +0100 Kai Pastor $") 39 40 static unsigned char *ParseXPM( const char *pszInput, 41 unsigned int nFileSize, 42 int *pnXSize, int *pnYSize, 43 GDALColorTable **ppoRetTable ); 44 45 /************************************************************************/ 46 /* ==================================================================== */ 47 /* XPMDataset */ 48 /* ==================================================================== */ 49 /************************************************************************/ 50 51 class XPMDataset final: public GDALPamDataset 52 { 53 public: 54 XPMDataset() {} 55 ~XPMDataset(); 56 57 static GDALDataset *Open( GDALOpenInfo * ); 58 static int Identify( GDALOpenInfo * ); 59 }; 60 61 /************************************************************************/ 62 /* ~XPMDataset() */ 63 /************************************************************************/ 64 65 XPMDataset::~XPMDataset() 66 67 { 68 FlushCache(); 69 } 70 71 /************************************************************************/ 72 /* Identify() */ 73 /************************************************************************/ 74 75 int XPMDataset::Identify( GDALOpenInfo * poOpenInfo ) 76 77 { 78 /* -------------------------------------------------------------------- */ 79 /* First we check to see if the file has the expected header */ 80 /* bytes. For now we expect the XPM file to start with a line */ 81 /* containing the letters XPM, and to have "static" in the */ 82 /* header. */ 83 /* -------------------------------------------------------------------- */ 84 return poOpenInfo->nHeaderBytes >= 32 && 85 strstr(reinterpret_cast<const char *>( poOpenInfo->pabyHeader ), 86 "XPM") != nullptr && 87 strstr(reinterpret_cast<const char *>( poOpenInfo->pabyHeader ), 88 "static") != nullptr; 89 } 90 91 /************************************************************************/ 92 /* Open() */ 93 /************************************************************************/ 94 95 GDALDataset *XPMDataset::Open( GDALOpenInfo * poOpenInfo ) 96 97 { 98 if( !Identify(poOpenInfo) || poOpenInfo->fpL == nullptr ) 99 return nullptr; 100 101 if( poOpenInfo->eAccess == GA_Update ) 102 { 103 CPLError( CE_Failure, CPLE_NotSupported, 104 "The XPM driver does not support update access to existing" 105 " files." ); 106 return nullptr; 107 } 108 109 /* -------------------------------------------------------------------- */ 110 /* Read the whole file into a memory strings. */ 111 /* -------------------------------------------------------------------- */ 112 VSILFILE *fp = poOpenInfo->fpL; 113 poOpenInfo->fpL = nullptr; 114 115 if( VSIFSeekL( fp, 0, SEEK_END ) != 0 ) 116 { 117 CPL_IGNORE_RET_VAL(VSIFCloseL(fp)); 118 return nullptr; 119 } 120 unsigned int nFileSize = static_cast<unsigned int>( VSIFTellL( fp ) ); 121 122 char *pszFileContents = reinterpret_cast<char *>( VSI_MALLOC_VERBOSE(nFileSize+1) ); 123 if( pszFileContents == nullptr ) 124 { 125 CPL_IGNORE_RET_VAL(VSIFCloseL(fp)); 126 return nullptr; 127 } 128 pszFileContents[nFileSize] = '\0'; 129 130 if( VSIFSeekL( fp, 0, SEEK_SET ) != 0 || 131 VSIFReadL( pszFileContents, 1, nFileSize, fp ) != nFileSize) 132 { 133 CPLFree( pszFileContents ); 134 CPLError( CE_Failure, CPLE_FileIO, 135 "Failed to read all %d bytes from file %s.", 136 nFileSize, poOpenInfo->pszFilename ); 137 CPL_IGNORE_RET_VAL(VSIFCloseL(fp)); 138 return nullptr; 139 } 140 141 CPL_IGNORE_RET_VAL(VSIFCloseL(fp)); 142 fp = nullptr; 143 144 /* -------------------------------------------------------------------- */ 145 /* Convert into a binary image. */ 146 /* -------------------------------------------------------------------- */ 147 CPLErrorReset(); 148 149 int nXSize; 150 int nYSize; 151 GDALColorTable *poCT = nullptr; 152 153 GByte *pabyImage = ParseXPM( pszFileContents, nFileSize, &nXSize, &nYSize, &poCT ); 154 155 CPLFree( pszFileContents ); 156 157 if( pabyImage == nullptr ) 158 { 159 return nullptr; 160 } 161 162 /* -------------------------------------------------------------------- */ 163 /* Create a corresponding GDALDataset. */ 164 /* -------------------------------------------------------------------- */ 165 XPMDataset *poDS = new XPMDataset(); 166 167 /* -------------------------------------------------------------------- */ 168 /* Capture some information from the file that is of interest. */ 169 /* -------------------------------------------------------------------- */ 170 poDS->nRasterXSize = nXSize; 171 poDS->nRasterYSize = nYSize; 172 173 /* -------------------------------------------------------------------- */ 174 /* Create band information objects. */ 175 /* -------------------------------------------------------------------- */ 176 MEMRasterBand *poBand = new MEMRasterBand( poDS, 1, pabyImage, GDT_Byte, 1, 177 nXSize, TRUE ); 178 poBand->SetColorTable( poCT ); 179 poDS->SetBand( 1, poBand ); 180 181 delete poCT; 182 183 /* -------------------------------------------------------------------- */ 184 /* Initialize any PAM information. */ 185 /* -------------------------------------------------------------------- */ 186 poDS->SetDescription( poOpenInfo->pszFilename ); 187 poDS->TryLoadXML(); 188 189 /* -------------------------------------------------------------------- */ 190 /* Support overviews. */ 191 /* -------------------------------------------------------------------- */ 192 poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename ); 193 194 return poDS; 195 } 196 197 /************************************************************************/ 198 /* XPMCreateCopy() */ 199 /************************************************************************/ 200 201 static GDALDataset * 202 XPMCreateCopy( const char * pszFilename, 203 GDALDataset *poSrcDS, 204 int bStrict, 205 char ** /* papszOptions */, 206 GDALProgressFunc /* pfnProgress */, 207 void * /* pProgressData */) 208 { 209 /* -------------------------------------------------------------------- */ 210 /* Some some rudimentary checks */ 211 /* -------------------------------------------------------------------- */ 212 const int nBands = poSrcDS->GetRasterCount(); 213 if( nBands != 1 ) 214 { 215 CPLError( CE_Failure, CPLE_NotSupported, 216 "XPM driver only supports one band images.\n" ); 217 218 return nullptr; 219 } 220 221 if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte 222 && bStrict ) 223 { 224 CPLError( CE_Failure, CPLE_NotSupported, 225 "XPM driver doesn't support data type %s. " 226 "Only eight bit bands supported.\n", 227 GDALGetDataTypeName( 228 poSrcDS->GetRasterBand(1)->GetRasterDataType()) ); 229 230 return nullptr; 231 } 232 233 /* -------------------------------------------------------------------- */ 234 /* If there is no colortable on the source image, create a */ 235 /* greyscale one with 64 levels of grey. */ 236 /* -------------------------------------------------------------------- */ 237 GDALRasterBand *poBand = poSrcDS->GetRasterBand(1); 238 239 GDALColorTable oGreyTable; 240 GDALColorTable *poCT = poBand->GetColorTable(); 241 242 if( poCT == nullptr ) 243 { 244 poCT = &oGreyTable; 245 246 for( int i = 0; i < 256; i++ ) 247 { 248 GDALColorEntry sColor; 249 250 sColor.c1 = (short) i; 251 sColor.c2 = (short) i; 252 sColor.c3 = (short) i; 253 sColor.c4 = 255; 254 255 poCT->SetColorEntry( i, &sColor ); 256 } 257 } 258 259 /* -------------------------------------------------------------------- */ 260 /* Build list of active colors, and the mapping from pixels to */ 261 /* our active colormap. */ 262 /* -------------------------------------------------------------------- */ 263 const char *pszColorCodes 264 = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@" 265 "#$%^&*()-+=[]|:;,.<>?/"; 266 267 int anPixelMapping[256]; 268 GDALColorEntry asPixelColor[256]; 269 int nActiveColors = std::min(poCT->GetColorEntryCount(),256); 270 271 // Setup initial colortable and pixel value mapping. 272 memset( anPixelMapping+0, 0, sizeof(int) * 256 ); 273 for( int i = 0; i < nActiveColors; i++ ) 274 { 275 poCT->GetColorEntryAsRGB( i, asPixelColor + i ); 276 anPixelMapping[i] = i; 277 } 278 279 /* ==================================================================== */ 280 /* Iterate merging colors until we are under our limit (about 85). */ 281 /* ==================================================================== */ 282 while( nActiveColors > static_cast<int>( strlen(pszColorCodes) ) ) 283 { 284 int nClosestDistance = 768; 285 int iClose1 = -1; 286 int iClose2 = -1; 287 288 // Find the closest pair of colors. 289 for( int iColor1 = 0; iColor1 < nActiveColors; iColor1++ ) 290 { 291 for( int iColor2 = iColor1+1; iColor2 < nActiveColors; iColor2++ ) 292 { 293 int nDistance; 294 295 if( asPixelColor[iColor1].c4 < 128 296 && asPixelColor[iColor2].c4 < 128 ) 297 nDistance = 0; 298 else 299 nDistance = 300 std::abs(asPixelColor[iColor1].c1 - 301 asPixelColor[iColor2].c1) 302 + std::abs(asPixelColor[iColor1].c2 - 303 asPixelColor[iColor2].c2) 304 + std::abs(asPixelColor[iColor1].c3 - 305 asPixelColor[iColor2].c3); 306 307 if( nDistance < nClosestDistance ) 308 { 309 nClosestDistance = nDistance; 310 iClose1 = iColor1; 311 iClose2 = iColor2; 312 } 313 } 314 315 if( nClosestDistance < 8 ) 316 break; 317 } 318 319 // This should never happen! 320 if( iClose1 == -1 ) 321 break; 322 323 // Merge two selected colors - shift icolor2 into icolor1 and 324 // move the last active color into icolor2's slot. 325 for( int i = 0; i < 256; i++ ) 326 { 327 if( anPixelMapping[i] == iClose2 ) 328 anPixelMapping[i] = iClose1; 329 else if( anPixelMapping[i] == nActiveColors-1 ) 330 anPixelMapping[i] = iClose2; 331 } 332 333 asPixelColor[iClose2] = asPixelColor[nActiveColors-1]; 334 nActiveColors--; 335 } 336 337 /* ==================================================================== */ 338 /* Write the output image. */ 339 /* ==================================================================== */ 340 VSILFILE *fpPBM = VSIFOpenL( pszFilename, "wb+" ); 341 if( fpPBM == nullptr ) 342 { 343 CPLError( CE_Failure, CPLE_OpenFailed, 344 "Unable to create file `%s'.", 345 pszFilename ); 346 347 return nullptr; 348 } 349 350 /* -------------------------------------------------------------------- */ 351 /* Write the header lines. */ 352 /* -------------------------------------------------------------------- */ 353 bool bOK = VSIFPrintfL( fpPBM, "/* XPM */\n" ) >= 0; 354 bOK &= VSIFPrintfL( fpPBM, "static char *%s[] = {\n", 355 CPLGetBasename( pszFilename ) ) >= 0; 356 bOK &= VSIFPrintfL( fpPBM, "/* width height num_colors chars_per_pixel */\n" ) >= 0; 357 358 const int nXSize = poSrcDS->GetRasterXSize(); 359 const int nYSize = poSrcDS->GetRasterYSize(); 360 361 bOK &= VSIFPrintfL( fpPBM, "\" %3d %3d %3d 1\",\n", 362 nXSize, nYSize, nActiveColors ) >= 0; 363 364 bOK &= VSIFPrintfL( fpPBM, "/* colors */\n" ) >= 0; 365 366 /* -------------------------------------------------------------------- */ 367 /* Write the color table. */ 368 /* -------------------------------------------------------------------- */ 369 for( int i = 0; bOK && i < nActiveColors; i++ ) 370 { 371 if( asPixelColor[i].c4 < 128 ) 372 bOK &= VSIFPrintfL( fpPBM, "\"%c c None\",\n", pszColorCodes[i] ) >= 0; 373 else 374 bOK &= VSIFPrintfL( fpPBM, 375 "\"%c c #%02x%02x%02x\",\n", 376 pszColorCodes[i], 377 asPixelColor[i].c1, 378 asPixelColor[i].c2, 379 asPixelColor[i].c3 ) >= 0; 380 } 381 382 /* -------------------------------------------------------------------- */ 383 /* Dump image. */ 384 /* -------------------------------------------------------------------- */ 385 GByte *pabyScanline = reinterpret_cast<GByte *>( CPLMalloc( nXSize ) ); 386 387 for( int iLine = 0; bOK && iLine < nYSize; iLine++ ) 388 { 389 if( poBand->RasterIO( 390 GF_Read, 0, iLine, nXSize, 1, 391 reinterpret_cast<void *>( pabyScanline), nXSize, 1, GDT_Byte, 392 0, 0, nullptr ) != CE_None ) 393 { 394 CPLFree( pabyScanline ); 395 CPL_IGNORE_RET_VAL(VSIFCloseL( fpPBM )); 396 return nullptr; 397 } 398 399 bOK &= VSIFPutcL( '"', fpPBM ) >= 0; 400 for( int iPixel = 0; iPixel < nXSize; iPixel++ ) 401 bOK &= VSIFPutcL( pszColorCodes[anPixelMapping[pabyScanline[iPixel]]], 402 fpPBM) >= 0; 403 bOK &= VSIFPrintfL( fpPBM, "\",\n" ) >= 0; 404 } 405 406 CPLFree( pabyScanline ); 407 408 /* -------------------------------------------------------------------- */ 409 /* cleanup */ 410 /* -------------------------------------------------------------------- */ 411 bOK &= VSIFPrintfL( fpPBM, "};\n" ) >= 0; 412 if( VSIFCloseL( fpPBM ) != 0 ) 413 bOK = false; 414 415 if( !bOK ) 416 return nullptr; 417 418 /* -------------------------------------------------------------------- */ 419 /* Re-open dataset, and copy any auxiliary pam information. */ 420 /* -------------------------------------------------------------------- */ 421 GDALPamDataset *poDS = reinterpret_cast<GDALPamDataset *>( 422 GDALOpen( pszFilename, GA_ReadOnly ) ); 423 424 if( poDS ) 425 poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT ); 426 427 return poDS; 428 } 429 430 /************************************************************************/ 431 /* GDALRegister_XPM() */ 432 /************************************************************************/ 433 434 void GDALRegister_XPM() 435 436 { 437 if( GDALGetDriverByName( "XPM" ) != nullptr ) 438 return; 439 440 GDALDriver *poDriver = new GDALDriver(); 441 442 poDriver->SetDescription( "XPM" ); 443 poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" ); 444 poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "X11 PixMap Format" ); 445 poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/raster/xpm.html" ); 446 poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "xpm" ); 447 poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/x-xpixmap" ); 448 poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte" ); 449 poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" ); 450 451 poDriver->pfnOpen = XPMDataset::Open; 452 poDriver->pfnIdentify = XPMDataset::Identify; 453 poDriver->pfnCreateCopy = XPMCreateCopy; 454 455 GetGDALDriverManager()->RegisterDriver( poDriver ); 456 } 457 458 /************************************************************************/ 459 /* ParseXPM() */ 460 /************************************************************************/ 461 462 static unsigned char * 463 ParseXPM( const char *pszInput, 464 unsigned int nFileSize, 465 int *pnXSize, int *pnYSize, 466 GDALColorTable **ppoRetTable ) 467 468 { 469 /* ==================================================================== */ 470 /* Parse input into an array of strings from within the first C */ 471 /* initializer (list os comma separated strings in braces). */ 472 /* ==================================================================== */ 473 const char *pszNext = pszInput; 474 475 // Skip till after open brace. 476 while( *pszNext != '\0' && *pszNext != '{' ) 477 pszNext++; 478 479 if( *pszNext == '\0' ) 480 return nullptr; 481 482 pszNext++; 483 484 // Read lines till close brace. 485 486 char **papszXPMList = nullptr; 487 int i = 0; 488 489 while( *pszNext != '\0' && *pszNext != '}' ) 490 { 491 // skip whole comment. 492 if( STARTS_WITH_CI(pszNext, "/*") ) 493 { 494 pszNext += 2; 495 while( *pszNext != '\0' && !STARTS_WITH_CI(pszNext, "*/") ) 496 pszNext++; 497 } 498 499 // reading string constants 500 else if( *pszNext == '"' ) 501 { 502 pszNext++; 503 i = 0; 504 505 while( pszNext[i] != '\0' && pszNext[i] != '"' ) 506 i++; 507 508 if( pszNext[i] == '\0' ) 509 { 510 CSLDestroy( papszXPMList ); 511 return nullptr; 512 } 513 514 char *pszLine = reinterpret_cast<char *>( CPLMalloc(i+1) ); 515 strncpy( pszLine, pszNext, i ); 516 pszLine[i] = '\0'; 517 518 papszXPMList = CSLAddString( papszXPMList, pszLine ); 519 CPLFree( pszLine ); 520 pszNext = pszNext + i + 1; 521 } 522 523 // just ignore everything else (whitespace, commas, newlines, etc). 524 else 525 pszNext++; 526 } 527 528 if( papszXPMList == nullptr || CSLCount(papszXPMList) < 3 || *pszNext != '}' ) 529 { 530 CSLDestroy( papszXPMList ); 531 return nullptr; 532 } 533 534 /* -------------------------------------------------------------------- */ 535 /* Get the image information. */ 536 /* -------------------------------------------------------------------- */ 537 int nColorCount, nCharsPerPixel; 538 539 if( sscanf( papszXPMList[0], "%d %d %d %d", 540 pnXSize, pnYSize, &nColorCount, &nCharsPerPixel ) != 4 || 541 *pnXSize <= 0 || *pnYSize <= 0 || nColorCount <= 0 || nColorCount > 256 || 542 static_cast<GUIntBig>(*pnXSize) * static_cast<GUIntBig>(*pnYSize) > nFileSize ) 543 { 544 CPLError( CE_Failure, CPLE_AppDefined, 545 "Image definition (%s) not well formed.", 546 papszXPMList[0] ); 547 CSLDestroy( papszXPMList ); 548 return nullptr; 549 } 550 551 if( nCharsPerPixel != 1 ) 552 { 553 CPLError( CE_Failure, CPLE_AppDefined, 554 "Only one character per pixel XPM images supported by GDAL at this time." ); 555 CSLDestroy( papszXPMList ); 556 return nullptr; 557 } 558 559 /* -------------------------------------------------------------------- */ 560 /* Parse out colors. */ 561 /* -------------------------------------------------------------------- */ 562 int anCharLookup[256]; 563 GDALColorTable oCTable; 564 565 for( i = 0; i < 256; i++ ) 566 anCharLookup[i] = -1; 567 568 for( int iColor = 0; iColor < nColorCount; iColor++ ) 569 { 570 if( papszXPMList[iColor+1] == nullptr || 571 papszXPMList[iColor+1][0] == '\0' ) 572 { 573 CPLError( CE_Failure, CPLE_AppDefined, 574 "Missing color definition for %d in XPM header.", 575 iColor+1 ); 576 CSLDestroy( papszXPMList ); 577 return nullptr; 578 } 579 580 char **papszTokens = CSLTokenizeString( papszXPMList[iColor+1]+1 ); 581 582 if( CSLCount(papszTokens) != 2 || !EQUAL(papszTokens[0],"c") ) 583 { 584 CPLError( CE_Failure, CPLE_AppDefined, 585 "Ill formed color definition (%s) in XPM header.", 586 papszXPMList[iColor+1] ); 587 CSLDestroy( papszXPMList ); 588 CSLDestroy( papszTokens ); 589 return nullptr; 590 } 591 592 anCharLookup[*(reinterpret_cast<GByte*>(papszXPMList[iColor+1]))] = iColor; 593 594 GDALColorEntry sColor; 595 unsigned int nRed, nGreen, nBlue; 596 597 if( EQUAL(papszTokens[1],"None") ) 598 { 599 sColor.c1 = 0; 600 sColor.c2 = 0; 601 sColor.c3 = 0; 602 sColor.c4 = 0; 603 } 604 else if( sscanf( papszTokens[1], "#%02x%02x%02x", 605 &nRed, &nGreen, &nBlue ) != 3 ) 606 { 607 CPLError( CE_Failure, CPLE_AppDefined, 608 "Ill formed color definition (%s) in XPM header.", 609 papszXPMList[iColor+1] ); 610 CSLDestroy( papszXPMList ); 611 CSLDestroy( papszTokens ); 612 return nullptr; 613 } 614 else 615 { 616 sColor.c1 = static_cast<short>( nRed ); 617 sColor.c2 = static_cast<short>( nGreen ); 618 sColor.c3 = static_cast<short>( nBlue ); 619 sColor.c4 = 255; 620 } 621 622 oCTable.SetColorEntry( iColor, &sColor ); 623 624 CSLDestroy( papszTokens ); 625 } 626 627 /* -------------------------------------------------------------------- */ 628 /* Prepare image buffer. */ 629 /* -------------------------------------------------------------------- */ 630 GByte *pabyImage 631 = reinterpret_cast<GByte *>( VSI_CALLOC_VERBOSE(*pnXSize, *pnYSize) ); 632 if( pabyImage == nullptr ) 633 { 634 CSLDestroy( papszXPMList ); 635 return nullptr; 636 } 637 638 /* -------------------------------------------------------------------- */ 639 /* Parse image. */ 640 /* -------------------------------------------------------------------- */ 641 for( int iLine = 0; iLine < *pnYSize; iLine++ ) 642 { 643 const GByte *pabyInLine = reinterpret_cast<GByte*>( 644 papszXPMList[iLine + nColorCount + 1]); 645 646 if( pabyInLine == nullptr ) 647 { 648 CPLFree( pabyImage ); 649 CSLDestroy( papszXPMList ); 650 CPLError( CE_Failure, CPLE_AppDefined, 651 "Insufficient imagery lines in XPM image." ); 652 return nullptr; 653 } 654 655 for( int iPixel = 0; iPixel < *pnXSize; iPixel++ ) 656 { 657 if( pabyInLine[iPixel] == '\0' ) 658 break; 659 const int nPixelValue 660 = anCharLookup[pabyInLine[iPixel]]; 661 if( nPixelValue != -1 ) 662 pabyImage[iLine * *pnXSize + iPixel] 663 = static_cast<GByte>( nPixelValue ); 664 } 665 } 666 667 CSLDestroy( papszXPMList ); 668 669 *ppoRetTable = oCTable.Clone(); 670 671 return pabyImage; 672 } 673