|
| 1 | +#include <nanogui/screen.h> |
| 2 | +#include <nanogui/window.h> |
| 3 | +#include <nanogui/layout.h> |
| 4 | +#include <nanogui/label.h> |
| 5 | +#include <nanogui/combobox.h> |
| 6 | +#include <nanogui/imageview.h> |
| 7 | +#include <nanogui/texture.h> |
| 8 | +#include <nanogui/chroma.h> |
| 9 | +#include <algorithm> |
| 10 | +#include <cmath> |
| 11 | +#include <vector> |
| 12 | + |
| 13 | +using namespace nanogui; |
| 14 | +using namespace nanogui::ituth273; |
| 15 | + |
| 16 | +struct PrimaryEntry { |
| 17 | + ColorPrimaries primary; |
| 18 | + const char* name; |
| 19 | +}; |
| 20 | + |
| 21 | +static const PrimaryEntry primary_table[] = { |
| 22 | + { ColorPrimaries::BT2020, "BT2020" }, |
| 23 | + { ColorPrimaries::BT470BG, "BT470BG" }, |
| 24 | + { ColorPrimaries::BT470M, "BT470M" }, |
| 25 | + { ColorPrimaries::BT709, "BT709" }, |
| 26 | + { ColorPrimaries::Film, "Film" }, |
| 27 | + { ColorPrimaries::SMPTE170M, "SMPTE170M" }, |
| 28 | + { ColorPrimaries::SMPTE240M, "SMPTE240M" }, |
| 29 | + { ColorPrimaries::SMPTE428, "SMPTE428" }, |
| 30 | + { ColorPrimaries::SMPTE431, "SMPTE431" }, |
| 31 | + { ColorPrimaries::SMPTE432, "SMPTE432" } |
| 32 | +}; |
| 33 | +static const int num_primaries = sizeof(primary_table) / sizeof(primary_table[0]); |
| 34 | + |
| 35 | +class HDRGamutTest : public Screen { |
| 36 | +public: |
| 37 | + HDRGamutTest() : Screen(Vector2i(800, 600), "NanoGUI test", /* resizable */ true, /* maximized */ false, |
| 38 | + /* fullscreen */ false, /* depth_buffer */ true, /* stencil_buffer */ false, |
| 39 | + /* float_buffer */ true, /* gl_major */ 3, /* gl_minor */ 2) { |
| 40 | + inc_ref(); |
| 41 | + |
| 42 | + Window *window = new Window(this, "HDR & Color gamut test"); |
| 43 | + window->set_layout(new GroupLayout()); |
| 44 | + |
| 45 | + m_texture = new Texture( |
| 46 | + Texture::PixelFormat::RGBA, |
| 47 | + Texture::ComponentFormat::Float32, |
| 48 | + Vector2i(512, 400), |
| 49 | + Texture::InterpolationMode::Nearest, |
| 50 | + Texture::InterpolationMode::Nearest |
| 51 | + ); |
| 52 | + |
| 53 | + new Label(window, "Primaries"); |
| 54 | + |
| 55 | + std::vector<std::string> primary_names; |
| 56 | + for (int i = 0; i < num_primaries; ++i) { |
| 57 | + primary_names.push_back(primary_table[i].name); |
| 58 | + } |
| 59 | + |
| 60 | + ColorPrimaries screen_primary = from_screen(this); |
| 61 | + int primary_index = 0; |
| 62 | + for (int i = 0; i < num_primaries; ++i) { |
| 63 | + if (primary_table[i].primary == screen_primary) { |
| 64 | + primary_index = i; |
| 65 | + break; |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + ComboBox *primaries_cbox = new ComboBox(window, primary_names); |
| 70 | + primaries_cbox->set_selected_index(primary_index); |
| 71 | + |
| 72 | + primaries_cbox->set_callback([this](int index) { |
| 73 | + auto chroma_array = chroma(primary_table[index].primary); |
| 74 | + m_rec709_matrix = chroma_to_rec709_matrix(chroma_array); |
| 75 | + update_texture(); |
| 76 | + }); |
| 77 | + |
| 78 | + // Initialize with current selection |
| 79 | + auto chroma_array = chroma(primary_table[primary_index].primary); |
| 80 | + m_rec709_matrix = chroma_to_rec709_matrix(chroma_array); |
| 81 | + |
| 82 | + new Label(window, "Linear ramps (0..4), bars mark integer values. Top: LDR sRGB, bottom: HDR with chosen primaries."); |
| 83 | + |
| 84 | + ImageView *img = new ImageView(window); |
| 85 | + img->set_image(m_texture); |
| 86 | + img->set_size(Vector2i(512, 400)); |
| 87 | + img->set_scale(pixel_ratio()); |
| 88 | + |
| 89 | + perform_layout(); |
| 90 | + window->set_position(Vector2i(5, 5)); |
| 91 | + |
| 92 | + update_texture(); |
| 93 | + } |
| 94 | + |
| 95 | +private: |
| 96 | + float to_srgb(float value) { |
| 97 | + float sign = value < 0 ? -1.0f : 1.0f; |
| 98 | + value = std::abs(value); |
| 99 | + return sign * (value < 0.0031308f ? value * 12.92f |
| 100 | + : 1.055f * std::pow(value, 1.0f/2.4f) - 0.055f); |
| 101 | + } |
| 102 | + |
| 103 | + void update_texture() { |
| 104 | + const int width = 512, height = 400; |
| 105 | + std::vector<float> img(width * height * 4, 0.0f); |
| 106 | + |
| 107 | + // Draw vertical grid lines at quarter positions |
| 108 | + for (int y = 0; y < height; ++y) { |
| 109 | + for (int x : {0, width/4, width/2, 3*width/4, width-1}) { |
| 110 | + float* p = &img[(y * width + x) * 4]; |
| 111 | + p[0] = p[1] = p[2] = p[3] = 1.0f; |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + // Draw color bars: red, green, blue, white |
| 116 | + const struct { int y0, y1; Vector3f srgb, primary; } bars[] = { |
| 117 | + {10, 90, {1, 0, 0}, m_rec709_matrix[0]}, |
| 118 | + {110, 190, {0, 1, 0}, m_rec709_matrix[1]}, |
| 119 | + {210, 290, {0, 0, 1}, m_rec709_matrix[2]}, |
| 120 | + {310, 390, {1, 1, 1}, {1, 1, 1}} |
| 121 | + }; |
| 122 | + |
| 123 | + for (auto& bar : bars) { |
| 124 | + int mid_y = (bar.y0 + bar.y1) / 2; |
| 125 | + for (int y = bar.y0; y < bar.y1; ++y) { |
| 126 | + bool is_srgb = (y < mid_y); |
| 127 | + for (int x = 0; x < width; ++x) { |
| 128 | + float ramp = x * 4.0f / (width - 1); |
| 129 | + float* p = &img[(y * width + x) * 4]; |
| 130 | + for (int c = 0; c < 3; ++c) { |
| 131 | + float val = ramp * (is_srgb ? bar.srgb[c] : bar.primary[c]); |
| 132 | + p[c] = to_srgb(is_srgb ? std::min(val, 1.0f) : val); |
| 133 | + } |
| 134 | + p[3] = 1.0f; |
| 135 | + } |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + m_texture->upload((uint8_t*)img.data()); |
| 140 | + } |
| 141 | + |
| 142 | +private: |
| 143 | + ref<Texture> m_texture; |
| 144 | + Matrix3f m_rec709_matrix; |
| 145 | +}; |
| 146 | + |
| 147 | +int main(int /* argc */, char ** /* argv */) { |
| 148 | + nanogui::init(); |
| 149 | + |
| 150 | + { |
| 151 | + ref<HDRGamutTest> app = new HDRGamutTest(); |
| 152 | + app->dec_ref(); |
| 153 | + app->draw_all(); |
| 154 | + app->set_visible(true); |
| 155 | + nanogui::run(RunMode::Lazy); |
| 156 | + } |
| 157 | + |
| 158 | + nanogui::shutdown(); |
| 159 | + return 0; |
| 160 | +} |
0 commit comments