@@ -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