From 65d26692ae8736f67f3951f088b26086eeb6b829 Mon Sep 17 00:00:00 2001
From: Julian T <julian@jtle.dk>
Date: Thu, 13 Aug 2020 21:17:30 +0200
Subject: Added nicer ui which can save

---
 app/draw.cpp        |  6 +-----
 app/draw.hpp        |  4 +---
 app/mainwindow.cpp  | 32 +++++++++++++++++++++++++++++++-
 app/mainwindow.hpp  | 10 ++++++++++
 app/rendercoord.cpp | 45 ++++++++++++++++++++++++++++++++++++++++-----
 app/rendercoord.hpp | 16 +++++++++++++++-
 src/render.cpp      | 17 ++++++++---------
 src/render.hpp      | 17 +++++++++++++----
 8 files changed, 119 insertions(+), 28 deletions(-)

diff --git a/app/draw.cpp b/app/draw.cpp
index 469b469..b3187be 100644
--- a/app/draw.cpp
+++ b/app/draw.cpp
@@ -7,16 +7,12 @@
 #include <qwindowdefs.h>
 #include <iostream>
 
-DrawWidget::DrawWidget(unsigned width, unsigned height) : QWidget(), m_timer(this) {
+DrawWidget::DrawWidget(unsigned width, unsigned height) : QWidget() {
     m_width = width;
     m_height = height;
     m_drawbuffer = new QRgb[width * height];
 
     m_img = QImage((uchar*)m_drawbuffer, width, height, QImage::Format_ARGB32);
-
-    QObject::connect(&m_timer, &QTimer::timeout, this, &DrawWidget::redraw);
-
-    m_timer.start(500);
 }
 
 void DrawWidget::paintEvent(QPaintEvent*) {
diff --git a/app/draw.hpp b/app/draw.hpp
index f8e93a4..cee5734 100644
--- a/app/draw.hpp
+++ b/app/draw.hpp
@@ -13,6 +13,7 @@ class DrawWidget : public QWidget {
         void paintEvent(QPaintEvent*);
 
         QRgb *m_drawbuffer;
+        QImage m_img;
         unsigned m_width, m_height;
 
         ~DrawWidget();
@@ -20,10 +21,7 @@ class DrawWidget : public QWidget {
         void redraw();
 
     private:
-        QImage m_img;
         unsigned char i;
-
-        QTimer m_timer;
 };
 
 #endif
diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp
index 3077168..a365f38 100644
--- a/app/mainwindow.cpp
+++ b/app/mainwindow.cpp
@@ -1,12 +1,42 @@
 #include "mainwindow.hpp"
+#include <qaction.h>
+#include <qapplication.h>
+#include <qlabel.h>
+#include <qnamespace.h>
+#include <QFileDialog>
+#include <QMessageBox>
 
 MainWindow::MainWindow(Renderer r)
     : m_drawer(500, 500),
-    m_render(this, m_drawer, r)
+    runstatus("Not running", this),
+    m_render(this, m_drawer, r, &runstatus)
 {
 
     setCentralWidget(&m_drawer);
 
+    auto saveAct = new QAction(tr("&Save as"), this);
+    saveAct->setStatusTip(tr("Save the rendered image"));
+    connect(saveAct, &QAction::triggered, this, &MainWindow::saveimage);
 
+    fileMenu = menuBar()->addMenu(tr("&File"));
+    fileMenu->addAction(saveAct);
 
+    helpMenu = menuBar()->addMenu(tr("&Help"));
+    helpMenu->addAction(tr("About Qt"), qApp, &QApplication::aboutQt);
+
+    statusBar()->addWidget(&runstatus);
+}
+
+void MainWindow::saveimage() {
+
+    QGuiApplication::setOverrideCursor(Qt::WaitCursor);
+    QString fileName = QFileDialog::getSaveFileName(this,
+            tr("Save image"), "", tr("PNG image (*.png);;All Files (*)"));
+    if (fileName.isEmpty()) {
+        return;
+    }
+
+    if (!m_drawer.m_img.save(fileName)) {
+        QMessageBox::information(this, tr("Unable to save file"), "");
+    }
 }
diff --git a/app/mainwindow.hpp b/app/mainwindow.hpp
index 7e356a0..b2566f4 100644
--- a/app/mainwindow.hpp
+++ b/app/mainwindow.hpp
@@ -2,9 +2,13 @@
 #define MAIN_H
 
 #include <QMainWindow>
+#include <QLabel>
+#include <QMenuBar>
+#include <QStatusBar>
 
 #include "draw.hpp"
 #include "rendercoord.hpp"
+#include <qmainwindow.h>
 #include <render.hpp>
 
 class MainWindow : public QMainWindow {
@@ -13,9 +17,15 @@ class MainWindow : public QMainWindow {
     public:
         MainWindow(Renderer r);
 
+    private slots:
+        void saveimage();
     private:
         DrawWidget m_drawer;
+        QLabel runstatus;
         RenderCoordinator m_render;
+
+        QMenu *fileMenu;
+        QMenu *helpMenu;
 };
 
 #endif
diff --git a/app/rendercoord.cpp b/app/rendercoord.cpp
index 9c59acd..a5a3da5 100644
--- a/app/rendercoord.cpp
+++ b/app/rendercoord.cpp
@@ -4,6 +4,7 @@
 #include <qrgb.h>
 
 #include <render.hpp>
+#include <sstream>
 
 uint32_t colorToUint32(const Color &c) {
     Color cnew = Color(c);
@@ -28,11 +29,12 @@ void RenderThread::run() {
     while (1) {
         // Wait for work
         m_work.acquire();
-        m_render.m_sampler.seed(100);
 
         // Very expensive, but necesary to get live rendering
         Color *sum = new Color[m_render.m_width * m_render.m_height];
 
+        m_current_samples = 0;
+
         for (unsigned sample = 1; sample < m_samples+1; sample++) {
             for (unsigned x = 0; x < m_render.m_width; x++) {
                 for (unsigned y = 0; y < m_render.m_height; y++) {
@@ -42,6 +44,8 @@ void RenderThread::run() {
                     m_writebuffer[index] = colorToUint32(sum[index] / sample);
                 }
             }
+
+            m_current_samples = sample;
         }
 
         // Signal done
@@ -58,15 +62,25 @@ int RenderThread::render(QRgb *buffer, unsigned samples) {
     m_writebuffer = buffer;
     m_samples = samples;
     m_work.release();
+
     return 0;
 }
 
-RenderCoordinator::RenderCoordinator(QObject *parent, DrawWidget &target, Renderer r) 
+// Running on main thread
+unsigned RenderThread::current_samples() {
+    // No sync should not be a problem here.
+    return m_current_samples;
+}
+
+RenderCoordinator::RenderCoordinator(QObject *parent, DrawWidget &target, Renderer r, QLabel *status) 
     : QObject(parent),
     m_target(target),
     m_renderer(r),
-    m_worker(m_renderer, this)
+    m_worker(m_renderer, this),
+    m_timer(this)
 {
+    m_status = status;
+
     m_worker.start();
 
     QObject::connect(&m_worker, &RenderThread::done,
@@ -74,10 +88,31 @@ RenderCoordinator::RenderCoordinator(QObject *parent, DrawWidget &target, Render
 
     m_worker.render(target.m_drawbuffer, 100);
 
+    m_state = running;
+    updateUi();
+
+    QObject::connect(&m_timer, &QTimer::timeout, this, &RenderCoordinator::updateUi);
+
+    m_timer.start(500);
+
 }
 
 void RenderCoordinator::workerDone(unsigned workerid) {
-    std::cout << workerid << " done!" << std::endl;
-    m_target.repaint();
+    m_state = stopped;
+    m_timer.stop();
+    updateUi();
 }
 
+
+void RenderCoordinator::updateUi() {
+    m_target.repaint();
+
+    if (!m_status) {
+        return;
+    }
+
+    std::ostringstream status;
+    status << states[m_state] << " " << m_worker.current_samples() << " samples";
+
+    m_status->setText(QString::fromStdString(status.str()));
+}
diff --git a/app/rendercoord.hpp b/app/rendercoord.hpp
index 6aa8698..113b4dc 100644
--- a/app/rendercoord.hpp
+++ b/app/rendercoord.hpp
@@ -2,6 +2,7 @@
 #define RENDER_THREAD_H
 
 #include "draw.hpp"
+#include <qlabel.h>
 #include <render.hpp>
 
 #include <qobject.h>
@@ -19,6 +20,7 @@ class RenderThread : public QThread {
 
         // Returns 0 if successful or 1 if busy
         int render(QRgb *buffer, unsigned samples);
+        unsigned current_samples();
 
     signals:
         void done(unsigned workerid);
@@ -30,6 +32,7 @@ class RenderThread : public QThread {
 
         QRgb *m_writebuffer;
         unsigned m_samples;
+        unsigned m_current_samples;
 
         Renderer m_render;
 
@@ -38,23 +41,34 @@ class RenderThread : public QThread {
         unsigned m_id;
 };
 
+const std::string states[] = { "Stopped", "Running" };
+enum State { stopped, running };
+
 class RenderCoordinator : public QObject {
     Q_OBJECT
 
     public:
-        RenderCoordinator(QObject *parent, DrawWidget &target, Renderer r);
+        RenderCoordinator(QObject *parent, DrawWidget &target, Renderer r, QLabel *status=nullptr);
         void setSamples(unsigned samples);
         void render();
 
     public slots:
         void workerDone(unsigned workerid);
 
+    private slots:
+        void updateUi();
+
     private:
         DrawWidget &m_target;
 
         Renderer m_renderer;
         RenderThread m_worker;
 
+        QLabel *m_status;
+        QTimer m_timer;
+
+        State m_state;
+
         unsigned m_samples;
 };
 
diff --git a/src/render.cpp b/src/render.cpp
index 0bbe5cb..c278797 100644
--- a/src/render.cpp
+++ b/src/render.cpp
@@ -13,28 +13,26 @@
 
 const Vec3d up = Vec3d(0, 1, 0);
 
-Sampler::Sampler() {
-    m_seed = 0;
-}
-
-void Sampler::seed(unsigned seed) {
+void Random::seed(unsigned seed) {
     for (unsigned i = 0; i < seed; i++) {
         rand_r(&m_seed);
     }
 }
 
-double Sampler::random() {
+double Random::operator()() {
     return (double)rand_r(&m_seed) / (double)RAND_MAX;
 }
 
+Sampler::Sampler(Random &src) : m_src(src) { }
+
 Vec3d Sampler::sample(const Vec3d &norm) {
     /*
     auto theta = asin(pow(1 - random(), (double)1 / (1 + SAMPLING_POWER)));
     auto phi = 2 * M_PI * random();
     */
 
-    auto theta = 2.0 * M_PI * random();
-    auto phi = acos(2.0 * random() - 1.0);
+    auto theta = 2.0 * M_PI * m_src();
+    auto phi = acos(2.0 * m_src() - 1.0);
 
     auto sinphi = sin(phi);
 
@@ -48,6 +46,7 @@ Vec3d Sampler::sample(const Vec3d &norm) {
 }
 
 Renderer::Renderer(const Scene &scn, Vec3d eye, Vec3d target, unsigned width, unsigned height, unsigned maxhops) : 
+    m_sampler(m_random),
     m_scn(scn)
 {
     m_eye = eye;
@@ -88,11 +87,11 @@ Ray Renderer::findray(double x, double y) const {
 }
 
 Color Renderer::render(unsigned x, unsigned y, unsigned samples) {
-    auto r = findray(x, y);
 
     Color sum(0, 0, 0);
 
     for (unsigned i = 0; i < samples; i++) {
+        auto r = findray(x + m_random(), y + m_random());
         sum += pathtrace_sample(r, 0);
     }
 
diff --git a/src/render.hpp b/src/render.hpp
index 1274bb5..7557fce 100644
--- a/src/render.hpp
+++ b/src/render.hpp
@@ -5,18 +5,25 @@
 #include "ray.hpp"
 #include "scene.hpp"
 
+class Random {
+    public:
+        void seed(unsigned seed);
+        double operator()();
+
+    private:
+        unsigned m_seed;
+};
+
 // Samples a random direction in a hemisphere, cosine weighed
 // https://blog.thomaspoulet.fr/uniform-sampling-on-unit-hemisphere/
 class Sampler {
     public:
-        Sampler();
-        void seed(unsigned seed);
+        Sampler(Random &src);
 
         Vec3d sample(const Vec3d &norm);
 
     private:
-        double random();
-        unsigned m_seed;
+        Random &m_src;
 };
 
 class Renderer {
@@ -40,6 +47,8 @@ class Renderer {
 
     const Scene &m_scn;
 
+    Random m_random;
+
     // User options
     Vec3d m_eye, m_target;
     unsigned m_maxhops;
-- 
cgit v1.2.3