@@ -129,6 +129,103 @@ namespace {
129129 return true ;
130130 }
131131
132+ bool compute_rect_stats (const LoadedImage& image, int xbegin, int ybegin,
133+ int xend, int yend, std::vector<double >& out_min,
134+ std::vector<double >& out_max,
135+ std::vector<double >& out_avg, int & out_samples,
136+ ProbeStatsSemantics semantics
137+ = ProbeStatsSemantics::OIIOFloat)
138+ {
139+ out_min.clear ();
140+ out_max.clear ();
141+ out_avg.clear ();
142+ out_samples = 0 ;
143+ if (image.width <= 0 || image.height <= 0 || image.nchannels <= 0 )
144+ return false ;
145+
146+ const int xmin = std::clamp (std::min (xbegin, xend), 0 , image.width - 1 );
147+ const int xmax = std::clamp (std::max (xbegin, xend), 0 , image.width - 1 );
148+ const int ymin = std::clamp (std::min (ybegin, yend), 0 ,
149+ image.height - 1 );
150+ const int ymax = std::clamp (std::max (ybegin, yend), 0 ,
151+ image.height - 1 );
152+ if (xmax < xmin || ymax < ymin)
153+ return false ;
154+
155+ const size_t channels = static_cast <size_t >(image.nchannels );
156+ out_min.assign (channels, std::numeric_limits<double >::infinity ());
157+ out_max.assign (channels, -std::numeric_limits<double >::infinity ());
158+ out_avg.assign (channels, 0.0 );
159+
160+ std::vector<double > sample;
161+ for (int y = ymin; y <= ymax; ++y) {
162+ for (int x = xmin; x <= xmax; ++x) {
163+ if (!sample_loaded_pixel_with_semantics (image, x, y, semantics,
164+ sample)) {
165+ continue ;
166+ }
167+ if (sample.size () != channels)
168+ continue ;
169+ for (size_t c = 0 ; c < channels; ++c) {
170+ out_min[c] = std::min (out_min[c], sample[c]);
171+ out_max[c] = std::max (out_max[c], sample[c]);
172+ out_avg[c] += sample[c];
173+ }
174+ ++out_samples;
175+ }
176+ }
177+
178+ if (out_samples <= 0 ) {
179+ out_min.clear ();
180+ out_max.clear ();
181+ out_avg.clear ();
182+ return false ;
183+ }
184+ for (double & value : out_avg)
185+ value /= static_cast <double >(out_samples);
186+ return true ;
187+ }
188+
189+ void build_area_probe_placeholder_lines (const LoadedImage& image,
190+ std::vector<std::string>& out_lines)
191+ {
192+ out_lines.clear ();
193+ out_lines.emplace_back (" Area Probe:" );
194+ if (image.width <= 0 || image.height <= 0 || image.nchannels <= 0 )
195+ return ;
196+ for (int c = 0 ; c < image.nchannels ; ++c) {
197+ const std::string channel
198+ = pixel_preview_channel_label (image, static_cast <int >(c));
199+ out_lines.emplace_back (Strutil::fmt::format (
200+ " {:<5}: [min: ----- max: ----- avg: -----]" , channel));
201+ }
202+ }
203+
204+ void build_area_probe_result_lines (const LoadedImage& image,
205+ const std::vector<double >& min_values,
206+ const std::vector<double >& max_values,
207+ const std::vector<double >& avg_values,
208+ std::vector<std::string>& out_lines)
209+ {
210+ out_lines.clear ();
211+ out_lines.emplace_back (" Area Probe:" );
212+ const int channel_count = std::max (1 , image.nchannels );
213+ for (int c = 0 ; c < channel_count; ++c) {
214+ const std::string channel
215+ = pixel_preview_channel_label (image, static_cast <int >(c));
216+ if (static_cast <size_t >(c) < min_values.size ()
217+ && static_cast <size_t >(c) < max_values.size ()
218+ && static_cast <size_t >(c) < avg_values.size ()) {
219+ out_lines.emplace_back (Strutil::fmt::format (
220+ " {:<5}: [min: {:>6.3f} max: {:>6.3f} avg: {:>6.3f}]" ,
221+ channel, min_values[c], max_values[c], avg_values[c]));
222+ } else {
223+ out_lines.emplace_back (Strutil::fmt::format (
224+ " {:<5}: [min: ----- max: ----- avg: -----]" , channel));
225+ }
226+ }
227+ }
228+
132229 std::string format_probe_iv_float (double value)
133230 {
134231 if (value < 10.0 )
@@ -358,6 +455,40 @@ namespace {
358455
359456} // namespace
360457
458+ void
459+ reset_area_probe_overlay (ViewerState& viewer)
460+ {
461+ viewer.area_probe_drag_active = false ;
462+ viewer.area_probe_drag_start_uv = ImVec2 (0 .0f , 0 .0f );
463+ viewer.area_probe_drag_end_uv = ImVec2 (0 .0f , 0 .0f );
464+ build_area_probe_placeholder_lines (viewer.image , viewer.area_probe_lines );
465+ }
466+
467+ void
468+ update_area_probe_overlay (ViewerState& viewer, int xbegin, int ybegin, int xend,
469+ int yend)
470+ {
471+ if (viewer.image .path .empty ()) {
472+ viewer.area_probe_lines .clear ();
473+ return ;
474+ }
475+
476+ std::vector<double > min_values;
477+ std::vector<double > max_values;
478+ std::vector<double > avg_values;
479+ int sample_count = 0 ;
480+ if (!compute_rect_stats (viewer.image , xbegin, ybegin, xend, yend,
481+ min_values, max_values, avg_values, sample_count,
482+ ProbeStatsSemantics::OIIOFloat)) {
483+ build_area_probe_placeholder_lines (viewer.image ,
484+ viewer.area_probe_lines );
485+ return ;
486+ }
487+
488+ build_area_probe_result_lines (viewer.image , min_values, max_values,
489+ avg_values, viewer.area_probe_lines );
490+ }
491+
361492OverlayPanelRect
362493draw_pixel_closeup_overlay (const ViewerState& viewer,
363494 PlaceholderUiState& ui_state,
@@ -697,38 +828,35 @@ draw_area_probe_overlay(const ViewerState& viewer,
697828 if (!ui_state.show_area_probe_window || !map.valid )
698829 return ;
699830
700- std::vector<std::string> lines;
701- lines.emplace_back (" Area Probe:" );
702- if (viewer.image .path .empty ()) {
703- lines.emplace_back (" No image loaded." );
704- } else {
705- std::vector<double > min_values;
706- std::vector<double > max_values;
707- std::vector<double > avg_values;
708- int sample_count = 0 ;
709- const bool have_stats
710- = viewer.probe_valid
711- && compute_area_stats (viewer.image , viewer.probe_x ,
712- viewer.probe_y , ui_state.closeup_avg_pixels ,
713- min_values, max_values, avg_values,
714- sample_count,
715- ProbeStatsSemantics::OIIOFloat);
716-
717- const int channel_count = std::max (1 , viewer.image .nchannels );
718- for (int c = 0 ; c < channel_count; ++c) {
719- const std::string channel
720- = pixel_preview_channel_label (viewer.image ,
721- static_cast <int >(c));
722- if (have_stats && static_cast <size_t >(c) < min_values.size ()
723- && static_cast <size_t >(c) < max_values.size ()
724- && static_cast <size_t >(c) < avg_values.size ()) {
725- lines.emplace_back (Strutil::fmt::format (
726- " {:<5}: [min: {:>6.3f} max: {:>6.3f} avg: {:>6.3f}]" ,
727- channel, min_values[c], max_values[c], avg_values[c]));
728- } else {
729- lines.emplace_back (Strutil::fmt::format (
730- " {:<5}: [min: ----- max: ----- avg: -----]" , channel));
731- }
831+ if (viewer.area_probe_drag_active ) {
832+ ImVec2 start_screen (0 .0f , 0 .0f );
833+ ImVec2 end_screen (0 .0f , 0 .0f );
834+ if (source_uv_to_screen (map, viewer.area_probe_drag_start_uv ,
835+ start_screen)
836+ && source_uv_to_screen (map, viewer.area_probe_drag_end_uv ,
837+ end_screen)) {
838+ const ImVec2 rect_min (std::min (start_screen.x , end_screen.x ),
839+ std::min (start_screen.y , end_screen.y ));
840+ const ImVec2 rect_max (std::max (start_screen.x , end_screen.x ),
841+ std::max (start_screen.y , end_screen.y ));
842+ ImDrawList* draw_list = ImGui::GetWindowDrawList ();
843+ draw_list->PushClipRect (map.viewport_rect_min ,
844+ map.viewport_rect_max , true );
845+ draw_list->AddRectFilled (rect_min, rect_max,
846+ IM_COL32 (52 , 128 , 255 , 76 ), 0 .0f );
847+ draw_list->AddRect (rect_min, rect_max, IM_COL32 (96 , 176 , 255 , 220 ),
848+ 0 .0f , 0 , 1 .2f );
849+ draw_list->PopClipRect ();
850+ }
851+ }
852+
853+ std::vector<std::string> lines = viewer.area_probe_lines ;
854+ if (lines.empty ()) {
855+ if (viewer.image .path .empty ()) {
856+ lines.emplace_back (" Area Probe:" );
857+ lines.emplace_back (" No image loaded." );
858+ } else {
859+ build_area_probe_placeholder_lines (viewer.image , lines);
732860 }
733861 }
734862
0 commit comments