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 >) 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 >) 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 : }
|