LCOV - code coverage report
Current view: top level - frmts/raw - pnmdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 142 167 85.0 %
Date: 2026-02-04 11:14:01 Functions: 10 10 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(GDALProgressFunc = nullptr, void * = nullptr) 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 *OpenInternal(GDALOpenInfo *, bool bInCreation);
      48             :     static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
      49             :                                int nBandsIn, GDALDataType eType,
      50             :                                CSLConstList papszOptions);
      51             : };
      52             : 
      53             : /************************************************************************/
      54             : /*                            ~PNMDataset()                             */
      55             : /************************************************************************/
      56             : 
      57         136 : PNMDataset::~PNMDataset()
      58             : 
      59             : {
      60          68 :     PNMDataset::Close();
      61         136 : }
      62             : 
      63             : /************************************************************************/
      64             : /*                               Close()                                */
      65             : /************************************************************************/
      66             : 
      67         133 : CPLErr PNMDataset::Close(GDALProgressFunc, void *)
      68             : {
      69         133 :     CPLErr eErr = CE_None;
      70         133 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
      71             :     {
      72          68 :         if (PNMDataset::FlushCache(true) != CE_None)
      73           0 :             eErr = CE_Failure;
      74             : 
      75          68 :         if (fpImage)
      76             :         {
      77          68 :             if (VSIFCloseL(fpImage) != 0)
      78             :             {
      79           0 :                 CPLError(CE_Failure, CPLE_FileIO, "I/O error");
      80           0 :                 eErr = CE_Failure;
      81             :             }
      82             :         }
      83             : 
      84          68 :         if (GDALPamDataset::Close() != CE_None)
      85           0 :             eErr = CE_Failure;
      86             :     }
      87         133 :     return eErr;
      88             : }
      89             : 
      90             : /************************************************************************/
      91             : /*                          GetGeoTransform()                           */
      92             : /************************************************************************/
      93             : 
      94           4 : CPLErr PNMDataset::GetGeoTransform(GDALGeoTransform &gt) const
      95             : 
      96             : {
      97           4 :     if (bGeoTransformValid)
      98             :     {
      99           0 :         gt = m_gt;
     100           0 :         return CE_None;
     101             :     }
     102             : 
     103           4 :     return CE_Failure;
     104             : }
     105             : 
     106             : /************************************************************************/
     107             : /*                              Identify()                              */
     108             : /************************************************************************/
     109             : 
     110       62085 : int PNMDataset::Identify(GDALOpenInfo *poOpenInfo)
     111             : 
     112             : {
     113             :     /* -------------------------------------------------------------------- */
     114             :     /*      Verify that this is a _raw_ ppm or pgm file.  Note, we don't    */
     115             :     /*      support ascii files, or pbm (1bit) files.                       */
     116             :     /* -------------------------------------------------------------------- */
     117       62085 :     if (poOpenInfo->nHeaderBytes < 10 || poOpenInfo->fpL == nullptr)
     118       56952 :         return FALSE;
     119             : 
     120        5133 :     if (poOpenInfo->pabyHeader[0] != 'P' ||
     121         258 :         (poOpenInfo->pabyHeader[2] != ' ' &&   // XXX: Magick number
     122         258 :          poOpenInfo->pabyHeader[2] != '\t' &&  // may be followed
     123         258 :          poOpenInfo->pabyHeader[2] != '\n' &&  // any of the blank
     124         145 :          poOpenInfo->pabyHeader[2] != '\r'))   // characters
     125        5020 :         return FALSE;
     126             : 
     127         113 :     if (poOpenInfo->pabyHeader[1] != '5' && poOpenInfo->pabyHeader[1] != '6')
     128           0 :         return FALSE;
     129             : 
     130         113 :     return TRUE;
     131             : }
     132             : 
     133             : /************************************************************************/
     134             : /*                                Open()                                */
     135             : /************************************************************************/
     136             : 
     137          45 : GDALDataset *PNMDataset::Open(GDALOpenInfo *poOpenInfo)
     138             : 
     139             : {
     140          45 :     return OpenInternal(poOpenInfo, false);
     141             : }
     142             : 
     143             : /************************************************************************/
     144             : /*                            OpenInternal()                            */
     145             : /************************************************************************/
     146             : 
     147          68 : GDALDataset *PNMDataset::OpenInternal(GDALOpenInfo *poOpenInfo,
     148             :                                       bool bInCreation)
     149             : 
     150             : {
     151             :     /* -------------------------------------------------------------------- */
     152             :     /*      Verify that this is a _raw_ ppm or pgm file.  Note, we don't    */
     153             :     /*      support ascii files, or pbm (1bit) files.                       */
     154             :     /* -------------------------------------------------------------------- */
     155          68 :     if (!Identify(poOpenInfo))
     156           0 :         return nullptr;
     157             : 
     158             :     /* -------------------------------------------------------------------- */
     159             :     /*      Parse out the tokens from the header.                           */
     160             :     /* -------------------------------------------------------------------- */
     161          68 :     const char *pszSrc = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
     162          68 :     char szToken[512] = {'\0'};
     163          68 :     int iToken = 0;
     164          68 :     int nWidth = -1;
     165          68 :     int nHeight = -1;
     166          68 :     int nMaxValue = -1;
     167             : 
     168          68 :     int iIn = 2;
     169         272 :     while (iIn < poOpenInfo->nHeaderBytes && iToken < 3)
     170             :     {
     171         204 :         unsigned int iOut = 0;
     172         204 :         szToken[0] = '\0';
     173         799 :         while (iOut < sizeof(szToken) && iIn < poOpenInfo->nHeaderBytes)
     174             :         {
     175         799 :             if (pszSrc[iIn] == '#')
     176             :             {
     177           0 :                 while (iIn < poOpenInfo->nHeaderBytes - 1 &&
     178           0 :                        pszSrc[iIn] != 10 && pszSrc[iIn] != 13)
     179           0 :                     iIn++;
     180             :             }
     181             : 
     182         799 :             if (iOut != 0 && isspace(static_cast<unsigned char>(pszSrc[iIn])))
     183             :             {
     184         204 :                 szToken[iOut] = '\0';
     185             : 
     186         204 :                 if (iToken == 0)
     187          68 :                     nWidth = atoi(szToken);
     188         136 :                 else if (iToken == 1)
     189          68 :                     nHeight = atoi(szToken);
     190          68 :                 else if (iToken == 2)
     191          68 :                     nMaxValue = atoi(szToken);
     192             : 
     193         204 :                 iToken++;
     194         204 :                 iIn++;
     195         204 :                 break;
     196             :             }
     197             : 
     198         595 :             else if (!isspace(static_cast<unsigned char>(pszSrc[iIn])))
     199             :             {
     200         527 :                 szToken[iOut++] = pszSrc[iIn];
     201             :             }
     202             : 
     203         595 :             iIn++;
     204             :         }
     205             :     }
     206             : 
     207          68 :     CPLDebug("PNM", "PNM header contains: width=%d, height=%d, maxval=%d",
     208             :              nWidth, nHeight, nMaxValue);
     209             : 
     210          68 :     if (iToken != 3 || nWidth < 1 || nHeight < 1 || nMaxValue < 1)
     211           0 :         return nullptr;
     212             : 
     213             :     /* -------------------------------------------------------------------- */
     214             :     /*      Create a corresponding GDALDataset.                             */
     215             :     /* -------------------------------------------------------------------- */
     216         136 :     auto poDS = std::make_unique<PNMDataset>();
     217             : 
     218             :     /* -------------------------------------------------------------------- */
     219             :     /*      Capture some information from the file that is of interest.     */
     220             :     /* -------------------------------------------------------------------- */
     221          68 :     poDS->nRasterXSize = nWidth;
     222          68 :     poDS->nRasterYSize = nHeight;
     223             : 
     224             :     // Borrow file pointer
     225          68 :     std::swap(poDS->fpImage, poOpenInfo->fpL);
     226             : 
     227          68 :     poDS->eAccess = poOpenInfo->eAccess;
     228             : 
     229             :     /* -------------------------------------------------------------------- */
     230             :     /*      Create band information objects.                                */
     231             :     /* -------------------------------------------------------------------- */
     232             : 
     233          68 :     GDALDataType eDataType = GDT_Unknown;
     234          68 :     if (nMaxValue < 256)
     235          54 :         eDataType = GDT_UInt8;
     236             :     else
     237          14 :         eDataType = GDT_UInt16;
     238             : 
     239          68 :     const int iPixelSize = GDALGetDataTypeSizeBytes(eDataType);
     240             : 
     241          68 :     if (poOpenInfo->pabyHeader[1] == '5')
     242             :     {
     243          51 :         if (nWidth > INT_MAX / iPixelSize)
     244             :         {
     245           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
     246           0 :             return nullptr;
     247             :         }
     248             :         auto poBand = RawRasterBand::Create(
     249         102 :             poDS.get(), 1, poDS->fpImage, iIn, iPixelSize, nWidth * iPixelSize,
     250             :             eDataType, RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
     251          51 :             RawRasterBand::OwnFP::NO);
     252          51 :         if (!poBand)
     253           0 :             return nullptr;
     254          51 :         poBand->SetColorInterpretation(GCI_GrayIndex);
     255          51 :         poDS->SetBand(1, std::move(poBand));
     256             :     }
     257             :     else
     258             :     {
     259          17 :         if (nWidth > INT_MAX / (3 * iPixelSize))
     260             :         {
     261           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
     262           0 :             return nullptr;
     263             :         }
     264          68 :         for (int i = 0; i < 3; ++i)
     265             :         {
     266             :             auto poBand = RawRasterBand::Create(
     267         102 :                 poDS.get(), i + 1, poDS->fpImage, iIn + i * iPixelSize,
     268          51 :                 3 * iPixelSize, nWidth * 3 * iPixelSize, eDataType,
     269             :                 RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
     270          51 :                 RawRasterBand::OwnFP::NO);
     271          51 :             if (!poBand)
     272           0 :                 return nullptr;
     273          51 :             poBand->SetTruncatedFileAllowed(bInCreation);
     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         136 :     poDS->bGeoTransformValid = CPL_TO_BOOL(
     284         136 :         GDALReadWorldFile(poOpenInfo->pszFilename, ".wld", poDS->m_gt.data()));
     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             :                                 CSLConstList papszOptions)
     307             : 
     308             : {
     309             :     /* -------------------------------------------------------------------- */
     310             :     /*      Verify input options.                                           */
     311             :     /* -------------------------------------------------------------------- */
     312          65 :     if (eType != GDT_UInt8 && 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(CPLGetExtensionSafe(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_UInt8 && (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_UInt8)
     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 OpenInternal(&oOpenInfo, true);
     400             : }
     401             : 
     402             : /************************************************************************/
     403             : /*                          GDALRegister_PNM()                          */
     404             : /************************************************************************/
     405             : 
     406        2059 : void GDALRegister_PNM()
     407             : 
     408             : {
     409        2059 :     if (GDALGetDriverByName("PNM") != nullptr)
     410         283 :         return;
     411             : 
     412        1776 :     GDALDriver *poDriver = new GDALDriver();
     413             : 
     414        1776 :     poDriver->SetDescription("PNM");
     415        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     416        1776 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
     417        1776 :                               "Portable Pixmap Format (netpbm)");
     418        1776 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/pnm.html");
     419             :     // pgm : grey
     420             :     // ppm : RGB
     421             :     // pnm : ??
     422        1776 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "pgm ppm pnm");
     423        1776 :     poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/x-portable-anymap");
     424        1776 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte UInt16");
     425        1776 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
     426             :                               "<CreationOptionList>"
     427             :                               "   <Option name='MAXVAL' type='unsigned int' "
     428             :                               "description='Maximum color value'/>"
     429        1776 :                               "</CreationOptionList>");
     430        1776 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     431             : 
     432        1776 :     poDriver->pfnOpen = PNMDataset::Open;
     433        1776 :     poDriver->pfnCreate = PNMDataset::Create;
     434        1776 :     poDriver->pfnIdentify = PNMDataset::Identify;
     435             : 
     436        1776 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     437             : }

Generated by: LCOV version 1.14