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