Skip to content

Commit 350b197

Browse files
committed
Add pnm namespace and related functions
Add pnm namespace and related functions
1 parent 7648205 commit 350b197

1 file changed

Lines changed: 230 additions & 0 deletions

File tree

image_io.h

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,236 @@ namespace TinyDIP
4646
double* array_to_raw_image(Image<HSV> input);
4747
}
4848

49+
namespace pnm
50+
{
51+
// -------------------------------------------------------------------------
52+
// read_pnm_token function implementation
53+
// Helper: Skip comments and read the next string token securely from a PPM
54+
// -------------------------------------------------------------------------
55+
inline std::string read_pnm_token(std::ifstream& file)
56+
{
57+
std::string token;
58+
while (file >> token)
59+
{
60+
if (token.empty())
61+
{
62+
continue;
63+
}
64+
65+
if (token[0] == '#')
66+
{
67+
// If it's a comment, discard the rest of the line
68+
file.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
69+
}
70+
else
71+
{
72+
return token;
73+
}
74+
}
75+
return "";
76+
}
77+
78+
// -------------------------------------------------------------------------
79+
// shift_color_value function implementation
80+
// Helper: Precision bit shifting strictly ported from easyppm_read logic
81+
// -------------------------------------------------------------------------
82+
constexpr std::uint8_t shift_color_value(const int value, const int shift_bit)
83+
{
84+
if (shift_bit >= 0)
85+
{
86+
return static_cast<std::uint8_t>(value << shift_bit);
87+
}
88+
else
89+
{
90+
return static_cast<std::uint8_t>(value >> std::abs(shift_bit));
91+
}
92+
}
93+
94+
// -------------------------------------------------------------------------
95+
// Default Lambda Object: Functor with operator() for pixel processing
96+
// -------------------------------------------------------------------------
97+
struct ProcessPPMData
98+
{
99+
const std::vector<int>& raw_data;
100+
const std::string& magic;
101+
const int shift_bit;
102+
const std::size_t width;
103+
const std::size_t height;
104+
TinyDIP::Image<RGB>& image;
105+
106+
constexpr void operator()(const std::size_t i) const
107+
{
108+
const std::size_t x = i % width;
109+
const std::size_t y = i / width;
110+
const std::size_t flipped_y = height - 1 - y;
111+
112+
RGB pixel{};
113+
114+
if (magic == "P1")
115+
{
116+
const int val = (raw_data[i] == 0) ? 1 : 0;
117+
pixel.channels[0] = static_cast<std::uint8_t>(val);
118+
pixel.channels[1] = static_cast<std::uint8_t>(val);
119+
pixel.channels[2] = static_cast<std::uint8_t>(val);
120+
}
121+
else if (magic == "P2")
122+
{
123+
const auto shifted_val = shift_color_value(raw_data[i], shift_bit);
124+
pixel.channels[0] = shifted_val;
125+
pixel.channels[1] = shifted_val;
126+
pixel.channels[2] = shifted_val;
127+
}
128+
else if (magic == "P3")
129+
{
130+
const std::size_t base_idx = i * 3;
131+
pixel.channels[0] = shift_color_value(raw_data[base_idx], shift_bit);
132+
pixel.channels[1] = shift_color_value(raw_data[base_idx + 1], shift_bit);
133+
pixel.channels[2] = shift_color_value(raw_data[base_idx + 2], shift_bit);
134+
}
135+
136+
image.at_without_boundary_check(x, flipped_y) = pixel;
137+
}
138+
};
139+
140+
// -------------------------------------------------------------------------
141+
// Generic Threading Engine for Data Processing (with invocable constraint)
142+
// -------------------------------------------------------------------------
143+
template <class ExecutionPolicy, typename Func>
144+
requires (std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> &&
145+
std::invocable<Func, std::size_t>)
146+
void execute_pixel_processing(ExecutionPolicy&& policy, const std::vector<std::size_t>& indices, Func&& func)
147+
{
148+
std::for_each(std::forward<ExecutionPolicy>(policy), std::ranges::begin(indices), std::ranges::end(indices), std::forward<Func>(func));
149+
}
150+
151+
// Overload specifically designed to fall back to OpenMP
152+
template <typename Func>
153+
requires std::invocable<Func, std::size_t>
154+
void execute_pixel_processing_omp(const std::vector<std::size_t>& indices, Func&& func)
155+
{
156+
#pragma omp parallel for
157+
for (std::ptrdiff_t i = 0; i < static_cast<std::ptrdiff_t>(indices.size()); ++i)
158+
{
159+
func(indices[i]);
160+
}
161+
}
162+
163+
// -------------------------------------------------------------------------
164+
// Modern read_pnm incorporating execution policies and safety checks
165+
// -------------------------------------------------------------------------
166+
template <class ExecutionPolicy>
167+
requires std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>>
168+
TinyDIP::Image<RGB> read(ExecutionPolicy&& policy, const std::filesystem::path& file_path, const int outbits = 8)
169+
{
170+
std::ifstream file(file_path, std::ios::binary);
171+
if (!file.is_open())
172+
{
173+
throw std::runtime_error("Could not open file for reading: " + file_path.string());
174+
}
175+
176+
const std::string magic = read_pnm_token(file);
177+
if (magic != "P1" && magic != "P2" && magic != "P3")
178+
{
179+
throw std::runtime_error("Unsupported image format. Magic number found: " + magic);
180+
}
181+
182+
const std::size_t width = std::stoull(read_pnm_token(file));
183+
const std::size_t height = std::stoull(read_pnm_token(file));
184+
185+
int max_value = 1; // Default for P1 (PBM)
186+
if (magic != "P1")
187+
{
188+
max_value = std::stoi(read_pnm_token(file));
189+
}
190+
191+
const int in_bits = static_cast<int>(std::log2(max_value + 1));
192+
const int shift_bit = outbits - in_bits;
193+
194+
const std::size_t pixel_count = width * height;
195+
const std::size_t expected_values = (magic == "P3") ? (pixel_count * 3) : pixel_count;
196+
197+
// Fast-read phase securely ignoring comments throughout the matrix
198+
std::vector<int> raw_data;
199+
raw_data.reserve(expected_values);
200+
for (std::size_t i = 0; i < expected_values; ++i)
201+
{
202+
std::string token = read_pnm_token(file);
203+
if (token.empty())
204+
{
205+
throw std::runtime_error("Unexpected end of file while reading PNM data.");
206+
}
207+
raw_data.emplace_back(std::stoi(token));
208+
}
209+
210+
TinyDIP::Image<RGB> image(width, height);
211+
212+
std::vector<std::size_t> indices(pixel_count);
213+
std::ranges::iota(indices, 0); // Using C++23 std::ranges::iota
214+
215+
ProcessPPMData processor{ raw_data, magic, shift_bit, width, height, image };
216+
217+
// Dispatches to execution policy with constraints verified
218+
execute_pixel_processing(std::forward<ExecutionPolicy>(policy), indices, processor);
219+
220+
return image;
221+
}
222+
223+
// Overload fallback avoiding execution policies, instead opting for OpenMP
224+
inline TinyDIP::Image<RGB> read(const std::filesystem::path& file_path, const int outbits = 8)
225+
{
226+
std::ifstream file(file_path, std::ios::binary);
227+
if (!file.is_open())
228+
{
229+
throw std::runtime_error("Could not open file for reading: " + file_path.string());
230+
}
231+
232+
const std::string magic = read_pnm_token(file);
233+
if (magic != "P1" && magic != "P2" && magic != "P3")
234+
{
235+
throw std::runtime_error("Unsupported image format. Magic number found: " + magic);
236+
}
237+
238+
const std::size_t width = std::stoull(read_pnm_token(file));
239+
const std::size_t height = std::stoull(read_pnm_token(file));
240+
241+
int max_value = 1;
242+
if (magic != "P1")
243+
{
244+
max_value = std::stoi(read_pnm_token(file));
245+
}
246+
247+
const int in_bits = static_cast<int>(std::log2(max_value + 1));
248+
const int shift_bit = outbits - in_bits;
249+
250+
const std::size_t pixel_count = width * height;
251+
const std::size_t expected_values = (magic == "P3") ? (pixel_count * 3) : pixel_count;
252+
253+
std::vector<int> raw_data;
254+
raw_data.reserve(expected_values);
255+
for (std::size_t i = 0; i < expected_values; ++i)
256+
{
257+
std::string token = read_pnm_token(file);
258+
if (token.empty())
259+
{
260+
throw std::runtime_error("Unexpected end of file while reading PNM data.");
261+
}
262+
raw_data.emplace_back(std::stoi(token));
263+
}
264+
265+
TinyDIP::Image<RGB> image(width, height);
266+
267+
std::vector<std::size_t> indices(pixel_count);
268+
std::ranges::iota(indices, 0); // Using C++23 std::ranges::iota
269+
270+
ProcessPPMData processor{ raw_data, magic, shift_bit, width, height, image };
271+
272+
// Dispatch explicitly to the OpenMP implementation
273+
execute_pixel_processing_omp(indices, processor);
274+
275+
return image;
276+
}
277+
}
278+
49279
int hsv_write_detail(const char* const filename, const int xsize, const int ysize, const double* const image);
50280

51281
int hsv_write(const char* const filename, Image<HSV> input);

0 commit comments

Comments
 (0)