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

Generated by: LCOV version 1.14