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() {
110 m_shader->set_uniform(
"scale", m_scale);
111 m_texture->upload((uint8_t*)m_block.data());
112 m_shader->set_texture(
"source", m_texture);
114 m_shader->draw_array(nanogui::Shader::PrimitiveType::Triangle, 0, 6,
true);
120 void NoriCanvas::update() {
121 using namespace nanogui;
123 const Vector2i& size = m_block.getSize();
124 const int borderSize = m_block.getBorderSize();
128 Texture::PixelFormat::RGBA,
129 Texture::ComponentFormat::Float32,
130 nanogui::Vector2i(size.x() + 2 * borderSize,
131 size.y() + 2 * borderSize),
132 Texture::InterpolationMode::Nearest,
133 Texture::InterpolationMode::Nearest);
137 : nanogui::Screen(nanogui::
Vector2i(block.getSize().x(), block.getSize().y() + PANEL_HEIGHT),
"Nori", true),
138 m_block(block), m_renderThread(m_block)
140 using namespace nanogui;
142 set_size(nanogui::Vector2i(block.
getSize().x(), block.
getSize().y() + PANEL_HEIGHT));
145 panel =
new Widget(
this);
146 panel->set_width(m_block.getSize().x());
147 panel->set_layout(
new BoxLayout(Orientation::Horizontal, Alignment::Middle, 10, 10));
148 Button* buttonOpen =
new Button(panel,
"", FA_FOLDER);
150 buttonOpen->set_callback(
152 using FileType = std::pair<std::string, std::string>;
153 std::vector<FileType> filetypes = { FileType(
"xml",
"Nori Scene File"), FileType(
"exr",
"EXR Image File") };
154 std::string filename = nanogui::file_dialog(filetypes,
false);
155 drop_event({ filename });
158 buttonOpen->set_tooltip(
"Open XML or EXR file (Ctrl+O)");
160 m_progressBar =
new ProgressBar(panel);
161 m_progressBar->set_fixed_size(nanogui::Vector2i(200, 10));
162 Button* buttonStop =
new Button(panel,
"", FA_STOP);
163 buttonStop->set_callback(
165 m_renderThread.stopRendering();
168 buttonStop->set_tooltip(
"Abort rendering (Ctrl+Z)");
170 new Label(panel,
"Exposure: ",
"sans-bold");
171 m_slider =
new Slider(panel);
172 m_slider->set_value(0.5f);
173 m_slider->set_fixed_width(120);
174 m_slider->set_callback(
176 m_scale = std::pow(2.f, (value - 0.5f) * 20);
177 m_render_canvas->set_scale(m_scale);
181 Button* buttonSave =
new Button(panel,
"", FA_SAVE);
182 buttonSave->set_callback(
184 using FileType = std::pair<std::string, std::string>;
185 std::vector<FileType> filetypes = { FileType(
"",
"Image File Name") };
186 std::string filename;
188 auto file_exists = [](
const std::string& name) {
189 std::ifstream f(name.c_str());
193 filename = nanogui::file_dialog(filetypes,
true);
194 if (filename.empty() || (!file_exists(filename +
".png") && !file_exists(filename +
".exr")))
196 cerr <<
"Error: file \"" << filename <<
"\" already exists!" << endl;
198 if (!filename.empty()) {
200 std::unique_ptr<Bitmap> bitmap(m_block.toBitmap());
202 bitmap->array() *= m_scale;
203 bitmap->save(filename);
207 buttonSave->set_tooltip(
"Save rendering with the selected exposure to disk");
209 set_resize_callback([
this](nanogui::Vector2i) { requestLayoutUpdate(); });
211 m_render_canvas =
new NoriCanvas(
this, m_block);
212 m_render_canvas->set_background_color({ 100, 100, 100, 255 });
221 void NoriScreen::draw_contents() {
222 if (m_requiresLayoutUpdate) {
224 m_requiresLayoutUpdate =
false;
228 m_progressBar->set_value(m_renderThread.getProgress());
230 nanogui::Screen::draw_contents();
234 void NoriScreen::updateLayout() {
238 panel->set_position(nanogui::Vector2i((m_size.x() - panel->size().x()) / 2, 0));
241 nanogui::Vector2i contentOffset(0, PANEL_HEIGHT);
244 nanogui::Vector2i contentWindow = m_size - contentOffset;
245 nanogui::Vector2f blockSize(m_block.
getSize().x(), m_block.
getSize().y());
246 nanogui::Vector2f ratio = (blockSize / nanogui::Vector2f(contentWindow));
247 float maxRatio = std::max(ratio.x(), ratio.y());
248 nanogui::Vector2i canvasSize = blockSize / maxRatio;
250 m_render_canvas->set_fixed_size(canvasSize);
251 m_render_canvas->set_position({ ((contentWindow - canvasSize) / 2 + contentOffset).x(), PANEL_HEIGHT });
258 bool NoriScreen::drop_event(
const std::vector<std::string>& filenames) {
259 if (filenames.size() > 0) {
260 std::string filename = filenames[0];
261 if (filename.size() == 0) {
264 filesystem::path path(filename);
266 if (path.extension() ==
"xml") {
269 }
else if (path.extension() ==
"exr") {
273 cerr <<
"Error: unknown file \"" << filename
274 <<
"\", expected an extension of type .xml or .exr" << endl;
281 bool NoriScreen::keyboard_event(
int key,
int scancode,
int action,
int modifiers) {
282 const bool press = (action == GLFW_PRESS);
283 if (press && key == GLFW_KEY_O && modifiers & GLFW_MOD_CONTROL) {
284 using FileType = std::pair<std::string, std::string>;
285 std::vector<FileType> filetypes = { FileType(
"xml",
"Nori Scene File"), FileType(
"exr",
"EXR Image File") };
286 std::string filename = nanogui::file_dialog(filetypes,
false);
287 drop_event({ filename });
290 if (press && key == GLFW_KEY_Z && modifiers & GLFW_MOD_CONTROL) {
291 m_renderThread.stopRendering();
295 return nanogui::Screen::keyboard_event(key, scancode, action, modifiers);
298 void NoriScreen::openXML(
const std::string& filename) {
300 if(m_renderThread.isBusy()) {
301 cerr <<
"Error: rendering in progress, you need to wait until it's done" << endl;
311 m_render_canvas->update();
314 requestLayoutUpdate();
316 }
catch (
const std::exception &e) {
317 cerr <<
"Fatal error: " << e.what() << endl;
322 void NoriScreen::openEXR(
const std::string& filename) {
324 if(m_renderThread.isBusy()) {
325 cerr <<
"Error: rendering in progress, you need to wait until it's done" << endl;
331 m_block.init(
Vector2i(bitmap.cols(), bitmap.rows()),
nullptr);
334 m_render_canvas->update();
337 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.