1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4
5 #include <cstdlib>
6 #include <fstream>
7 #include <string>
8
9 #include <OpenImageIO/filesystem.h>
10 #include <OpenImageIO/fmath.h>
11 #include <OpenImageIO/imageio.h>
12
13 OIIO_PLUGIN_NAMESPACE_BEGIN
14
15 //
16 // Documentation on the PNM formats can be found at:
17 // http://netpbm.sourceforge.net/doc/pbm.html (B&W)
18 // http://netpbm.sourceforge.net/doc/ppm.html (grey)
19 // http://netpbm.sourceforge.net/doc/pgm.html (color)
20 // http://netpbm.sourceforge.net/doc/pam.html (base format)
21 //
22
23
24 class PNMInput final : public ImageInput {
25 public:
PNMInput()26 PNMInput() {}
~PNMInput()27 virtual ~PNMInput() { close(); }
format_name(void) const28 virtual const char* format_name(void) const override { return "pnm"; }
29 virtual bool open(const std::string& name, ImageSpec& newspec) override;
30 virtual bool close() override;
current_subimage(void) const31 virtual int current_subimage(void) const override { return 0; }
32 virtual bool read_native_scanline(int subimage, int miplevel, int y, int z,
33 void* data) override;
34
35 private:
36 enum PNMType { P1, P2, P3, P4, P5, P6, Pf, PF };
37
38 OIIO::ifstream m_file;
39 std::streampos m_header_end_pos; // file position after the header
40 std::string m_current_line; ///< Buffer the image pixels
41 const char* m_pos;
42 PNMType m_pnm_type;
43 unsigned int m_max_val;
44 float m_scaling_factor;
45
46 bool read_file_scanline(void* data, int y);
47 bool read_file_header();
48 };
49
50
51
52 // Obligatory material to make this a recognizeable imageio plugin:
53 OIIO_PLUGIN_EXPORTS_BEGIN
54
55 OIIO_EXPORT ImageInput*
pnm_input_imageio_create()56 pnm_input_imageio_create()
57 {
58 return new PNMInput;
59 }
60
61 OIIO_EXPORT int pnm_imageio_version = OIIO_PLUGIN_VERSION;
62
63 OIIO_EXPORT const char*
pnm_imageio_library_version()64 pnm_imageio_library_version()
65 {
66 return nullptr;
67 }
68
69 OIIO_EXPORT const char* pnm_input_extensions[] = { "ppm", "pgm", "pbm",
70 "pnm", "pfm", nullptr };
71
72 OIIO_PLUGIN_EXPORTS_END
73
74
75 inline bool
nextLine(std::istream & file,std::string & current_line,const char * & pos)76 nextLine(std::istream& file, std::string& current_line, const char*& pos)
77 {
78 if (!file.good())
79 return false;
80 getline(file, current_line);
81 if (file.fail())
82 return false;
83 pos = current_line.c_str();
84 return true;
85 }
86
87
88
89 inline const char*
nextToken(std::istream & file,std::string & current_line,const char * & pos)90 nextToken(std::istream& file, std::string& current_line, const char*& pos)
91 {
92 while (1) {
93 while (isspace(*pos))
94 pos++;
95 if (*pos)
96 break;
97 else
98 nextLine(file, current_line, pos);
99 }
100 return pos;
101 }
102
103
104
105 inline const char*
skipComments(std::istream & file,std::string & current_line,const char * & pos,char comment='#')106 skipComments(std::istream& file, std::string& current_line, const char*& pos,
107 char comment = '#')
108 {
109 while (1) {
110 nextToken(file, current_line, pos);
111 if (*pos == comment)
112 nextLine(file, current_line, pos);
113 else
114 break;
115 }
116 return pos;
117 }
118
119
120
121 inline bool
nextVal(std::istream & file,std::string & current_line,const char * & pos,int & val,char comment='#')122 nextVal(std::istream& file, std::string& current_line, const char*& pos,
123 int& val, char comment = '#')
124 {
125 skipComments(file, current_line, pos, comment);
126 if (!isdigit(*pos))
127 return false;
128 val = strtol(pos, (char**)&pos, 10);
129 return true;
130 }
131
132
133
134 template<class T>
135 inline void
invert(const T * read,T * write,imagesize_t nvals)136 invert(const T* read, T* write, imagesize_t nvals)
137 {
138 for (imagesize_t i = 0; i < nvals; i++)
139 write[i] = std::numeric_limits<T>::max() - read[i];
140 }
141
142
143
144 template<class T>
145 inline bool
ascii_to_raw(std::istream & file,std::string & current_line,const char * & pos,T * write,imagesize_t nvals,T max)146 ascii_to_raw(std::istream& file, std::string& current_line, const char*& pos,
147 T* write, imagesize_t nvals, T max)
148 {
149 if (max)
150 for (imagesize_t i = 0; i < nvals; i++) {
151 int tmp;
152 if (!nextVal(file, current_line, pos, tmp))
153 return false;
154 write[i] = std::min((int)max, tmp) * std::numeric_limits<T>::max()
155 / max;
156 }
157 else
158 for (imagesize_t i = 0; i < nvals; i++)
159 write[i] = std::numeric_limits<T>::max();
160 return true;
161 }
162
163
164
165 template<class T>
166 inline void
raw_to_raw(const T * read,T * write,imagesize_t nvals,T max)167 raw_to_raw(const T* read, T* write, imagesize_t nvals, T max)
168 {
169 if (max)
170 for (imagesize_t i = 0; i < nvals; i++) {
171 int tmp = read[i];
172 write[i] = std::min((int)max, tmp) * std::numeric_limits<T>::max()
173 / max;
174 }
175 else
176 for (imagesize_t i = 0; i < nvals; i++)
177 write[i] = std::numeric_limits<T>::max();
178 }
179
180
181
182 inline void
unpack(const unsigned char * read,unsigned char * write,imagesize_t size)183 unpack(const unsigned char* read, unsigned char* write, imagesize_t size)
184 {
185 imagesize_t w = 0, r = 0;
186 unsigned char bit = 0x7, byte = 0;
187 for (imagesize_t x = 0; x < size; x++) {
188 if (bit == 0x7)
189 byte = ~read[r++];
190 write[w++] = 0 - ((byte & (1 << bit)) >> bit); //assign expanded bit
191 bit = (bit - 1) & 0x7; // limit bit to [0; 8[
192 }
193 }
194
195 inline void
unpack_floats(const unsigned char * read,float * write,imagesize_t numsamples,float scaling_factor)196 unpack_floats(const unsigned char* read, float* write, imagesize_t numsamples,
197 float scaling_factor)
198 {
199 float* read_floats = (float*)read;
200
201 if ((scaling_factor < 0 && bigendian())
202 || (scaling_factor > 0 && littleendian())) {
203 swap_endian(read_floats, numsamples);
204 }
205
206 float absfactor = fabs(scaling_factor);
207 for (imagesize_t i = 0; i < numsamples; i++) {
208 write[i] = absfactor * read_floats[i];
209 }
210 }
211
212
213
214 template<class T>
215 inline bool
read_int(std::istream & in,T & dest,char comment='#')216 read_int(std::istream& in, T& dest, char comment = '#')
217 {
218 T ret;
219 char c;
220 while (!in.eof()) {
221 in >> ret;
222 if (!in.good()) {
223 in.clear();
224 in >> c;
225 if (c == comment)
226 in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
227 else
228 return false;
229 } else {
230 dest = ret;
231 return true;
232 }
233 }
234 return false;
235 }
236
237
238
239 bool
read_file_scanline(void * data,int y)240 PNMInput::read_file_scanline(void* data, int y)
241 {
242 try {
243 std::vector<unsigned char> buf;
244 bool good = true;
245 if (!m_file)
246 return false;
247 int nsamples = m_spec.width * m_spec.nchannels;
248
249 // PFM files are bottom-to-top, so we need to seek to the right spot
250 if (m_pnm_type == PF || m_pnm_type == Pf) {
251 int file_scanline = m_spec.height - 1 - (y - m_spec.y);
252 std::streampos offset = file_scanline * m_spec.scanline_bytes();
253 m_file.seekg(m_header_end_pos + offset, std::ios_base::beg);
254 }
255
256 if ((m_pnm_type >= P4 && m_pnm_type <= P6) || m_pnm_type == PF
257 || m_pnm_type == Pf) {
258 int numbytes;
259 if (m_pnm_type == P4)
260 numbytes = (m_spec.width + 7) / 8;
261 else if (m_pnm_type == PF || m_pnm_type == Pf)
262 numbytes = m_spec.nchannels * 4 * m_spec.width;
263 else
264 numbytes = m_spec.scanline_bytes();
265 buf.resize(numbytes);
266 m_file.read((char*)&buf[0], numbytes);
267 if (!m_file.good())
268 return false;
269 }
270
271 switch (m_pnm_type) {
272 //Ascii
273 case P1:
274 good &= ascii_to_raw(m_file, m_current_line, m_pos,
275 (unsigned char*)data, nsamples,
276 (unsigned char)m_max_val);
277 invert((unsigned char*)data, (unsigned char*)data, nsamples);
278 break;
279 case P2:
280 case P3:
281 if (m_max_val > std::numeric_limits<unsigned char>::max())
282 good &= ascii_to_raw(m_file, m_current_line, m_pos,
283 (unsigned short*)data, nsamples,
284 (unsigned short)m_max_val);
285 else
286 good &= ascii_to_raw(m_file, m_current_line, m_pos,
287 (unsigned char*)data, nsamples,
288 (unsigned char)m_max_val);
289 break;
290 //Raw
291 case P4: unpack(&buf[0], (unsigned char*)data, nsamples); break;
292 case P5:
293 case P6:
294 if (m_max_val > std::numeric_limits<unsigned char>::max()) {
295 if (littleendian())
296 swap_endian((unsigned short*)&buf[0], nsamples);
297 raw_to_raw((unsigned short*)&buf[0], (unsigned short*)data,
298 nsamples, (unsigned short)m_max_val);
299 } else {
300 raw_to_raw((unsigned char*)&buf[0], (unsigned char*)data,
301 nsamples, (unsigned char)m_max_val);
302 }
303 break;
304 //Floating point
305 case Pf:
306 case PF:
307 unpack_floats(&buf[0], (float*)data, nsamples, m_scaling_factor);
308 break;
309 default: return false;
310 }
311 return good;
312
313 } catch (const std::exception& e) {
314 errorf("PNM exception: %s", e.what());
315 return false;
316 }
317 }
318
319
320
321 bool
read_file_header()322 PNMInput::read_file_header()
323 {
324 try {
325 unsigned int width, height;
326 char c;
327 if (!m_file)
328 return false;
329
330 //MagicNumber
331 m_file >> c;
332 if (c != 'P')
333 return false;
334
335 m_file >> c;
336 switch (c) {
337 case '1': m_pnm_type = P1; break;
338 case '2': m_pnm_type = P2; break;
339 case '3': m_pnm_type = P3; break;
340 case '4': m_pnm_type = P4; break;
341 case '5': m_pnm_type = P5; break;
342 case '6': m_pnm_type = P6; break;
343 case 'f': m_pnm_type = Pf; break;
344 case 'F': m_pnm_type = PF; break;
345 default: return false;
346 }
347
348 //Size
349 if (!read_int(m_file, width))
350 return false;
351 if (!read_int(m_file, height))
352 return false;
353
354 if (m_pnm_type != PF && m_pnm_type != Pf) {
355 //Max Val
356 if (m_pnm_type != P1 && m_pnm_type != P4) {
357 if (!read_int(m_file, m_max_val))
358 return false;
359 } else
360 m_max_val = 1;
361
362 //Space before content
363 if (!(isspace(m_file.get()) && m_file.good()))
364 return false;
365 m_header_end_pos = m_file.tellg(); // remember file pos
366
367 if (m_pnm_type == P3 || m_pnm_type == P6)
368 m_spec = ImageSpec(width, height, 3,
369 (m_max_val > 255) ? TypeDesc::UINT16
370 : TypeDesc::UINT8);
371 else
372 m_spec = ImageSpec(width, height, 1,
373 (m_max_val > 255) ? TypeDesc::UINT16
374 : TypeDesc::UINT8);
375
376 if (m_spec.nchannels == 1)
377 m_spec.channelnames[0] = "I";
378 else
379 m_spec.default_channel_names();
380
381 if (m_pnm_type >= P1 && m_pnm_type <= P3)
382 m_spec.attribute("pnm:binary", 0);
383 else
384 m_spec.attribute("pnm:binary", 1);
385
386 m_spec.attribute("oiio:BitsPerSample",
387 ceilf(logf(m_max_val + 1) / logf(2)));
388 return true;
389 } else {
390 //Read scaling factor
391 if (!read_int(m_file, m_scaling_factor)) {
392 return false;
393 }
394
395 //Space before content
396 if (!(isspace(m_file.get()) && m_file.good()))
397 return false;
398 m_header_end_pos = m_file.tellg(); // remember file pos
399
400 if (m_pnm_type == PF) {
401 m_spec = ImageSpec(width, height, 3, TypeDesc::FLOAT);
402 m_spec.default_channel_names();
403 } else {
404 m_spec = ImageSpec(width, height, 1, TypeDesc::FLOAT);
405 m_spec.channelnames[0] = "I";
406 }
407
408 if (m_scaling_factor < 0) {
409 m_spec.attribute("pnm:bigendian", 0);
410 } else {
411 m_spec.attribute("pnm:bigendian", 1);
412 }
413
414 return true;
415 }
416 } catch (const std::exception& e) {
417 errorf("PNM exception: %s", e.what());
418 return false;
419 }
420 }
421
422
423
424 bool
open(const std::string & name,ImageSpec & newspec)425 PNMInput::open(const std::string& name, ImageSpec& newspec)
426 {
427 close(); //close previously opened file
428
429 Filesystem::open(m_file, name, std::ios::in | std::ios::binary);
430
431 m_current_line = "";
432 m_pos = m_current_line.c_str();
433
434 if (!read_file_header())
435 return false;
436
437 newspec = m_spec;
438 return true;
439 }
440
441
442
443 bool
close()444 PNMInput::close()
445 {
446 m_file.close();
447 return true;
448 }
449
450
451
452 bool
read_native_scanline(int subimage,int miplevel,int y,int z,void * data)453 PNMInput::read_native_scanline(int subimage, int miplevel, int y, int z,
454 void* data)
455 {
456 lock_guard lock(m_mutex);
457 if (!seek_subimage(subimage, miplevel))
458 return false;
459
460 if (z)
461 return false;
462 if (!read_file_scanline(data, y))
463 return false;
464 return true;
465 }
466
467 OIIO_PLUGIN_NAMESPACE_END
468