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