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