20 #include <nori/block.h>
21 #include <nori/parser.h>
22 #include <nori/bitmap.h>
23 #include <nanogui/shader.h>
24 #include <nanogui/label.h>
25 #include <nanogui/slider.h>
26 #include <nanogui/button.h>
27 #include <nanogui/layout.h>
28 #include <nanogui/icons.h>
29 #include <nanogui/renderpass.h>
30 #include <nanogui/progressbar.h>
31 #include <nanogui/texture.h>
33 #include <nanogui/opengl.h>
35 #include <filesystem/resolver.h>
40 #define PANEL_HEIGHT 50
42 NoriCanvas::NoriCanvas(nanogui::Widget* parent,
const ImageBlock& block) : nanogui::Canvas(parent, 1), m_block(block) {
43 using namespace nanogui;
45 m_shader =
new Shader(
52 uniform int borderSize;
57 gl_Position = vec4(position.x * 2 - 1, position.y * 2 - 1, 0.0, 1.0);
59 // Crop away image border (due to pixel filter)
60 vec2 total_size = size + 2 * borderSize;
61 vec2 scale = size / total_size;
62 uv = vec2(position.x * scale.x + borderSize / total_size.x,
63 1 - (position.y * scale.y + borderSize / total_size.y));
67 uniform sampler2D source;
71 float toSRGB(float value) {
72 if (value < 0.0031308)
74 return 1.055 * pow(value, 0.41666) - 0.055;
77 vec4 color = texture(source, uv);
78 color *= scale / color.w;
79 out_color = vec4(toSRGB(color.r), toSRGB(color.g), toSRGB(color.b), 1);
84 uint32_t indices[3 * 2] = {
88 float positions[2 * 4] = {
95 m_shader->set_buffer(
"indices", VariableType::UInt32, { 3 * 2 }, indices);
96 m_shader->set_buffer(
"position", VariableType::Float32, { 4, 2 }, positions);
98 const Vector2i& size = m_block.getSize();
99 m_shader->set_uniform(
"size", nanogui::Vector2i(size.x(), size.y()));
100 m_shader->set_uniform(
"borderSize", m_block.getBorderSize());
106 void NoriCanvas::draw_contents() {
109 m_shader->set_uniform(
"scale", m_scale);
110 m_texture->upload((uint8_t*)m_block.data());
111 m_shader->set_texture(
"source", m_texture);
113 m_shader->draw_array(nanogui::Shader::PrimitiveType::Triangle, 0, 6,
true);
119 void NoriCanvas::update() {
120 using namespace nanogui;
122 const Vector2i& size = m_block.getSize();
123 const int borderSize = m_block.getBorderSize();
127 Texture::PixelFormat::RGBA,
128 Texture::ComponentFormat::Float32,
129 nanogui::Vector2i(size.x() + 2 * borderSize,
130 size.y() + 2 * borderSize),
131 Texture::InterpolationMode::Bilinear,
132 Texture::InterpolationMode::Bilinear);
136 : nanogui::Screen(nanogui::
Vector2i(block.getSize().x(), block.getSize().y() + PANEL_HEIGHT),
"Nori", true),
137 m_block(block), m_renderThread(m_block)
139 using namespace nanogui;
141 set_size(nanogui::Vector2i(block.
getSize().x(), block.
getSize().y() + PANEL_HEIGHT));
144 panel =
new Widget(
this);
145 panel->set_width(m_block.getSize().x());
146 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 10, 10));
147 Button* buttonOpen =
new Button(panel,
"", FA_FOLDER);
149 buttonOpen->set_callback(
151 using FileType = std::pair<std::string, std::string>;
152 std::vector<FileType> filetypes = { FileType(
"xml",
"Nori Scene File"), FileType(
"exr",
"EXR Image File") };
153 std::string filename = nanogui::file_dialog(filetypes,
false);
154 drop_event({ filename });
157 buttonOpen->set_tooltip(
"Open XML or EXR file (Ctrl+O)");
159 m_progressBar =
new ProgressBar(panel);
160 m_progressBar->set_fixed_size(nanogui::Vector2i(200, 10));
161 Button* buttonStop =
new Button(panel,
"", FA_STOP);
162 buttonStop->set_callback(
164 m_renderThread.stopRendering();
167 buttonStop->set_tooltip(
"Abort rendering (Ctrl+Z)");
169 new Label(panel,
"Exposure: ",
"sans-bold");
170 m_slider =
new Slider(panel);
171 m_slider->set_value(0.5f);
172 m_slider->set_fixed_width(120);
173 m_slider->set_callback(
175 m_scale = std::pow(2.f, (value - 0.5f) * 20);
176 m_render_canvas->set_scale(m_scale);
180 Button* buttonSave =
new Button(panel,
"", FA_SAVE);
181 buttonSave->set_callback(
183 using FileType = std::pair<std::string, std::string>;
184 std::vector<FileType> filetypes = { FileType(
"",
"Image File Name") };
185 std::string filename;
187 auto file_exists = [](
const std::string& name) {
188 std::ifstream f(name.c_str());
192 filename = nanogui::file_dialog(filetypes,
true);
193 if (filename.empty() || (!file_exists(filename +
".png") && !file_exists(filename +
".exr")))
195 cerr <<
"Error: file \"" << filename <<
"\" already exists!" << endl;
197 if (!filename.empty()) {
199 std::unique_ptr<Bitmap> bitmap(m_block.toBitmap());
201 bitmap->array() *= m_scale;
202 bitmap->save(filename);
206 buttonSave->set_tooltip(
"Save rendering with the selected exposure to disk");
208 set_resize_callback([
this](nanogui::Vector2i) { requestLayoutUpdate(); });
210 m_render_canvas =
new NoriCanvas(
this, m_block);
211 m_render_canvas->set_background_color({ 100, 100, 100, 255 });
220 void NoriScreen::draw_contents() {
221 if (m_requiresLayoutUpdate) {
223 m_requiresLayoutUpdate =
false;
227 m_progressBar->set_value(m_renderThread.getProgress());
229 nanogui::Screen::draw_contents();
233 void NoriScreen::updateLayout() {
237 panel->set_position(nanogui::Vector2i((m_size.x() - panel->size().x()) / 2, 0));
240 nanogui::Vector2i contentOffset(0, PANEL_HEIGHT);
243 nanogui::Vector2i contentWindow = m_size - contentOffset;
244 nanogui::Vector2f blockSize(m_block.
getSize().x(), m_block.
getSize().y());
245 nanogui::Vector2f ratio = nanogui::Vector2f(contentWindow) / blockSize;
246 float minRatio = std::min(ratio.x(), ratio.y());
249 nanogui::Vector2i canvasSize = blockSize * minRatio * 0.98f;
250 nanogui::Vector2i canvasPosition = (contentWindow - canvasSize) / 2 + contentOffset;
252 m_render_canvas->set_fixed_size(canvasSize);
253 m_render_canvas->set_position(canvasPosition);
260 bool NoriScreen::drop_event(
const std::vector<std::string>& filenames) {
261 if (filenames.size() > 0) {
262 std::string filename = filenames[0];
263 if (filename.size() == 0) {
266 filesystem::path path(filename);
268 if (path.extension() ==
"xml") {
271 }
else if (path.extension() ==
"exr") {
275 cerr <<
"Error: unknown file \"" << filename
276 <<
"\", expected an extension of type .xml or .exr" << endl;
283 bool NoriScreen::keyboard_event(
int key,
int scancode,
int action,
int modifiers) {
284 const bool press = (action == GLFW_PRESS);
285 if (press && key == GLFW_KEY_O && modifiers & GLFW_MOD_CONTROL) {
286 using FileType = std::pair<std::string, std::string>;
287 std::vector<FileType> filetypes = { FileType(
"xml",
"Nori Scene File"), FileType(
"exr",
"EXR Image File") };
288 std::string filename = nanogui::file_dialog(filetypes,
false);
289 drop_event({ filename });
292 if (press && key == GLFW_KEY_Z && modifiers & GLFW_MOD_CONTROL) {
293 m_renderThread.stopRendering();
297 return nanogui::Screen::keyboard_event(key, scancode, action, modifiers);
300 void NoriScreen::adjustWindow(
Vector2i blockSize) {
302 const nanogui::Vector2i contentOffset(0, PANEL_HEIGHT);
303 const nanogui::Vector2f contentWindow = m_size - contentOffset;
306 float nPixels = contentWindow.x() * contentWindow.y();
307 float ratio = std::sqrt(nPixels / ((
float)blockSize.x() * blockSize.y()));
310 ratio = std::max(ratio, panel->size().x() / (
float)blockSize.x());
312 nanogui::Vector2i newSize(blockSize.x() * ratio + 1, blockSize.y() * ratio + PANEL_HEIGHT);
317 void NoriScreen::openXML(
const std::string& filename) {
319 if(m_renderThread.isBusy()) {
320 cerr <<
"Error: rendering in progress, you need to wait until it's done" << endl;
330 m_render_canvas->update();
334 requestLayoutUpdate();
336 }
catch (
const std::exception &e) {
337 cerr <<
"Fatal error: " << e.what() << endl;
342 void NoriScreen::openEXR(
const std::string& filename) {
344 if(m_renderThread.isBusy()) {
345 cerr <<
"Error: rendering in progress, you need to wait until it's done" << endl;
351 m_block.init(
Vector2i(bitmap.cols(), bitmap.rows()),
nullptr);
354 m_render_canvas->update();
358 requestLayoutUpdate();
Stores a RGB high dynamic-range bitmap.
Weighted pixel storage for a rectangular subregion of an image.
void unlock() const
Unlock the image block.
const Vector2i & getSize() const
Return the size of the block within the main image.
void fromBitmap(const Bitmap &bitmap)
Convert a bitmap into an image block.
void lock() const
Lock the image block (using an internal mutex)
void renderScene(const std::string &filename)
Superclass of all texture.