LCOV - code coverage report
Current view: top level - frmts/raw - pnmdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 146 171 85.4 %
Date: 2024-11-21 22:18:42 Functions: 9 9 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  PNM Driver
       4             :  * Purpose:  Portable anymap file format implementation
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2001, Frank Warmerdam
       9             :  * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_string.h"
      15             : #include "gdal_frmts.h"
      16             : #include "rawdataset.h"
      17             : 
      18             : #include <algorithm>
      19             : #include <cctype>
      20             : 
      21             : /************************************************************************/
      22             : /* ==================================================================== */
      23             : /*                              PNMDataset                              */
      24             : /* ==================================================================== */
      25             : /************************************************************************/
      26             : 
      27             : class PNMDataset final : public RawDataset
      28             : {
      29             :     VSILFILE *fpImage;  // Image data file.
      30             : 
      31             :     bool bGeoTransformValid;
      32             :     double adfGeoTransform[6];
      33             : 
      34             :     CPL_DISALLOW_COPY_ASSIGN(PNMDataset)
      35             : 
      36             :     CPLErr Close() override;
      37             : 
      38             :   public:
      39             :     PNMDataset();
      40             :     ~PNMDataset() override;
      41             : 
      42             :     CPLErr GetGeoTransform(double *) override;
      43             : 
      44             :     static int Identify(GDALOpenInfo *);
      45             :     static GDALDataset *Open(GDALOpenInfo *);
      46             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
      47             :                                int nBandsIn, GDALDataType eType,
      48             :                                char **papszOptions);
      49             : };
      50             : 
      51             : /************************************************************************/
      52             : /*                            PNMDataset()                             */
      53             : /************************************************************************/
      54             : 
      55          68 : PNMDataset::PNMDataset() : fpImage(nullptr), bGeoTransformValid(false)
      56             : {
      57          68 :     adfGeoTransform[0] = 0.0;
      58          68 :     adfGeoTransform[1] = 1.0;
      59          68 :     adfGeoTransform[2] = 0.0;
      60          68 :     adfGeoTransform[3] = 0.0;
      61          68 :     adfGeoTransform[4] = 0.0;
      62          68 :     adfGeoTransform[5] = 1.0;
      63          68 : }
      64             : 
      65             : /************************************************************************/
      66             : /*                            ~PNMDataset()                            */
      67             : /************************************************************************/
      68             : 
      69         136 : PNMDataset::~PNMDataset()
      70             : 
      71             : {
      72          68 :     PNMDataset::Close();
      73         136 : }
      74             : 
      75             : /************************************************************************/
      76             : /*                              Close()                                 */
      77             : /************************************************************************/
      78             : 
      79         133 : CPLErr PNMDataset::Close()
      80             : {
      81         133 :     CPLErr eErr = CE_None;
      82         133 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
      83             :     {
      84          68 :         if (PNMDataset::FlushCache(true) != CE_None)
      85           0 :             eErr = CE_Failure;
      86             : 
      87          68 :         if (fpImage)
      88             :         {
      89          68 :             if (VSIFCloseL(fpImage) != 0)
      90             :             {
      91           0 :                 CPLError(CE_Failure, CPLE_FileIO, "I/O error");
      92           0 :                 eErr = CE_Failure;
      93             :             }
      94             :         }
      95             : 
      96          68 :         if (GDALPamDataset::Close() != CE_None)
      97           0 :             eErr = CE_Failure;
      98             :     }
      99         133 :     return eErr;
     100             : }
     101             : 
     102             : /************************************************************************/
     103             : /*                          GetGeoTransform()                           */
     104             : /************************************************************************/
     105             : 
     106           4 : CPLErr PNMDataset::GetGeoTransform(double *padfTransform)
     107             : 
     108             : {
     109           4 :     if (bGeoTransformValid)
     110             :     {
     111           0 :         memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
     112           0 :         return CE_None;
     113             :     }
     114             : 
     115           4 :     return CE_Failure;
     116             : }
     117             : 
     118             : /************************************************************************/
     119             : /*                              Identify()                              */
     120             : /************************************************************************/
     121             : 
     122       51895 : int PNMDataset::Identify(GDALOpenInfo *poOpenInfo)
     123             : 
     124             : {
     125             :     /* -------------------------------------------------------------------- */
     126             :     /*      Verify that this is a _raw_ ppm or pgm file.  Note, we don't    */
     127             :     /*      support ascii files, or pbm (1bit) files.                       */
     128             :     /* -------------------------------------------------------------------- */
     129       51895 :     if (poOpenInfo->nHeaderBytes < 10 || poOpenInfo->fpL == nullptr)
     130       48111 :         return FALSE;
     131             : 
     132        3784 :     if (poOpenInfo->pabyHeader[0] != 'P' ||
     133         253 :         (poOpenInfo->pabyHeader[2] != ' ' &&   // XXX: Magick number
     134         253 :          poOpenInfo->pabyHeader[2] != '\t' &&  // may be followed
     135         253 :          poOpenInfo->pabyHeader[2] != '\n' &&  // any of the blank
     136         140 :          poOpenInfo->pabyHeader[2] != '\r'))   // characters
     137        3671 :         return FALSE;
     138             : 
     139         113 :     if (poOpenInfo->pabyHeader[1] != '5' && poOpenInfo->pabyHeader[1] != '6')
     140           0 :         return FALSE;
     141             : 
     142         113 :     return TRUE;
     143             : }
     144             : 
     145             : /************************************************************************/
     146             : /*                                Open()                                */
     147             : /************************************************************************/
     148             : 
     149          68 : GDALDataset *PNMDataset::Open(GDALOpenInfo *poOpenInfo)
     150             : 
     151             : {
     152             :     /* -------------------------------------------------------------------- */
     153             :     /*      Verify that this is a _raw_ ppm or pgm file.  Note, we don't    */
     154             :     /*      support ascii files, or pbm (1bit) files.                       */
     155             :     /* -------------------------------------------------------------------- */
     156          68 :     if (!Identify(poOpenInfo))
     157           0 :         return nullptr;
     158             : 
     159             :     /* -------------------------------------------------------------------- */
     160             :     /*      Parse out the tokens from the header.                           */
     161             :     /* -------------------------------------------------------------------- */
     162          68 :     const char *pszSrc = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
     163          68 :     char szToken[512] = {'\0'};
     164          68 :     int iToken = 0;
     165          68 :     int nWidth = -1;
     166          68 :     int nHeight = -1;
     167          68 :     int nMaxValue = -1;
     168             : 
     169          68 :     int iIn = 2;
     170         272 :     while (iIn < poOpenInfo->nHeaderBytes && iToken < 3)
     171             :     {
     172         204 :         unsigned int iOut = 0;
     173         204 :         szToken[0] = '\0';
     174         784 :         while (iOut < sizeof(szToken) && iIn < poOpenInfo->nHeaderBytes)
     175             :         {
     176         784 :             if (pszSrc[iIn] == '#')
     177             :             {
     178           0 :                 while (iIn < poOpenInfo->nHeaderBytes - 1 &&
     179           0 :                        pszSrc[iIn] != 10 && pszSrc[iIn] != 13)
     180           0 :                     iIn++;
     181             :             }
     182             : 
     183         784 :             if (iOut != 0 && isspace(static_cast<unsigned char>(pszSrc[iIn])))
     184             :             {
     185         204 :                 szToken[iOut] = '\0';
     186             : 
     187         204 :                 if (iToken == 0)
     188          68 :                     nWidth = atoi(szToken);
     189         136 :                 else if (iToken == 1)
     190          68 :                     nHeight = atoi(szToken);
     191          68 :                 else if (iToken == 2)
     192          68 :                     nMaxValue = atoi(szToken);
     193             : 
     194         204 :                 iToken++;
     195         204 :                 iIn++;
     196         204 :                 break;
     197             :             }
     198             : 
     199         580 :             else if (!isspace(static_cast<unsigned char>(pszSrc[iIn])))
     200             :             {
     201         512 :                 szToken[iOut++] = pszSrc[iIn];
     202             :             }
     203             : 
     204         580 :             iIn++;
     205             :         }
     206             :     }
     207             : 
     208          68 :     CPLDebug("PNM", "PNM header contains: width=%d, height=%d, maxval=%d",
     209             :              nWidth, nHeight, nMaxValue);
     210             : 
     211          68 :     if (iToken != 3 || nWidth < 1 || nHeight < 1 || nMaxValue < 1)
     212           0 :         return nullptr;
     213             : 
     214             :     /* -------------------------------------------------------------------- */
     215             :     /*      Create a corresponding GDALDataset.                             */
     216             :     /* -------------------------------------------------------------------- */
     217         136 :     auto poDS = std::make_unique<PNMDataset>();
     218             : 
     219             :     /* -------------------------------------------------------------------- */
     220             :     /*      Capture some information from the file that is of interest.     */
     221             :     /* -------------------------------------------------------------------- */
     222          68 :     poDS->nRasterXSize = nWidth;
     223          68 :     poDS->nRasterYSize = nHeight;
     224             : 
     225             :     // Borrow file pointer
     226          68 :     std::swap(poDS->fpImage, poOpenInfo->fpL);
     227             : 
     228          68 :     poDS->eAccess = poOpenInfo->eAccess;
     229             : 
     230             :     /* -------------------------------------------------------------------- */
     231             :     /*      Create band information objects.                                */
     232             :     /* -------------------------------------------------------------------- */
     233             : 
     234          68 :     GDALDataType eDataType = GDT_Unknown;
     235          68 :     if (nMaxValue < 256)
     236          54 :         eDataType = GDT_Byte;
     237             :     else
     238          14 :         eDataType = GDT_UInt16;
     239             : 
     240          68 :     const int iPixelSize = GDALGetDataTypeSizeBytes(eDataType);
     241             : 
     242          68 :     if (poOpenInfo->pabyHeader[1] == '5')
     243             :     {
     244          51 :         if (nWidth > INT_MAX / iPixelSize)
     245             :         {
     246           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
     247           0 :             return nullptr;
     248             :         }
     249             :         auto poBand = RawRasterBand::Create(
     250         102 :             poDS.get(), 1, poDS->fpImage, iIn, iPixelSize, nWidth * iPixelSize,
     251             :             eDataType, RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
     252          51 :             RawRasterBand::OwnFP::NO);
     253          51 :         if (!poBand)
     254           0 :             return nullptr;
     255          51 :         poBand->SetColorInterpretation(GCI_GrayIndex);
     256          51 :         poDS->SetBand(1, std::move(poBand));
     257             :     }
     258             :     else
     259             :     {
     260          17 :         if (nWidth > INT_MAX / (3 * iPixelSize))
     261             :         {
     262           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
     263           0 :             return nullptr;
     264             :         }
     265          68 :         for (int i = 0; i < 3; ++i)
     266             :         {
     267             :             auto poBand = RawRasterBand::Create(
     268         102 :                 poDS.get(), i + 1, poDS->fpImage, iIn + i * iPixelSize,
     269          51 :                 3 * iPixelSize, nWidth * 3 * iPixelSize, eDataType,
     270             :                 RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
     271          51 :                 RawRasterBand::OwnFP::NO);
     272          51 :             if (!poBand)
     273           0 :                 return nullptr;
     274          51 :             poBand->SetColorInterpretation(
     275          51 :                 static_cast<GDALColorInterp>(GCI_RedBand + i));
     276          51 :             poDS->SetBand(i + 1, std::move(poBand));
     277             :         }
     278             :     }
     279             : 
     280             :     /* -------------------------------------------------------------------- */
     281             :     /*      Check for world file.                                           */
     282             :     /* -------------------------------------------------------------------- */
     283          68 :     poDS->bGeoTransformValid = CPL_TO_BOOL(GDALReadWorldFile(
     284          68 :         poOpenInfo->pszFilename, ".wld", poDS->adfGeoTransform));
     285             : 
     286             :     /* -------------------------------------------------------------------- */
     287             :     /*      Initialize any PAM information.                                 */
     288             :     /* -------------------------------------------------------------------- */
     289          68 :     poDS->SetDescription(poOpenInfo->pszFilename);
     290          68 :     poDS->TryLoadXML();
     291             : 
     292             :     /* -------------------------------------------------------------------- */
     293             :     /*      Check for overviews.                                            */
     294             :     /* -------------------------------------------------------------------- */
     295          68 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
     296             : 
     297          68 :     return poDS.release();
     298             : }
     299             : 
     300             : /************************************************************************/
     301             : /*                               Create()                               */
     302             : /************************************************************************/
     303             : 
     304          65 : GDALDataset *PNMDataset::Create(const char *pszFilename, int nXSize, int nYSize,
     305             :                                 int nBandsIn, GDALDataType eType,
     306             :                                 char **papszOptions)
     307             : 
     308             : {
     309             :     /* -------------------------------------------------------------------- */
     310             :     /*      Verify input options.                                           */
     311             :     /* -------------------------------------------------------------------- */
     312          65 :     if (eType != GDT_Byte && eType != GDT_UInt16)
     313             :     {
     314          33 :         CPLError(CE_Failure, CPLE_AppDefined,
     315             :                  "Attempt to create PNM dataset with an illegal "
     316             :                  "data type (%s), only Byte and UInt16 supported.",
     317             :                  GDALGetDataTypeName(eType));
     318             : 
     319          33 :         return nullptr;
     320             :     }
     321             : 
     322          32 :     if (nBandsIn != 1 && nBandsIn != 3)
     323             :     {
     324           7 :         CPLError(CE_Failure, CPLE_AppDefined,
     325             :                  "Attempt to create PNM dataset with an illegal number"
     326             :                  "of bands (%d).  Must be 1 (greyscale) or 3 (RGB).",
     327             :                  nBandsIn);
     328             : 
     329           7 :         return nullptr;
     330             :     }
     331          50 :     const CPLString osExt(CPLGetExtension(pszFilename));
     332          25 :     if (nBandsIn == 1)
     333             :     {
     334          18 :         if (!EQUAL(osExt, "PGM"))
     335             :         {
     336          13 :             CPLError(CE_Warning, CPLE_AppDefined,
     337             :                      "Extension for a 1-band netpbm file should be .pgm");
     338             :         }
     339             :     }
     340             :     else /* if( nBands == 3 ) */
     341             :     {
     342           7 :         if (!EQUAL(osExt, "PPM"))
     343             :         {
     344           5 :             CPLError(CE_Warning, CPLE_AppDefined,
     345             :                      "Extension for a 3-band netpbm file should be .ppm");
     346             :         }
     347             :     }
     348             : 
     349             :     /* -------------------------------------------------------------------- */
     350             :     /*      Try to create the file.                                         */
     351             :     /* -------------------------------------------------------------------- */
     352          25 :     VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
     353          25 :     if (fp == nullptr)
     354             :     {
     355           2 :         CPLError(CE_Failure, CPLE_OpenFailed,
     356             :                  "Attempt to create file `%s' failed.", pszFilename);
     357           2 :         return nullptr;
     358             :     }
     359             : 
     360             :     /* -------------------------------------------------------------------- */
     361             :     /*      Write out the header.                                           */
     362             :     /* -------------------------------------------------------------------- */
     363          23 :     int nMaxValue = 0;
     364             : 
     365          23 :     const char *pszMaxValue = CSLFetchNameValue(papszOptions, "MAXVAL");
     366          23 :     if (pszMaxValue)
     367             :     {
     368           0 :         nMaxValue = atoi(pszMaxValue);
     369           0 :         if (eType == GDT_Byte && (nMaxValue > 255 || nMaxValue < 0))
     370           0 :             nMaxValue = 255;
     371           0 :         else if (nMaxValue > 65535 || nMaxValue < 0)
     372           0 :             nMaxValue = 65535;
     373             :     }
     374             :     else
     375             :     {
     376          23 :         if (eType == GDT_Byte)
     377          18 :             nMaxValue = 255;
     378             :         else
     379           5 :             nMaxValue = 65535;
     380             :     }
     381             : 
     382          23 :     char szHeader[500] = {'\0'};
     383             : 
     384          23 :     if (nBandsIn == 3)
     385           7 :         snprintf(szHeader, sizeof(szHeader), "P6\n%d %d\n%d\n", nXSize, nYSize,
     386             :                  nMaxValue);
     387             :     else
     388          16 :         snprintf(szHeader, sizeof(szHeader), "P5\n%d %d\n%d\n", nXSize, nYSize,
     389             :                  nMaxValue);
     390             : 
     391          23 :     bool bOK = VSIFWriteL(szHeader, strlen(szHeader) + 2, 1, fp) == 1;
     392          23 :     if (VSIFCloseL(fp) != 0)
     393           0 :         bOK = false;
     394             : 
     395          23 :     if (!bOK)
     396           0 :         return nullptr;
     397             : 
     398          46 :     GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
     399          23 :     return Open(&oOpenInfo);
     400             : }
     401             : 
     402             : /************************************************************************/
     403             : /*                         GDALRegister_PNM()                           */
     404             : /************************************************************************/
     405             : 
     406        1595 : void GDALRegister_PNM()
     407             : 
     408             : {
     409        1595 :     if (GDALGetDriverByName("PNM") != nullptr)
     410         302 :         return;
     411             : 
     412        1293 :     GDALDriver *poDriver = new GDALDriver();
     413             : 
     414        1293 :     poDriver->SetDescription("PNM");
     415        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     416        1293 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
     417        1293 :                               "Portable Pixmap Format (netpbm)");
     418        1293 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/pnm.html");
     419             :     // pgm : grey
     420             :     // ppm : RGB
     421             :     // pnm : ??
     422        1293 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "pgm ppm pnm");
     423        1293 :     poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/x-portable-anymap");
     424        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte UInt16");
     425        1293 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
     426             :                               "<CreationOptionList>"
     427             :                               "   <Option name='MAXVAL' type='unsigned int' "
     428             :                               "description='Maximum color value'/>"
     429        1293 :                               "</CreationOptionList>");
     430        1293 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     431             : 
     432        1293 :     poDriver->pfnOpen = PNMDataset::Open;
     433        1293 :     poDriver->pfnCreate = PNMDataset::Create;
     434        1293 :     poDriver->pfnIdentify = PNMDataset::Identify;
     435             : 
     436        1293 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     437             : }

Generated by: LCOV version 1.14