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

Generated by: LCOV version 1.14