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 = nullptr; // Image data file.
30 :
31 : bool bGeoTransformValid{};
32 : GDALGeoTransform m_gt{};
33 :
34 : CPL_DISALLOW_COPY_ASSIGN(PNMDataset)
35 :
36 : CPLErr Close() override;
37 :
38 : public:
39 69 : PNMDataset() = default;
40 : ~PNMDataset() override;
41 :
42 : CPLErr GetGeoTransform(GDALGeoTransform >) const 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 138 : PNMDataset::~PNMDataset()
56 :
57 : {
58 69 : PNMDataset::Close();
59 138 : }
60 :
61 : /************************************************************************/
62 : /* Close() */
63 : /************************************************************************/
64 :
65 135 : CPLErr PNMDataset::Close()
66 : {
67 135 : CPLErr eErr = CE_None;
68 135 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
69 : {
70 69 : if (PNMDataset::FlushCache(true) != CE_None)
71 0 : eErr = CE_Failure;
72 :
73 69 : if (fpImage)
74 : {
75 69 : if (VSIFCloseL(fpImage) != 0)
76 : {
77 0 : CPLError(CE_Failure, CPLE_FileIO, "I/O error");
78 0 : eErr = CE_Failure;
79 : }
80 : }
81 :
82 69 : if (GDALPamDataset::Close() != CE_None)
83 0 : eErr = CE_Failure;
84 : }
85 135 : return eErr;
86 : }
87 :
88 : /************************************************************************/
89 : /* GetGeoTransform() */
90 : /************************************************************************/
91 :
92 4 : CPLErr PNMDataset::GetGeoTransform(GDALGeoTransform >) const
93 :
94 : {
95 4 : if (bGeoTransformValid)
96 : {
97 0 : gt = m_gt;
98 0 : return CE_None;
99 : }
100 :
101 4 : return CE_Failure;
102 : }
103 :
104 : /************************************************************************/
105 : /* Identify() */
106 : /************************************************************************/
107 :
108 57889 : int PNMDataset::Identify(GDALOpenInfo *poOpenInfo)
109 :
110 : {
111 : /* -------------------------------------------------------------------- */
112 : /* Verify that this is a _raw_ ppm or pgm file. Note, we don't */
113 : /* support ascii files, or pbm (1bit) files. */
114 : /* -------------------------------------------------------------------- */
115 57889 : if (poOpenInfo->nHeaderBytes < 10 || poOpenInfo->fpL == nullptr)
116 54014 : return FALSE;
117 :
118 3875 : if (poOpenInfo->pabyHeader[0] != 'P' ||
119 259 : (poOpenInfo->pabyHeader[2] != ' ' && // XXX: Magick number
120 259 : poOpenInfo->pabyHeader[2] != '\t' && // may be followed
121 259 : poOpenInfo->pabyHeader[2] != '\n' && // any of the blank
122 144 : poOpenInfo->pabyHeader[2] != '\r')) // characters
123 3760 : return FALSE;
124 :
125 115 : if (poOpenInfo->pabyHeader[1] != '5' && poOpenInfo->pabyHeader[1] != '6')
126 0 : return FALSE;
127 :
128 115 : return TRUE;
129 : }
130 :
131 : /************************************************************************/
132 : /* Open() */
133 : /************************************************************************/
134 :
135 69 : GDALDataset *PNMDataset::Open(GDALOpenInfo *poOpenInfo)
136 :
137 : {
138 : /* -------------------------------------------------------------------- */
139 : /* Verify that this is a _raw_ ppm or pgm file. Note, we don't */
140 : /* support ascii files, or pbm (1bit) files. */
141 : /* -------------------------------------------------------------------- */
142 69 : if (!Identify(poOpenInfo))
143 0 : return nullptr;
144 :
145 : /* -------------------------------------------------------------------- */
146 : /* Parse out the tokens from the header. */
147 : /* -------------------------------------------------------------------- */
148 69 : const char *pszSrc = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
149 69 : char szToken[512] = {'\0'};
150 69 : int iToken = 0;
151 69 : int nWidth = -1;
152 69 : int nHeight = -1;
153 69 : int nMaxValue = -1;
154 :
155 69 : int iIn = 2;
156 276 : while (iIn < poOpenInfo->nHeaderBytes && iToken < 3)
157 : {
158 207 : unsigned int iOut = 0;
159 207 : szToken[0] = '\0';
160 811 : while (iOut < sizeof(szToken) && iIn < poOpenInfo->nHeaderBytes)
161 : {
162 811 : if (pszSrc[iIn] == '#')
163 : {
164 0 : while (iIn < poOpenInfo->nHeaderBytes - 1 &&
165 0 : pszSrc[iIn] != 10 && pszSrc[iIn] != 13)
166 0 : iIn++;
167 : }
168 :
169 811 : if (iOut != 0 && isspace(static_cast<unsigned char>(pszSrc[iIn])))
170 : {
171 207 : szToken[iOut] = '\0';
172 :
173 207 : if (iToken == 0)
174 69 : nWidth = atoi(szToken);
175 138 : else if (iToken == 1)
176 69 : nHeight = atoi(szToken);
177 69 : else if (iToken == 2)
178 69 : nMaxValue = atoi(szToken);
179 :
180 207 : iToken++;
181 207 : iIn++;
182 207 : break;
183 : }
184 :
185 604 : else if (!isspace(static_cast<unsigned char>(pszSrc[iIn])))
186 : {
187 535 : szToken[iOut++] = pszSrc[iIn];
188 : }
189 :
190 604 : iIn++;
191 : }
192 : }
193 :
194 69 : CPLDebug("PNM", "PNM header contains: width=%d, height=%d, maxval=%d",
195 : nWidth, nHeight, nMaxValue);
196 :
197 69 : if (iToken != 3 || nWidth < 1 || nHeight < 1 || nMaxValue < 1)
198 0 : return nullptr;
199 :
200 : /* -------------------------------------------------------------------- */
201 : /* Create a corresponding GDALDataset. */
202 : /* -------------------------------------------------------------------- */
203 138 : auto poDS = std::make_unique<PNMDataset>();
204 :
205 : /* -------------------------------------------------------------------- */
206 : /* Capture some information from the file that is of interest. */
207 : /* -------------------------------------------------------------------- */
208 69 : poDS->nRasterXSize = nWidth;
209 69 : poDS->nRasterYSize = nHeight;
210 :
211 : // Borrow file pointer
212 69 : std::swap(poDS->fpImage, poOpenInfo->fpL);
213 :
214 69 : poDS->eAccess = poOpenInfo->eAccess;
215 :
216 : /* -------------------------------------------------------------------- */
217 : /* Create band information objects. */
218 : /* -------------------------------------------------------------------- */
219 :
220 69 : GDALDataType eDataType = GDT_Unknown;
221 69 : if (nMaxValue < 256)
222 55 : eDataType = GDT_Byte;
223 : else
224 14 : eDataType = GDT_UInt16;
225 :
226 69 : const int iPixelSize = GDALGetDataTypeSizeBytes(eDataType);
227 :
228 69 : if (poOpenInfo->pabyHeader[1] == '5')
229 : {
230 52 : if (nWidth > INT_MAX / iPixelSize)
231 : {
232 0 : CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
233 0 : return nullptr;
234 : }
235 : auto poBand = RawRasterBand::Create(
236 104 : poDS.get(), 1, poDS->fpImage, iIn, iPixelSize, nWidth * iPixelSize,
237 : eDataType, RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
238 52 : RawRasterBand::OwnFP::NO);
239 52 : if (!poBand)
240 0 : return nullptr;
241 52 : poBand->SetColorInterpretation(GCI_GrayIndex);
242 52 : poDS->SetBand(1, std::move(poBand));
243 : }
244 : else
245 : {
246 17 : if (nWidth > INT_MAX / (3 * iPixelSize))
247 : {
248 0 : CPLError(CE_Failure, CPLE_AppDefined, "Int overflow occurred.");
249 0 : return nullptr;
250 : }
251 68 : for (int i = 0; i < 3; ++i)
252 : {
253 : auto poBand = RawRasterBand::Create(
254 102 : poDS.get(), i + 1, poDS->fpImage, iIn + i * iPixelSize,
255 51 : 3 * iPixelSize, nWidth * 3 * iPixelSize, eDataType,
256 : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
257 51 : RawRasterBand::OwnFP::NO);
258 51 : if (!poBand)
259 0 : return nullptr;
260 51 : poBand->SetColorInterpretation(
261 51 : static_cast<GDALColorInterp>(GCI_RedBand + i));
262 51 : poDS->SetBand(i + 1, std::move(poBand));
263 : }
264 : }
265 :
266 : /* -------------------------------------------------------------------- */
267 : /* Check for world file. */
268 : /* -------------------------------------------------------------------- */
269 138 : poDS->bGeoTransformValid = CPL_TO_BOOL(
270 138 : GDALReadWorldFile(poOpenInfo->pszFilename, ".wld", poDS->m_gt.data()));
271 :
272 : /* -------------------------------------------------------------------- */
273 : /* Initialize any PAM information. */
274 : /* -------------------------------------------------------------------- */
275 69 : poDS->SetDescription(poOpenInfo->pszFilename);
276 69 : poDS->TryLoadXML();
277 :
278 : /* -------------------------------------------------------------------- */
279 : /* Check for overviews. */
280 : /* -------------------------------------------------------------------- */
281 69 : poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
282 :
283 69 : return poDS.release();
284 : }
285 :
286 : /************************************************************************/
287 : /* Create() */
288 : /************************************************************************/
289 :
290 65 : GDALDataset *PNMDataset::Create(const char *pszFilename, int nXSize, int nYSize,
291 : int nBandsIn, GDALDataType eType,
292 : char **papszOptions)
293 :
294 : {
295 : /* -------------------------------------------------------------------- */
296 : /* Verify input options. */
297 : /* -------------------------------------------------------------------- */
298 65 : if (eType != GDT_Byte && eType != GDT_UInt16)
299 : {
300 33 : CPLError(CE_Failure, CPLE_AppDefined,
301 : "Attempt to create PNM dataset with an illegal "
302 : "data type (%s), only Byte and UInt16 supported.",
303 : GDALGetDataTypeName(eType));
304 :
305 33 : return nullptr;
306 : }
307 :
308 32 : if (nBandsIn != 1 && nBandsIn != 3)
309 : {
310 7 : CPLError(CE_Failure, CPLE_AppDefined,
311 : "Attempt to create PNM dataset with an illegal number"
312 : "of bands (%d). Must be 1 (greyscale) or 3 (RGB).",
313 : nBandsIn);
314 :
315 7 : return nullptr;
316 : }
317 50 : const CPLString osExt(CPLGetExtensionSafe(pszFilename));
318 25 : if (nBandsIn == 1)
319 : {
320 18 : if (!EQUAL(osExt, "PGM"))
321 : {
322 13 : CPLError(CE_Warning, CPLE_AppDefined,
323 : "Extension for a 1-band netpbm file should be .pgm");
324 : }
325 : }
326 : else /* if( nBands == 3 ) */
327 : {
328 7 : if (!EQUAL(osExt, "PPM"))
329 : {
330 5 : CPLError(CE_Warning, CPLE_AppDefined,
331 : "Extension for a 3-band netpbm file should be .ppm");
332 : }
333 : }
334 :
335 : /* -------------------------------------------------------------------- */
336 : /* Try to create the file. */
337 : /* -------------------------------------------------------------------- */
338 25 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
339 25 : if (fp == nullptr)
340 : {
341 2 : CPLError(CE_Failure, CPLE_OpenFailed,
342 : "Attempt to create file `%s' failed.", pszFilename);
343 2 : return nullptr;
344 : }
345 :
346 : /* -------------------------------------------------------------------- */
347 : /* Write out the header. */
348 : /* -------------------------------------------------------------------- */
349 23 : int nMaxValue = 0;
350 :
351 23 : const char *pszMaxValue = CSLFetchNameValue(papszOptions, "MAXVAL");
352 23 : if (pszMaxValue)
353 : {
354 0 : nMaxValue = atoi(pszMaxValue);
355 0 : if (eType == GDT_Byte && (nMaxValue > 255 || nMaxValue < 0))
356 0 : nMaxValue = 255;
357 0 : else if (nMaxValue > 65535 || nMaxValue < 0)
358 0 : nMaxValue = 65535;
359 : }
360 : else
361 : {
362 23 : if (eType == GDT_Byte)
363 18 : nMaxValue = 255;
364 : else
365 5 : nMaxValue = 65535;
366 : }
367 :
368 23 : char szHeader[500] = {'\0'};
369 :
370 23 : if (nBandsIn == 3)
371 7 : snprintf(szHeader, sizeof(szHeader), "P6\n%d %d\n%d\n", nXSize, nYSize,
372 : nMaxValue);
373 : else
374 16 : snprintf(szHeader, sizeof(szHeader), "P5\n%d %d\n%d\n", nXSize, nYSize,
375 : nMaxValue);
376 :
377 23 : bool bOK = VSIFWriteL(szHeader, strlen(szHeader) + 2, 1, fp) == 1;
378 23 : if (VSIFCloseL(fp) != 0)
379 0 : bOK = false;
380 :
381 23 : if (!bOK)
382 0 : return nullptr;
383 :
384 46 : GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
385 23 : return Open(&oOpenInfo);
386 : }
387 :
388 : /************************************************************************/
389 : /* GDALRegister_PNM() */
390 : /************************************************************************/
391 :
392 1935 : void GDALRegister_PNM()
393 :
394 : {
395 1935 : if (GDALGetDriverByName("PNM") != nullptr)
396 282 : return;
397 :
398 1653 : GDALDriver *poDriver = new GDALDriver();
399 :
400 1653 : poDriver->SetDescription("PNM");
401 1653 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
402 1653 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
403 1653 : "Portable Pixmap Format (netpbm)");
404 1653 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/pnm.html");
405 : // pgm : grey
406 : // ppm : RGB
407 : // pnm : ??
408 1653 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "pgm ppm pnm");
409 1653 : poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/x-portable-anymap");
410 1653 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Byte UInt16");
411 1653 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
412 : "<CreationOptionList>"
413 : " <Option name='MAXVAL' type='unsigned int' "
414 : "description='Maximum color value'/>"
415 1653 : "</CreationOptionList>");
416 1653 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
417 :
418 1653 : poDriver->pfnOpen = PNMDataset::Open;
419 1653 : poDriver->pfnCreate = PNMDataset::Create;
420 1653 : poDriver->pfnIdentify = PNMDataset::Identify;
421 :
422 1653 : GetGDALDriverManager()->RegisterDriver(poDriver);
423 : }
|