Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: PNM Driver
4 : * Purpose: Portable anymap file format implementation
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2001, Frank Warmerdam
9 : * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_string.h"
15 : #include "gdal_frmts.h"
16 : #include "rawdataset.h"
17 :
18 : #include <algorithm>
19 : #include <cctype>
20 :
21 : /************************************************************************/
22 : /* ==================================================================== */
23 : /* PNMDataset */
24 : /* ==================================================================== */
25 : /************************************************************************/
26 :
27 : class PNMDataset final : public RawDataset
28 : {
29 : VSILFILE *fpImage; // Image data file.
30 :
31 : bool bGeoTransformValid;
32 : double adfGeoTransform[6];
33 :
34 : CPL_DISALLOW_COPY_ASSIGN(PNMDataset)
35 :
36 : CPLErr Close() override;
37 :
38 : public:
39 : PNMDataset();
40 : ~PNMDataset() override;
41 :
42 : CPLErr GetGeoTransform(double *) override;
43 :
44 : static int Identify(GDALOpenInfo *);
45 : static GDALDataset *Open(GDALOpenInfo *);
46 : static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
47 : int nBandsIn, GDALDataType eType,
48 : char **papszOptions);
49 : };
50 :
51 : /************************************************************************/
52 : /* PNMDataset() */
53 : /************************************************************************/
54 :
55 68 : PNMDataset::PNMDataset() : fpImage(nullptr), bGeoTransformValid(false)
56 : {
57 68 : adfGeoTransform[0] = 0.0;
58 68 : adfGeoTransform[1] = 1.0;
59 68 : adfGeoTransform[2] = 0.0;
60 68 : adfGeoTransform[3] = 0.0;
61 68 : adfGeoTransform[4] = 0.0;
62 68 : adfGeoTransform[5] = 1.0;
63 68 : }
64 :
65 : /************************************************************************/
66 : /* ~PNMDataset() */
67 : /************************************************************************/
68 :
69 136 : PNMDataset::~PNMDataset()
70 :
71 : {
72 68 : PNMDataset::Close();
73 136 : }
74 :
75 : /************************************************************************/
76 : /* Close() */
77 : /************************************************************************/
78 :
79 133 : CPLErr PNMDataset::Close()
80 : {
81 133 : CPLErr eErr = CE_None;
82 133 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
83 : {
84 68 : if (PNMDataset::FlushCache(true) != CE_None)
85 0 : eErr = CE_Failure;
86 :
87 68 : if (fpImage)
88 : {
89 68 : if (VSIFCloseL(fpImage) != 0)
90 : {
91 0 : CPLError(CE_Failure, CPLE_FileIO, "I/O error");
92 0 : eErr = CE_Failure;
93 : }
94 : }
95 :
96 68 : if (GDALPamDataset::Close() != CE_None)
97 0 : eErr = CE_Failure;
98 : }
99 133 : return eErr;
100 : }
101 :
102 : /************************************************************************/
103 : /* GetGeoTransform() */
104 : /************************************************************************/
105 :
106 4 : CPLErr PNMDataset::GetGeoTransform(double *padfTransform)
107 :
108 : {
109 4 : if (bGeoTransformValid)
110 : {
111 0 : memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
112 0 : return CE_None;
113 : }
114 :
115 4 : return CE_Failure;
116 : }
117 :
118 : /************************************************************************/
119 : /* Identify() */
120 : /************************************************************************/
121 :
122 51895 : int PNMDataset::Identify(GDALOpenInfo *poOpenInfo)
123 :
124 : {
125 : /* -------------------------------------------------------------------- */
126 : /* Verify that this is a _raw_ ppm or pgm file. Note, we don't */
127 : /* support ascii files, or pbm (1bit) files. */
128 : /* -------------------------------------------------------------------- */
129 51895 : if (poOpenInfo->nHeaderBytes < 10 || poOpenInfo->fpL == nullptr)
130 48111 : return FALSE;
131 :
132 3784 : if (poOpenInfo->pabyHeader[0] != 'P' ||
133 253 : (poOpenInfo->pabyHeader[2] != ' ' && // XXX: Magick number
134 253 : poOpenInfo->pabyHeader[2] != '\t' && // may be followed
135 253 : poOpenInfo->pabyHeader[2] != '\n' && // any of the blank
136 140 : poOpenInfo->pabyHeader[2] != '\r')) // characters
137 3671 : return FALSE;
138 :
139 113 : if (poOpenInfo->pabyHeader[1] != '5' && poOpenInfo->pabyHeader[1] != '6')
140 0 : return FALSE;
141 :
142 113 : return TRUE;
143 : }
144 :
145 : /************************************************************************/
146 : /* Open() */
147 : /************************************************************************/
148 :
149 68 : GDALDataset *PNMDataset::Open(GDALOpenInfo *poOpenInfo)
150 :
151 : {
152 : /* -------------------------------------------------------------------- */
153 : /* Verify that this is a _raw_ ppm or pgm file. Note, we don't */
154 : /* support ascii files, or pbm (1bit) files. */
155 : /* -------------------------------------------------------------------- */
156 68 : if (!Identify(poOpenInfo))
157 0 : return nullptr;
158 :
159 : /* -------------------------------------------------------------------- */
160 : /* Parse out the tokens from the header. */
161 : /* -------------------------------------------------------------------- */
162 68 : const char *pszSrc = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
163 68 : char szToken[512] = {'\0'};
164 68 : int iToken = 0;
165 68 : int nWidth = -1;
166 68 : int nHeight = -1;
167 68 : int nMaxValue = -1;
168 :
169 68 : int iIn = 2;
170 272 : while (iIn < poOpenInfo->nHeaderBytes && iToken < 3)
171 : {
172 204 : unsigned int iOut = 0;
173 204 : szToken[0] = '\0';
174 784 : while (iOut < sizeof(szToken) && iIn < poOpenInfo->nHeaderBytes)
175 : {
176 784 : if (pszSrc[iIn] == '#')
177 : {
178 0 : while (iIn < poOpenInfo->nHeaderBytes - 1 &&
179 0 : pszSrc[iIn] != 10 && pszSrc[iIn] != 13)
180 0 : iIn++;
181 : }
182 :
183 784 : if (iOut != 0 && isspace(static_cast<unsigned char>(pszSrc[iIn])))
184 : {
185 204 : szToken[iOut] = '\0';
186 :
187 204 : if (iToken == 0)
188 68 : nWidth = atoi(szToken);
189 136 : else if (iToken == 1)
190 68 : nHeight = atoi(szToken);
191 68 : else if (iToken == 2)
192 68 : nMaxValue = atoi(szToken);
193 :
194 204 : iToken++;
195 204 : iIn++;
196 204 : break;
197 : }
198 :
199 580 : else if (!isspace(static_cast<unsigned char>(pszSrc[iIn])))
200 : {
201 512 : szToken[iOut++] = pszSrc[iIn];
202 : }
203 :
204 580 : iIn++;
205 : }
206 : }
207 :
208 68 : CPLDebug("PNM", "PNM header contains: width=%d, height=%d, maxval=%d",
209 : nWidth, nHeight, nMaxValue);
210 :
211 68 : if (iToken != 3 || nWidth < 1 || nHeight < 1 || nMaxValue < 1)
212 0 : return nullptr;
213 :
214 : /* -------------------------------------------------------------------- */
215 : /* Create a corresponding GDALDataset. */
216 : /* -------------------------------------------------------------------- */
217 136 : auto poDS = std::make_unique<PNMDataset>();
218 :
219 : /* -------------------------------------------------------------------- */
220 : /* Capture some information from the file that is of interest. */
221 : /* -------------------------------------------------------------------- */
222 68 : poDS->nRasterXSize = nWidth;
223 68 : poDS->nRasterYSize = nHeight;
224 :
225 : // Borrow file pointer
226 68 : std::swap(poDS->fpImage, poOpenInfo->fpL);
227 :
228 68 : poDS->eAccess = poOpenInfo->eAccess;
229 :
230 : /* -------------------------------------------------------------------- */
231 : /* Create band information objects. */
232 : /* -------------------------------------------------------------------- */
233 :
234 68 : GDALDataType eDataType = GDT_Unknown;
235 68 : if (nMaxValue < 256)
236 54 : eDataType = GDT_Byte;
237 : else
238 14 : eDataType = GDT_UInt16;
239 :
240 68 : const int iPixelSize = GDALGetDataTypeSizeBytes(eDataType);
241 :
242 68 : if (poOpenInfo->pabyHeader[1] == '5')
243 : {
244 51 : if (nWidth > INT_MAX / iPixelSize)
245 : {
246 0 : CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
247 0 : return nullptr;
248 : }
249 : auto poBand = RawRasterBand::Create(
250 102 : poDS.get(), 1, poDS->fpImage, iIn, iPixelSize, nWidth * iPixelSize,
251 : eDataType, RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
252 51 : RawRasterBand::OwnFP::NO);
253 51 : if (!poBand)
254 0 : return nullptr;
255 51 : poBand->SetColorInterpretation(GCI_GrayIndex);
256 51 : poDS->SetBand(1, std::move(poBand));
257 : }
258 : else
259 : {
260 17 : if (nWidth > INT_MAX / (3 * iPixelSize))
261 : {
262 0 : CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
263 0 : return nullptr;
264 : }
265 68 : for (int i = 0; i < 3; ++i)
266 : {
267 : auto poBand = RawRasterBand::Create(
268 102 : poDS.get(), i + 1, poDS->fpImage, iIn + i * iPixelSize,
269 51 : 3 * iPixelSize, nWidth * 3 * iPixelSize, eDataType,
270 : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
271 51 : RawRasterBand::OwnFP::NO);
272 51 : if (!poBand)
273 0 : return nullptr;
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 68 : poDS->bGeoTransformValid = CPL_TO_BOOL(GDALReadWorldFile(
284 68 : poOpenInfo->pszFilename, ".wld", poDS->adfGeoTransform));
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 : char **papszOptions)
307 :
308 : {
309 : /* -------------------------------------------------------------------- */
310 : /* Verify input options. */
311 : /* -------------------------------------------------------------------- */
312 65 : if (eType != GDT_Byte && 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(CPLGetExtension(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_Byte && (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_Byte)
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 Open(&oOpenInfo);
400 : }
401 :
402 : /************************************************************************/
403 : /* GDALRegister_PNM() */
404 : /************************************************************************/
405 :
406 1595 : void GDALRegister_PNM()
407 :
408 : {
409 1595 : if (GDALGetDriverByName("PNM") != nullptr)
410 302 : return;
411 :
412 1293 : GDALDriver *poDriver = new GDALDriver();
413 :
414 1293 : poDriver->SetDescription("PNM");
415 1293 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
416 1293 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
417 1293 : "Portable Pixmap Format (netpbm)");
418 1293 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/pnm.html");
419 : // pgm : grey
420 : // ppm : RGB
421 : // pnm : ??
422 1293 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "pgm ppm pnm");
423 1293 : poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/x-portable-anymap");
424 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte UInt16");
425 1293 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
426 : "<CreationOptionList>"
427 : " <Option name='MAXVAL' type='unsigned int' "
428 : "description='Maximum color value'/>"
429 1293 : "</CreationOptionList>");
430 1293 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
431 :
432 1293 : poDriver->pfnOpen = PNMDataset::Open;
433 1293 : poDriver->pfnCreate = PNMDataset::Create;
434 1293 : poDriver->pfnIdentify = PNMDataset::Identify;
435 :
436 1293 : GetGDALDriverManager()->RegisterDriver(poDriver);
437 : }
|