@@ -71,8 +71,7 @@ static float g_scroll_multiplier = 1.0f;
71
71
struct WatchedPixel
72
72
{
73
73
int2 pixel;
74
- int color_mode_current = 0 ;
75
- int color_mode_reference = 0 ;
74
+ int3 color_mode{0 , 0 , 0 }; // !< Color mode for current, reference, and composite pixels
76
75
};
77
76
78
77
static vector<WatchedPixel> g_watched_pixels;
@@ -791,6 +790,21 @@ HDRViewApp::HDRViewApp(std::optional<float> force_exposure, std::optional<float>
791
790
save_as (filename);
792
791
},
793
792
if_img});
793
+ add_action ({" Export image as..." , ICON_MY_SAVE_AS, ImGuiKey_None, 0 ,
794
+ [this ]()
795
+ {
796
+ string filename =
797
+ pfd::save_file (" Export image as" , g_blank_icon,
798
+ {
799
+ " Supported image files" ,
800
+ fmt::format (" *.{}" , fmt::join (Image::savable_formats (), " *." )),
801
+ })
802
+ .result ();
803
+
804
+ if (!filename.empty ())
805
+ export_as (filename);
806
+ },
807
+ if_img});
794
808
795
809
#else
796
810
add_action ({" Save as..." , ICON_MY_SAVE_AS, ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S, 0 ,
@@ -1222,6 +1236,7 @@ void HDRViewApp::draw_menus()
1222
1236
ImGui::Separator ();
1223
1237
1224
1238
MenuItem (action (" Save as..." ));
1239
+ MenuItem (action (" Export image as..." ));
1225
1240
1226
1241
ImGui::Separator ();
1227
1242
@@ -1359,6 +1374,55 @@ void HDRViewApp::save_as(const string &filename) const
1359
1374
}
1360
1375
}
1361
1376
1377
+ void HDRViewApp::export_as (const string &filename) const
1378
+ {
1379
+ try
1380
+ {
1381
+ Image img (current_image ()->size (), 4 );
1382
+ img.finalize ();
1383
+ int2 p;
1384
+ auto bounds = current_image ()->data_window ;
1385
+ int block_size = std::max (1 , 1024 * 1024 / img.size ().x );
1386
+ parallel_for (blocked_range<int >(0 , img.size ().y , block_size),
1387
+ [this , &img, bounds](int begin_y, int end_y, int , int )
1388
+ {
1389
+ for (int y = begin_y; y < end_y; ++y)
1390
+ for (int x = 0 ; x < img.size ().x ; ++x)
1391
+ {
1392
+ float4 v = pixel_value (int2{x, y} + bounds.min , false , 2 );
1393
+
1394
+ img.channels [0 ](x, y) = v[0 ];
1395
+ img.channels [1 ](x, y) = v[1 ];
1396
+ img.channels [2 ](x, y) = v[2 ];
1397
+ img.channels [3 ](x, y) = v[3 ];
1398
+ }
1399
+ });
1400
+
1401
+ #if !defined(__EMSCRIPTEN__)
1402
+ std::ofstream os{filename, std::ios_base::binary};
1403
+ img.save (os, filename, 1 .f , 1 .f , false , m_dither);
1404
+ #else
1405
+ std::ostringstream os;
1406
+ img.save (os, filename, 1 .f , 1 .f , false , m_dither);
1407
+ string buffer = os.str ();
1408
+ emscripten_browser_file::download (
1409
+ filename, // the default filename for the browser to save.
1410
+ " application/octet-stream" , // the MIME type of the data, treated as if it were a webserver
1411
+ // serving a file
1412
+ string_view (buffer.c_str (), buffer.length ()) // a buffer describing the data to download
1413
+ );
1414
+ #endif
1415
+ }
1416
+ catch (const std::exception &e)
1417
+ {
1418
+ spdlog::error (" An error occurred while exporting to '{}':\n\t {}." , filename, e.what ());
1419
+ }
1420
+ catch (...)
1421
+ {
1422
+ spdlog::error (" An unknown error occurred while exporting to '{}'." , filename);
1423
+ }
1424
+ }
1425
+
1362
1426
void HDRViewApp::load_images (const vector<string> &filenames)
1363
1427
{
1364
1428
for (auto f : filenames)
@@ -1645,19 +1709,68 @@ void HDRViewApp::draw_info_window()
1645
1709
return img->draw_info ();
1646
1710
}
1647
1711
1648
- static void draw_pixel_color (ConstImagePtr img, const int2 &pixel, int &color_mode, Target target,
1649
- bool allow_copy = false )
1712
+ float4 HDRViewApp::pixel_value (int2 p, bool raw, int which_image) const
1650
1713
{
1651
- if (!img)
1652
- return ;
1714
+ auto img1 = current_image ();
1715
+ auto img2 = reference_image ();
1716
+
1717
+ float4 value;
1718
+
1719
+ if (which_image == 0 )
1720
+ value = img1 ? (raw ? img1->raw_pixel (p, Target_Primary) : img1->rgba_pixel (p, Target_Primary)) : float4{0 .f };
1721
+ else if (which_image == 1 )
1722
+ value =
1723
+ img2 ? (raw ? img2->raw_pixel (p, Target_Secondary) : img2->rgba_pixel (p, Target_Secondary)) : float4{0 .f };
1724
+ else if (which_image == 2 )
1725
+ {
1726
+ auto rgba1 = img1 ? img1->rgba_pixel (p, Target_Primary) : float4{0 .f };
1727
+ auto rgba2 = img2 ? img2->rgba_pixel (p, Target_Secondary) : float4{0 .f };
1728
+ value = blend (rgba1, rgba2, m_blend_mode);
1729
+ }
1730
+
1731
+ return raw ? value
1732
+ : ::tonemap (float4{powf (2 .f , m_exposure_live) * value.xyz (), value.w }, m_gamma_live, m_tonemap,
1733
+ m_colormap);
1734
+ }
1653
1735
1654
- int group_idx = target == Target_Primary ? img->selected_group : img->reference_group ;
1655
- auto &group = img->groups [group_idx];
1656
- float4 color32 = img->raw_pixel (pixel, target);
1657
- float4 displayed_color = img->shaded_pixel (pixel, target, powf (2 .f , hdrview ()->exposure_live ()),
1658
- hdrview ()->gamma_live (), hdrview ()->tonemap (), hdrview ()->colormap ());
1736
+ static void pixel_color_widget (const int2 &pixel, int &color_mode, int which_image, bool allow_copy = false )
1737
+ {
1738
+ float4 color32 = hdrview ()->pixel_value (pixel, true , which_image);
1739
+ float4 displayed_color = hdrview ()->pixel_value (pixel, false , which_image);
1659
1740
uint32_t hex = color_f128_to_u32 (color_u32_to_f128 (color_f128_to_u32 (displayed_color)));
1660
1741
int4 ldr_color = int4{float4{color_u32_to_f128 (hex)} * 255 .f };
1742
+ bool3 inside = {false , false , false };
1743
+
1744
+ int components = 4 ;
1745
+ string channel_names[4 ] = {" R" , " G" , " B" , " A" };
1746
+ if (which_image != 2 )
1747
+ {
1748
+ ConstImagePtr img;
1749
+ ChannelGroup group;
1750
+ if (which_image == 0 )
1751
+ {
1752
+ if (!hdrview ()->current_image ())
1753
+ return ;
1754
+ img = hdrview ()->current_image ();
1755
+ components = color_mode == 0 ? img->groups [img->selected_group ].num_channels : 4 ;
1756
+ group = img->groups [img->selected_group ];
1757
+ inside[0 ] = img->contains (pixel);
1758
+ }
1759
+ else if (which_image == 1 )
1760
+ {
1761
+ if (!hdrview ()->reference_image ())
1762
+ return ;
1763
+ img = hdrview ()->reference_image ();
1764
+ components = color_mode == 0 ? img->groups [img->reference_group ].num_channels : 4 ;
1765
+ group = img->groups [img->reference_group ];
1766
+ inside[1 ] = img->contains (pixel);
1767
+ }
1768
+
1769
+ if (color_mode == 0 )
1770
+ for (int c = 0 ; c < components; ++c)
1771
+ channel_names[c] = Channel::tail (img->channels [group.channels [c]].name );
1772
+ }
1773
+ inside[2 ] = inside[0 ] || inside[1 ];
1661
1774
1662
1775
ImGuiColorEditFlags color_flags = ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_AlphaPreviewHalf;
1663
1776
if (ImGui::ColorButton (" colorbutton" , displayed_color, color_flags))
@@ -1672,11 +1785,11 @@ static void draw_pixel_color(ConstImagePtr img, const int2 &pixel, int &color_mo
1672
1785
string buf;
1673
1786
if (color_mode == 0 )
1674
1787
{
1675
- if (group. num_channels == 4 )
1788
+ if (components == 4 )
1676
1789
buf = fmt::format (" ({:g}, {:g}, {:g}, {:g})" , color32.x , color32.y , color32.z , color32.w );
1677
- else if (group. num_channels == 3 )
1790
+ else if (components == 3 )
1678
1791
buf = fmt::format (" ({:g}, {:g}, {:g})" , color32.x , color32.y , color32.z );
1679
- else if (group. num_channels == 2 )
1792
+ else if (components == 2 )
1680
1793
buf = fmt::format (" ({:g}, {:g})" , color32.x , color32.y );
1681
1794
else
1682
1795
buf = fmt::format (" {:g}" , color32.x );
@@ -1705,11 +1818,8 @@ static void draw_pixel_color(ConstImagePtr img, const int2 &pixel, int &color_mo
1705
1818
1706
1819
ImGui::SameLine (0 .f , ImGui::GetStyle ().ItemInnerSpacing .x );
1707
1820
1708
- // static const char *rgba = "R,G,B,A";
1709
- static const char *rgba_names[4 ] = {" R" , " G" , " B" , " A" };
1710
- float w_full = ImGui::GetContentRegionAvail ().x ;
1821
+ float w_full = ImGui::GetContentRegionAvail ().x ;
1711
1822
// width available to all items (without spacing)
1712
- int components = color_mode == 0 ? group.num_channels : 4 ;
1713
1823
float w_items = w_full - ImGui::GetStyle ().ItemInnerSpacing .x * (components - 1 );
1714
1824
float prev_split = w_items;
1715
1825
// distributes the available width without jitter during resize
@@ -1720,53 +1830,46 @@ static void draw_pixel_color(ConstImagePtr img, const int2 &pixel, int &color_mo
1720
1830
prev_split = next_split;
1721
1831
};
1722
1832
1723
- ImGui::BeginDisabled (!img-> contains (pixel) );
1833
+ ImGui::BeginDisabled (!inside[which_image] );
1724
1834
ImGui::BeginGroup ();
1725
1835
if (color_mode == 0 )
1726
1836
{
1727
- // ImGui::InputScalarN(group.name.c_str(), ImGuiDataType_Float, &color32, group.num_channels, NULL, NULL, "%-g",
1728
- // ImGuiInputTextFlags_ReadOnly);
1729
1837
for (int c = 0 ; c < components; ++c)
1730
1838
{
1731
1839
if (c > 0 )
1732
1840
ImGui::SameLine (0 .f , ImGui::GetStyle ().ItemInnerSpacing .x );
1733
1841
1734
1842
set_item_width (c);
1735
1843
ImGui::InputFloat (fmt::format (" ##component {}" , c).c_str (), &color32[c], 0 .f , 0 .f ,
1736
- fmt::format (" {}: %g" , Channel::tail (img->channels [group.channels [c]].name )).c_str (),
1737
- ImGuiInputTextFlags_ReadOnly);
1844
+ fmt::format (" {}: %g" , channel_names[c]).c_str (), ImGuiInputTextFlags_ReadOnly);
1738
1845
}
1739
1846
}
1740
1847
else if (color_mode == 1 )
1741
1848
{
1742
- // ImGui::InputScalarN(rgba, ImGuiDataType_Float, &displayed_color, 4, NULL, NULL, "%-g",
1743
- // ImGuiInputTextFlags_ReadOnly);
1744
1849
for (int c = 0 ; c < components; ++c)
1745
1850
{
1746
1851
if (c > 0 )
1747
1852
ImGui::SameLine (0 .f , ImGui::GetStyle ().ItemInnerSpacing .x );
1748
1853
1749
1854
set_item_width (c);
1750
1855
ImGui::InputFloat (fmt::format (" ##component {}" , c).c_str (), &displayed_color[c], 0 .f , 0 .f ,
1751
- fmt::format (" {}: %g" , rgba_names [c]).c_str (), ImGuiInputTextFlags_ReadOnly);
1856
+ fmt::format (" {}: %g" , channel_names [c]).c_str (), ImGuiInputTextFlags_ReadOnly);
1752
1857
}
1753
1858
}
1754
1859
else if (color_mode == 2 )
1755
1860
{
1756
- // ImGui::InputScalarN(rgba, ImGuiDataType_S32, &ldr_color, 4, NULL, NULL, "%-d", ImGuiInputTextFlags_ReadOnly);
1757
1861
for (int c = 0 ; c < components; ++c)
1758
1862
{
1759
1863
if (c > 0 )
1760
1864
ImGui::SameLine (0 .f , ImGui::GetStyle ().ItemInnerSpacing .x );
1761
1865
1762
1866
set_item_width (c);
1763
1867
ImGui::InputScalarN (fmt::format (" ##component {}" , c).c_str (), ImGuiDataType_S32, &ldr_color[c], 1 , NULL ,
1764
- NULL , fmt::format (" {}: %d" , rgba_names [c]).c_str (), ImGuiInputTextFlags_ReadOnly);
1868
+ NULL , fmt::format (" {}: %d" , channel_names [c]).c_str (), ImGuiInputTextFlags_ReadOnly);
1765
1869
}
1766
1870
}
1767
1871
else if (color_mode == 3 )
1768
1872
{
1769
- // ImGui::InputScalar(rgba, ImGuiDataType_S32, &hex, NULL, NULL, "#%-08X", ImGuiInputTextFlags_ReadOnly);
1770
1873
ImGui::SetNextItemWidth (IM_TRUNC (w_full));
1771
1874
ImGui::InputScalar (" ##hex color" , ImGuiDataType_S32, &hex, NULL , NULL , " #%08X" , ImGuiInputTextFlags_ReadOnly);
1772
1875
}
@@ -1819,16 +1922,22 @@ void HDRViewApp::draw_pixel_inspector_window()
1819
1922
auto hovered_pixel = int2{pixel_at_app_pos (io.MousePos )};
1820
1923
if (PixelHeader (ICON_MY_CURSOR_ARROW " ##hovered pixel" , hovered_pixel))
1821
1924
{
1822
- static int2 color_mode = {0 , 0 };
1925
+ static int3 color_mode = {0 , 0 , 0 };
1823
1926
ImGui::PushID (" Current" );
1824
- draw_pixel_color ( current_image (), hovered_pixel, color_mode.x , Target_Primary );
1927
+ pixel_color_widget ( hovered_pixel, color_mode.x , 0 );
1825
1928
ImGui::SetItemTooltip (" Hovered pixel values in current channel." );
1826
1929
ImGui::PopID ();
1930
+
1827
1931
ImGui::PushID (" Reference" );
1828
- draw_pixel_color ( reference_image (), hovered_pixel, color_mode.y , Target_Secondary );
1932
+ pixel_color_widget ( hovered_pixel, color_mode.y , 1 );
1829
1933
ImGui::SetItemTooltip (" Hovered pixel values in reference channel." );
1830
1934
ImGui::PopID ();
1831
1935
1936
+ ImGui::PushID (" Composite" );
1937
+ pixel_color_widget (hovered_pixel, color_mode.z , 2 );
1938
+ ImGui::SetItemTooltip (" Hovered pixel values in composite." );
1939
+ ImGui::PopID ();
1940
+
1832
1941
ImGui::Spacing ();
1833
1942
}
1834
1943
@@ -1844,15 +1953,20 @@ void HDRViewApp::draw_pixel_inspector_window()
1844
1953
if (PixelHeader (fmt::format (" {}{}" , ICON_MY_WATCHED_PIXEL, i + 1 ), wp.pixel , &visible))
1845
1954
{
1846
1955
ImGui::PushID (" Current" );
1847
- draw_pixel_color ( current_image (), wp.pixel , wp.color_mode_current , Target_Primary , true );
1956
+ pixel_color_widget ( wp.pixel , wp.color_mode . x , 0 , true );
1848
1957
ImGui::SetItemTooltip (" Pixel %s%d values in current channel." , ICON_MY_WATCHED_PIXEL, i + 1 );
1849
1958
ImGui::PopID ();
1850
1959
1851
1960
ImGui::PushID (" Reference" );
1852
- draw_pixel_color ( reference_image (), wp.pixel , wp.color_mode_reference , Target_Secondary , true );
1961
+ pixel_color_widget ( wp.pixel , wp.color_mode . y , 1 , true );
1853
1962
ImGui::SetItemTooltip (" Pixel %s%d values in reference channel." , ICON_MY_WATCHED_PIXEL, i + 1 );
1854
1963
ImGui::PopID ();
1855
1964
1965
+ ImGui::PushID (" Composite" );
1966
+ pixel_color_widget (wp.pixel , wp.color_mode .z , 2 , true );
1967
+ ImGui::SetItemTooltip (" Pixel %s%d values in composite." , ICON_MY_WATCHED_PIXEL, i + 1 );
1968
+ ImGui::PopID ();
1969
+
1856
1970
ImGui::Spacing ();
1857
1971
}
1858
1972
ImGui::PopID ();
@@ -2838,7 +2952,7 @@ void HDRViewApp::draw_background()
2838
2952
{
2839
2953
if (ImGui::IsMouseDoubleClicked (ImGuiMouseButton_Left))
2840
2954
// add watched pixel
2841
- g_watched_pixels.emplace_back (WatchedPixel{int2{pixel_at_app_pos (io.MousePos )}, 0 , 0 });
2955
+ g_watched_pixels.emplace_back (WatchedPixel{int2{pixel_at_app_pos (io.MousePos )}});
2842
2956
else if (ImGui::IsMouseDragging (ImGuiMouseButton_Left))
2843
2957
{
2844
2958
if (g_watched_pixels.size ())
0 commit comments