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