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

Generated by: LCOV version 1.14