-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathppm.hpp
More file actions
199 lines (170 loc) · 5.42 KB
/
ppm.hpp
File metadata and controls
199 lines (170 loc) · 5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/*
! ppm-io
! Single file header-only C++ module for reading and writing images in the PPM format.
! https://github.com/thinks/ppm-io
*/
#ifndef THINKS_PPM_PPM_HPP_INCLUDED
#define THINKS_PPM_PPM_HPP_INCLUDED
#include <cassert>
#include <cstdint>
#include <exception>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
namespace thinks {
namespace ppm {
namespace detail {
template<typename F> inline
F openFileStream(
std::string const& filename,
std::ios_base::openmode const mode = std::ios::binary)
{
using namespace std;
auto ofs = F(filename, mode);
if (ofs.fail()) {
auto ss = stringstream();
ss << "cannot open file '" << filename << "'";
throw runtime_error(ss.str());
}
return ofs;
}
} // namespace detail
//! Read a PPM image from an input stream.
//!
//! Assumptions:
//! - the PPM header does not contain any comments.
//! - the PPM header width and height are non-zero.
//! - the input pointers are non-null.
//!
//! Pixel data is read as RGB triplets in row major order. For instance,
//! the pixel data for a 2x2 image is represented as follows:
//!
//! | Column 0 | Column 1
//! +----------------------------------+------------------------------------+
//! Row 0 | RGB: {data[0], data[1], data[2]} | RGB: {data[3], data[4], data[5]} |
//! +----------------------------------+------------------------------------+
//! Row 1 | RGB: {data[6], data[7], data[8]} | RGB: {data[9], data[10], data[11]} |
//! +----------------------------------+------------------------------------+
//!
//! An std::runtime_error is thrown if:
//! - the magic number is not 'P6'.
//! - the max value is not '255'.
//! - the pixel data cannot be read.
inline
void readRgbImage(
std::istream& is,
std::size_t* width,
std::size_t* height,
std::vector<std::uint8_t>* pixel_data)
{
using namespace std;
assert(width != nullptr);
assert(height != nullptr);
assert(pixel_data != nullptr);
// Read header.
auto const expected_magic_number = string("P6");
auto const expected_max_value = string("255");
auto magic_number = string();
string comment;
auto max_value = string();
is >> magic_number;
is.ignore(256, '\n');
is.ignore(256, '\n');
is >> *width >> *height >> max_value;
assert(*width != 0);
assert(*height != 0);
if (magic_number != expected_magic_number) {
auto ss = stringstream();
ss << "magic number must be '" << expected_magic_number << "'";
throw runtime_error(ss.str());
}
if (max_value != expected_max_value) {
auto ss = stringstream();
ss << "max value must be " << expected_max_value;
throw runtime_error(ss.str());
}
// Skip ahead (an arbitrary number!) to the pixel data.
is.ignore(256, '\n');
// Read pixel data.
pixel_data->resize((*width) * (*height) * 3);
is.read(reinterpret_cast<char*>(pixel_data->data()), pixel_data->size());
if (!is) {
auto ss = stringstream();
ss << "failed reading " << pixel_data->size() << " bytes";
throw runtime_error(ss.str());
}
}
//! See std::istream overload version above.
//!
//! Throws an std::runtime_error if file cannot be opened.
inline
void readRgbImage(
std::string const& filename,
std::size_t* width,
std::size_t* height,
std::vector<std::uint8_t>* pixel_data)
{
auto ifs = detail::openFileStream<std::ifstream>(filename);
readRgbImage(ifs, width, height, pixel_data);
ifs.close();
}
//! Write a PPM image to an output stream.
//!
//! Pixel data is given as RGB triplets in row major order. For instance,
//! the pixel data for a 2x2 image is represented as follows:
//!
//! | Column 0 | Column 1
//! +----------------------------------+------------------------------------+
//! Row 0 | RGB: {data[0], data[1], data[2]} | RGB: {data[3], data[4], data[5]} |
//! +----------------------------------+------------------------------------+
//! Row 1 | RGB: {data[6], data[7], data[8]} | RGB: {data[9], data[10], data[11]} |
//! +----------------------------------+------------------------------------+
//!
//! An std::runtime_error is thrown if:
//! - width or height is zero.
//! - the size of the pixel data does not match the width and height.
inline
void writeRgbImage(
std::ostream& os,
std::size_t const width,
std::size_t const height,
std::vector<std::uint8_t> const& pixel_data)
{
using namespace std;
if (width == 0) {
throw runtime_error("width must be non-zero");
}
if (height == 0) {
throw runtime_error("height must be non-zero");
}
if (pixel_data.size() != width * height * 3) {
throw runtime_error("pixel data must match width and height");
}
// Write header.
auto const magic_number = string("P6");
auto const max_value = string("255");
os << magic_number << "\n"
<< width << " " << height << "\n"
<< max_value
<< "\n"; // Marks beginning of pixel data.
// Write pixel data.
os.write(reinterpret_cast<char const*>(pixel_data.data()), pixel_data.size());
}
//! See std::ostream overload version above.
//!
//! Throws an std::runtime_error if file cannot be opened.
inline
void writeRgbImage(
std::string const& filename,
std::size_t const width,
std::size_t const height,
std::vector<std::uint8_t> const& pixel_data)
{
auto ofs = detail::openFileStream<std::ofstream>(filename);
writeRgbImage(ofs, width, height, pixel_data);
ofs.close();
}
} // namespace ppm
} // namespace thinks
#endif // THINKS_PPM_PPM_HPP_INCLUDED