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 <fstream>
6 
7 #include <OpenImageIO/filesystem.h>
8 #include <OpenImageIO/imageio.h>
9 
10 OIIO_PLUGIN_NAMESPACE_BEGIN
11 
12 
13 class PNMOutput final : public ImageOutput {
14 public:
15     virtual ~PNMOutput();
format_name(void) const16     virtual const char* format_name(void) const override { return "pnm"; }
17     virtual bool open(const std::string& name, const ImageSpec& spec,
18                       OpenMode mode = Create) override;
19     virtual bool close() override;
20     virtual bool write_scanline(int y, int z, TypeDesc format, const void* data,
21                                 stride_t xstride) override;
22     virtual bool write_tile(int x, int y, int z, TypeDesc format,
23                             const void* data, stride_t xstride,
24                             stride_t ystride, stride_t zstride) override;
25 
26 private:
27     std::string m_filename;  ///< Stash the filename
28     OIIO::ofstream m_file;
29     unsigned int m_max_val, m_pnm_type;
30     unsigned int m_dither;
31     std::vector<unsigned char> m_scratch;
32     std::vector<unsigned char> m_tilebuffer;
33 };
34 
35 
36 
37 // Obligatory material to make this a recognizeable imageio plugin:
38 OIIO_PLUGIN_EXPORTS_BEGIN
39 
40 OIIO_EXPORT ImageOutput*
pnm_output_imageio_create()41 pnm_output_imageio_create()
42 {
43     return new PNMOutput;
44 }
45 
46 OIIO_EXPORT const char* pnm_output_extensions[] = { "ppm", "pgm", "pbm", "pnm",
47                                                     nullptr };
48 
49 OIIO_PLUGIN_EXPORTS_END
50 
51 
52 inline void
write_ascii_binary(std::ostream & file,const unsigned char * data,const stride_t stride,const ImageSpec & spec)53 write_ascii_binary(std::ostream& file, const unsigned char* data,
54                    const stride_t stride, const ImageSpec& spec)
55 {
56     for (int x = 0; x < spec.width; x++)
57         file << (data[x * stride] ? '1' : '0') << "\n";
58 }
59 
60 
61 
62 inline void
write_raw_binary(std::ostream & file,const unsigned char * data,const stride_t stride,const ImageSpec & spec)63 write_raw_binary(std::ostream& file, const unsigned char* data,
64                  const stride_t stride, const ImageSpec& spec)
65 {
66     unsigned char val;
67     for (int x = 0; x < spec.width;) {
68         val = 0;
69         for (int bit = 7; bit >= 0 && x < spec.width; x++, bit--)
70             val += (data[x * stride] ? (1 << bit) : 0);
71         file.write((char*)&val, sizeof(char));
72     }
73 }
74 
75 
76 
77 template<class T>
78 inline void
write_ascii(std::ostream & file,const T * data,const stride_t stride,const ImageSpec & spec,unsigned int max_val)79 write_ascii(std::ostream& file, const T* data, const stride_t stride,
80             const ImageSpec& spec, unsigned int max_val)
81 {
82     unsigned int pixel, val;
83     for (int x = 0; x < spec.width; x++) {
84         pixel = x * stride;
85         for (int c = 0; c < spec.nchannels; c++) {
86             val = data[pixel + c];
87             val = val * max_val / std::numeric_limits<T>::max();
88             file << val << "\n";
89         }
90     }
91 }
92 
93 
94 
95 template<class T>
96 inline void
write_raw(std::ostream & file,const T * data,const stride_t stride,const ImageSpec & spec,unsigned int max_val)97 write_raw(std::ostream& file, const T* data, const stride_t stride,
98           const ImageSpec& spec, unsigned int max_val)
99 {
100     unsigned char byte;
101     unsigned int pixel, val;
102     for (int x = 0; x < spec.width; x++) {
103         pixel = x * stride;
104         for (int c = 0; c < spec.nchannels; c++) {
105             val = data[pixel + c];
106             val = val * max_val / std::numeric_limits<T>::max();
107             if (sizeof(T) == 2) {
108                 // Writing a 16bit ppm file
109                 // I'll adopt the practice of Netpbm and write the MSB first
110                 byte = static_cast<unsigned char>(val >> 8);
111                 file.write((char*)&byte, 1);
112                 byte = static_cast<unsigned char>(val & 0xff);
113                 file.write((char*)&byte, 1);
114             } else {
115                 // This must be an 8bit ppm file
116                 byte = static_cast<unsigned char>(val);
117                 file.write((char*)&byte, 1);
118             }
119         }
120     }
121 }
122 
123 
124 
~PNMOutput()125 PNMOutput::~PNMOutput() { close(); }
126 
127 
128 
129 bool
open(const std::string & name,const ImageSpec & userspec,OpenMode mode)130 PNMOutput::open(const std::string& name, const ImageSpec& userspec,
131                 OpenMode mode)
132 {
133     if (mode != Create) {
134         errorf("%s does not support subimages or MIP levels", format_name());
135         return false;
136     }
137 
138     close();                             // Close any already-opened file
139     m_spec = userspec;                   // Stash the spec
140     m_spec.set_format(TypeDesc::UINT8);  // Force 8 bit output
141     int bits_per_sample = m_spec.get_int_attribute("oiio:BitsPerSample", 8);
142     m_dither            = (m_spec.format == TypeDesc::UINT8)
143                               ? m_spec.get_int_attribute("oiio:dither", 0)
144                               : 0;
145 
146     if (m_spec.nchannels != 1 && m_spec.nchannels != 3) {
147         errorf("%s does not support %d-channel images\n", format_name(),
148                m_spec.nchannels);
149         return false;
150     }
151 
152     if (bits_per_sample == 1)
153         m_pnm_type = 4;
154     else if (m_spec.nchannels == 1)
155         m_pnm_type = 5;
156     else
157         m_pnm_type = 6;
158     if (!m_spec.get_int_attribute("pnm:binary", 1)) {
159         m_pnm_type -= 3;
160         Filesystem::open(m_file, name);
161 
162     } else {
163         Filesystem::open(m_file, name, std::ios::out | std::ios::binary);
164     }
165 
166     if (!m_file) {
167         errorf("Could not open \"%s\"", name);
168         return false;
169     }
170 
171     m_max_val = (1 << bits_per_sample) - 1;
172     // Write header
173     m_file << "P" << m_pnm_type << std::endl;
174     m_file << m_spec.width << " " << m_spec.height << std::endl;
175     if (m_pnm_type != 1 && m_pnm_type != 4)  // only non-monochrome
176         m_file << m_max_val << std::endl;
177 
178     // If user asked for tiles -- which this format doesn't support, emulate
179     // it by buffering the whole image.
180     if (m_spec.tile_width && m_spec.tile_height)
181         m_tilebuffer.resize(m_spec.image_bytes());
182 
183     return m_file.good();
184 }
185 
186 
187 
188 bool
close()189 PNMOutput::close()
190 {
191     if (!m_file) {  // already closed
192         return true;
193     }
194 
195     bool ok = true;
196     if (m_spec.tile_width) {
197         // Handle tile emulation -- output the buffered pixels
198         OIIO_DASSERT(m_tilebuffer.size());
199         ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0,
200                               m_spec.format, &m_tilebuffer[0]);
201         std::vector<unsigned char>().swap(m_tilebuffer);
202     }
203 
204     m_file.close();
205     return true;
206 }
207 
208 
209 
210 bool
write_scanline(int y,int z,TypeDesc format,const void * data,stride_t xstride)211 PNMOutput::write_scanline(int y, int z, TypeDesc format, const void* data,
212                           stride_t xstride)
213 {
214     if (!m_file)
215         return false;
216     if (z)
217         return false;
218 
219     m_spec.auto_stride(xstride, format, spec().nchannels);
220     const void* origdata = data;
221     data = to_native_scanline(format, data, xstride, m_scratch, m_dither, y, z);
222     if (data != origdata)  // a conversion happened...
223         xstride = spec().nchannels;
224 
225     switch (m_pnm_type) {
226     case 1:
227         write_ascii_binary(m_file, (unsigned char*)data, xstride, m_spec);
228         break;
229     case 2:
230     case 3:
231         if (m_max_val > std::numeric_limits<unsigned char>::max())
232             write_ascii(m_file, (unsigned short*)data, xstride, m_spec,
233                         m_max_val);
234         else
235             write_ascii(m_file, (unsigned char*)data, xstride, m_spec,
236                         m_max_val);
237         break;
238     case 4:
239         write_raw_binary(m_file, (unsigned char*)data, xstride, m_spec);
240         break;
241     case 5:
242     case 6:
243         if (m_max_val > std::numeric_limits<unsigned char>::max())
244             write_raw(m_file, (unsigned short*)data, xstride, m_spec,
245                       m_max_val);
246         else
247             write_raw(m_file, (unsigned char*)data, xstride, m_spec, m_max_val);
248         break;
249     default: return false;
250     }
251 
252     return m_file.good();
253 }
254 
255 
256 
257 bool
write_tile(int x,int y,int z,TypeDesc format,const void * data,stride_t xstride,stride_t ystride,stride_t zstride)258 PNMOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data,
259                       stride_t xstride, stride_t ystride, stride_t zstride)
260 {
261     // Emulate tiles by buffering the whole image
262     return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride,
263                                      zstride, &m_tilebuffer[0]);
264 }
265 
266 
267 OIIO_PLUGIN_NAMESPACE_END
268