Browse Source

added video

Nicolas Winkler 6 năm trước cách đây
mục cha
commit
bec7179920
15 tập tin đã thay đổi với 730 bổ sung111 xóa
  1. 40 39
      Almond.cpp
  2. 11 19
      Almond.h
  3. 19 16
      Almond.pro
  4. 53 3
      Almond.ui
  5. 143 25
      MandelWidget.cpp
  6. 21 3
      MandelWidget.h
  7. 4 3
      VideoStream.cpp
  8. 1 0
      VideoStream.h
  9. 3 0
      benchmarkdialog.cpp
  10. 169 0
      exportdialogs.cpp
  11. 57 0
      exportdialogs.h
  12. 3 3
      exportimagedialog.ui
  13. 178 0
      exportvideodialog.ui
  14. 10 0
      libmandel/include/MandelUtil.h
  15. 18 0
      libmandel/src/MandelUtil.cpp

+ 40 - 39
Almond.cpp

@@ -2,6 +2,7 @@
 #include <QIntValidator>
 #include <QFileDialog>
 #include <QMessageBox>
+#include <QGradient>
 #include "benchmarkdialog.h"
 
 #include <cmath>
@@ -16,6 +17,7 @@ Almond::Almond(QWidget *parent) :
     //qRegisterMetaType<MandelWidget>("MandelWidget");
     printf("created!\n");
     ui.verticalLayout_left->addWidget(mw.get());
+    ui.maxIterations->setValidator(new QIntValidator(1, 1000000000, this));
     //ui.verticalLayout_left->addWidget(new MyGLWidget(ui.centralWidget));
     //mw->show();
 }
@@ -23,6 +25,7 @@ Almond::Almond(QWidget *parent) :
 void Almond::on_pushButton_clicked()
 {
     ExportImageDialog dialog(this);
+    //dialog.show();
     auto response = dialog.exec();
     if (response == 1) {
         mnd::MandelInfo mi;
@@ -41,60 +44,58 @@ void Almond::on_pushButton_clicked()
 }
 
 
-ExportImageDialog::ExportImageDialog(QWidget* parent) :
-    QDialog{ parent }
-{
-    eid.setupUi(this);
-    eid.maxIterations->setValidator(new QIntValidator(1, 10000000, this));
-    eid.imgWidth->setValidator(new QIntValidator(1, 10000000, this));
-    eid.imgHeight->setValidator(new QIntValidator(1, 10000000, this));
-}
-
-
-int ExportImageDialog::getMaxIterations(void) const
+void Almond::on_pushButton_2_clicked()
 {
-    return std::stoi(eid.maxIterations->text().toStdString());
+    BenchmarkDialog bd(mandelContext, this);
+    //bd.show();
+    bd.exec();
 }
 
-
-int ExportImageDialog::getWidth(void) const
+void Almond::on_zoom_out_clicked()
 {
-    return std::stoi(eid.imgWidth->text().toStdString());
+    mw->zoom(2);
 }
 
-
-int ExportImageDialog::getHeight(void) const
+void Almond::on_zoom_in_clicked()
 {
-    return std::stoi(eid.imgHeight->text().toStdString());
+    mw->zoom(0.5);
 }
 
-
-QString ExportImageDialog::getPath(void) const
+void Almond::on_maxIterations_editingFinished()
 {
-    return eid.savePath->text();
+    QString text = ui.maxIterations->text();
+    int maxIter = text.toInt();
+    mw->setMaxIterations(maxIter);
 }
 
-void ExportImageDialog::on_pushButton_clicked()
+void Almond::on_chooseGradient_clicked()
 {
-    QString saveAs = QFileDialog::getSaveFileName(this,
-            tr("Save exported image"), "",
-            tr("PNG image (*.png);;JPEG image (*.jpg);;All Files (*)"));
-    eid.savePath->setText(saveAs);
-    this->repaint();
+    QGradient qg;
 }
 
-void ExportImageDialog::on_buttonBox_accepted()
+void Almond::on_exportVideo_clicked()
 {
-    if (eid.savePath->text() == "") {
-        QMessageBox msgBox;
-        msgBox.setText("Please specify a path.");
-        msgBox.exec();
-        reject();
+    ExportVideoInfo evi;
+    evi.start = mnd::MandelViewport::standardView();
+    evi.end = mw->getViewport();
+    ExportVideoDialog dialog(this, evi);
+    //dialog.show();
+    auto response = dialog.exec();
+    printf("dialog executed\n"); fflush(stdout);
+    if (response == 1) {
+        mnd::MandelInfo mi;
+        evi = dialog.getExportVideoInfo();
+        //Video
+        /*mi.maxIter = dialog.getMaxIterations();
+        mi.view = mw->getViewport();
+        mi.bWidth = dialog.getWidth();
+        mi.bHeight = dialog.getHeight();
+        mi.view.adjustAspectRatio(mi.bWidth, mi.bHeight);
+        mnd::Generator& g = mandelContext.getDefaultGenerator();
+        auto fmap = Bitmap<float>(mi.bWidth, mi.bHeight);
+        g.generate(mi, fmap.pixels.get());
+        auto bitmap = fmap.map<RGBColor>([](float i) { return i < 0 ? RGBColor{ 0,0,0 } : RGBColor{ uint8_t(cos(i * 0.015f) * 127 + 127), uint8_t(sin(i * 0.01f) * 127 + 127), uint8_t(i) }; });//uint8_t(::sin(i * 0.01f) * 100 + 100), uint8_t(i) }; });
+        QImage img((unsigned char*)bitmap.pixels.get(), bitmap.width, bitmap.height, bitmap.width * 3, QImage::Format_RGB888);
+        img.save(dialog.getPath());*/
     }
 }
-
-void Almond::on_pushButton_2_clicked()
-{
-    BenchmarkDialog bd(mandelContext, this);
-    bd.exec();
-}

+ 11 - 19
Almond.h

@@ -1,12 +1,11 @@
 #pragma once
 
 #include <QtWidgets/QMainWindow>
-#include <QtWidgets/QDialog>
 #include "ui_Almond.h"
-#include "ui_exportimagedialog.h"
 
 #include <Mandel.h>
 #include "MandelWidget.h"
+#include "exportdialogs.h"
 
 #include <memory>
 
@@ -24,24 +23,17 @@ private slots:
 
     void on_pushButton_2_clicked();
 
-private:
-    Ui::AlmondClass ui;
-};
+    void on_zoom_out_clicked();
 
+    void on_zoom_in_clicked();
 
-class ExportImageDialog : public QDialog
-{
-    Q_OBJECT
-private:
-    Ui::ExportImageDialog eid;
-public:
-    ExportImageDialog(QWidget* parent);
+    void on_maxIterations_editingFinished();
 
-    int getMaxIterations(void) const;
-    int getWidth(void) const;
-    int getHeight(void) const;
-    QString getPath(void) const;
-private slots:
-    void on_pushButton_clicked();
-    void on_buttonBox_accepted();
+    void on_chooseGradient_clicked();
+
+    void on_exportVideo_clicked();
+
+private:
+    Ui::AlmondClass ui;
 };
+

+ 19 - 16
Almond.pro

@@ -32,6 +32,7 @@ SOURCES += \
         SectionManager.cpp \
         VideoStream.cpp \
         benchmarkdialog.cpp \
+        exportdialogs.cpp \
         main.cpp
 
 HEADERS += \
@@ -41,12 +42,14 @@ HEADERS += \
         MandelWidget.h \
         SectionManager.h \
         VideoStream.h \
-        benchmarkdialog.h
+        benchmarkdialog.h \
+        exportdialogs.h
 
 FORMS += \
         Almond.ui \
         benchmarks.ui \
-        exportimagedialog.ui
+        exportimagedialog.ui \
+        exportvideodialog.ui
 
 
 # Default rules for deployment.
@@ -64,44 +67,44 @@ else:unix:QMAKE_LFLAGS+= -fopenmp
 LIBS += -fopenmp
 unix:LIBS += -lm -latomic
 
-#win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
-#else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
-#else:unix: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
+win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
+else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
+else:unix: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
 
-#INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
-#DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
+INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
+DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
 
 #win32:CONFIG(release, debug|release): LIBS += -L$$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/lib/x86/' -lOpenCL
 #else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/lib/x86/' -lOpenCL
 #else:unix: LIBS += -lOpenCL
 
-#win32:INCLUDEPATH += $$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/include'
-#win32:DEPENDPATH += $$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/include'
+win32:INCLUDEPATH += $$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/include'
+win32:DEPENDPATH += $$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/include'
 
-#win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
-#else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
-#else:unix: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
+win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
+else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
+else:unix: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
-#unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavdevice
+unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavdevice
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
-#unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavfilter
+unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavfilter
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
-#unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavutil
+unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavutil
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
-#unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lswscale
+unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lswscale
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include

+ 53 - 3
Almond.ui

@@ -9,8 +9,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>712</width>
-    <height>268</height>
+    <width>752</width>
+    <height>609</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -21,7 +21,50 @@
     <item>
      <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="2,0">
       <item>
-       <layout class="QVBoxLayout" name="verticalLayout_left"/>
+       <layout class="QVBoxLayout" name="verticalLayout_left">
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_3">
+          <item>
+           <widget class="QPushButton" name="chooseGradient">
+            <property name="text">
+             <string>Choose Gradient</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="label">
+            <property name="text">
+             <string>Max. Iterations</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="maxIterations">
+            <property name="text">
+             <string>2000</string>
+            </property>
+            <property name="maxLength">
+             <number>32</number>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="zoom_in">
+            <property name="text">
+             <string>Zoom In</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="zoom_out">
+            <property name="text">
+             <string>Zoom Out</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
       </item>
       <item>
        <layout class="QVBoxLayout" name="verticalLayout_right">
@@ -46,6 +89,13 @@
          </spacer>
         </item>
         <item>
+         <widget class="QPushButton" name="exportVideo">
+          <property name="text">
+           <string>Export Video</string>
+          </property>
+         </widget>
+        </item>
+        <item>
          <widget class="QPushButton" name="pushButton">
           <property name="text">
            <string>Export Image</string>

+ 143 - 25
MandelWidget.cpp

@@ -6,6 +6,31 @@ using namespace mnd;
 #include <QOpenGLVertexArrayObject>
 
 
+Texture::Texture(const Bitmap<RGBColor>& bitmap) :
+    context{ nullptr }
+{
+    glGenTextures(1, &id);
+    glBindTexture(GL_TEXTURE_2D, id);
+
+    long lineLength = (bitmap.width * 3 + 3) & ~3;
+
+    unsigned char* pixels = new unsigned char[lineLength * bitmap.height];
+    for (int i = 0; i < bitmap.width; i++) {
+        for (int j = 0; j < bitmap.height; j++) {
+            int index = i * 3 + j * lineLength;
+            RGBColor c = bitmap.get(i, j);
+            pixels[index] = c.r;
+            pixels[index + 1] = c.g;
+            pixels[index + 2] = c.b;
+        }
+    }
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, int(bitmap.width), int(bitmap.height), 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+}
+
 Texture::Texture(const Bitmap<RGBColor>& bitmap, QOpenGLContext* context) :
     context{ context }
 {
@@ -34,7 +59,7 @@ Texture::Texture(const Bitmap<RGBColor>& bitmap, QOpenGLContext* context) :
 
 Texture::~Texture(void)
 {
-    context->functions()->glDeleteTextures(1, &id);
+    glDeleteTextures(1, &id);
 }
 
 
@@ -65,10 +90,22 @@ void Texture::drawRect(float x, float y, float width, float height)
 
 MandelView::MandelView(mnd::Generator& generator, MandelWidget* mWidget) :
     generator{ &generator },
-    mWidget{ mWidget },
-    context{ new QOpenGLContext(this) }
+    mWidget{ mWidget }
+    //context{ new QOpenGLContext(this) }
+{
+    //context->setShareContext(mWidget->context()->contextHandle());
+    hasToCalc.store(false);
+    finish.store(false);
+}
+
+
+MandelView::~MandelView(void)
 {
-    context->setShareContext(mWidget->context()->contextHandle());
+    finish.store(true);
+    condVar.notify_one();
+    //calcThread.wait(100);
+    calcThread.wait(100);
+    calcThread.terminate();
 }
 
 
@@ -77,6 +114,54 @@ void MandelView::setGenerator(mnd::Generator& value)
     generator = &value;
 }
 
+void MandelView::start(void)
+{
+    this->moveToThread(&calcThread);
+    connect(&calcThread, SIGNAL(started()), this, SLOT(loop()));
+    calcThread.start();
+}
+
+
+void MandelView::loop(void)
+{
+    printf("thread!\n"); fflush(stdout);
+    //QGLWidget* hiddenWidget = new QGLWidget(nullptr, mWidget);
+    //hiddenWidget->setVisible(false);
+    //hiddenWidget->context()->contextHandle()->moveToThread(&calcThread);
+    //QOpenGLContext* context = hiddenWidget->context()->contextHandle();
+    //context->setShareContext(mWidget->context()->contextHandle());
+    //context->create();
+    //printf("sharing: %d\n", QOpenGLContext::areSharing(hiddenWidget->context()->contextHandle(), mWidget->context()->contextHandle()));
+    //fflush(stdout);
+    //std::this_thread::sleep_for(std::chrono::milliseconds(3000));
+    std::unique_lock<std::mutex> lock(mut);
+    while(true) {
+        printf("calcing!\n"); fflush(stdout);
+        if (finish.load()) {
+            break;
+        }
+        if (hasToCalc.exchange(false)) {
+            const MandelInfo& mi = toCalc.load();
+            auto fmap = Bitmap<float>(mi.bWidth, mi.bHeight);
+            generator->generate(mi, fmap.pixels.get());
+            auto* bitmap = new Bitmap(fmap.map<RGBColor>([&mi](float i) { return i > mi.maxIter ?
+                            RGBColor{ 0,0,0 } :
+                            RGBColor{ uint8_t(cos(i * 0.015f) * 127 + 127),
+                                      uint8_t(sin(i * 0.01f) * 127 + 127),
+                                      uint8_t(i) }; }));//uint8_t(::sin(i * 0.01f) * 100 + 100), uint8_t(i) }; });
+
+            //hiddenWidget->makeCurrent();
+            //Texture* tex = new Texture(bitmap);
+            //hiddenWidget->doneCurrent();
+            //Texture* tex = 0;
+            emit updated(bitmap);
+        }
+        printf("finished calcing!\n"); fflush(stdout);
+        condVar.wait(lock);
+        printf("waking!\n"); fflush(stdout);
+    }
+}
+
 void MandelView::adaptViewport(const MandelInfo mi)
 {
     //bmp->get(0, 0) = RGBColor{ 10, uint8_t(sin(1 / vp.width) * 127 + 127), 10 };
@@ -91,14 +176,19 @@ void MandelView::adaptViewport(const MandelInfo mi)
             printf("ready!\n");
         }
     }*/
-    if (!calc.valid() || calc.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) {
+    /*if (!calc.valid() || calc.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) {
         toCalc = mi;
         hasToCalc = true;
         calc = std::async([this, mi] () {
-            QOpenGLContext* context = new QOpenGLContext();
-            context->setShareContext(mWidget->context()->contextHandle());
-            context->create();
-             while(hasToCalc.exchange(false)) {
+            QGLWidget* hiddenWidget = new QGLWidget(nullptr, (QGLWidget*) mWidget);
+            QOpenGLContext* context = hiddenWidget->context()->contextHandle();
+            hiddenWidget->makeCurrent();
+            //context->setShareContext(mWidget->context()->contextHandle());
+            //context->create();
+            printf("sharing: %d\n", QOpenGLContext::areSharing(context, mWidget->context()->contextHandle()));
+            fflush(stdout);
+            //std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+            do {
                 auto fmap = Bitmap<float>(mi.bWidth, mi.bHeight);
                 generator->generate(mi, fmap.pixels.get());
                 auto bitmap = fmap.map<RGBColor>([&mi](float i) { return i > mi.maxIter ?
@@ -107,15 +197,19 @@ void MandelView::adaptViewport(const MandelInfo mi)
                                           uint8_t(sin(i * 0.01f) * 127 + 127),
                                           uint8_t(i) }; });//uint8_t(::sin(i * 0.01f) * 100 + 100), uint8_t(i) }; });
 
-                //Texture* tex = new Texture(bitmap, context);
-                //emit updated(tex);
-            }
+                Texture* tex = new Texture(bitmap, context);
+                //Texture* tex = 0;
+                emit updated(tex);
+            } while(hasToCalc.exchange(false));
         });
     }
-    else {
-        toCalc = mi;
-        hasToCalc = true;
-    }
+    else {*/
+
+    //std::unique_lock<std::mutex> lock(mut, std::try_to_lock);
+    toCalc = mi;
+    hasToCalc.exchange(true);
+    condVar.notify_one();
+    //}
 }
 
 
@@ -128,7 +222,7 @@ MandelWidget::MandelWidget(mnd::MandelContext& ctxt, QWidget* parent) :
     this->setSizePolicy(QSizePolicy::Expanding,
         QSizePolicy::Expanding);
     QObject::connect(&mv, &MandelView::updated, this, &MandelWidget::viewUpdated, Qt::AutoConnection);
-    QObject::connect(this, &MandelWidget::needsUpdate, &mv, &MandelView::adaptViewport, Qt::AutoConnection);
+    QObject::connect(this, &MandelWidget::needsUpdate, &mv, &MandelView::adaptViewport, Qt::DirectConnection);
 
     if (!ctxt.getDevices().empty()) {
         if (auto* gen = ctxt.getDevices()[0].getGeneratorDouble(); gen) {
@@ -161,7 +255,8 @@ void MandelWidget::initializeGL(void)
     bitmap.get(0, 0) = RGBColor{50, 50, 50};
 
     tex = std::make_unique<Texture>(bitmap, context()->contextHandle());
-    emit needsUpdate(MandelInfo{ viewport, this->width(), this->height(), 2000 });
+    mv.start();
+    requestRecalc();
 }
 
 
@@ -223,8 +318,29 @@ void MandelWidget::drawRubberband(void)
 }
 
 
+void MandelWidget::zoom(float scale)
+{
+    viewport.zoomCenter(scale);
+    requestRecalc();
+}
+
+
+void MandelWidget::setMaxIterations(int maxIter)
+{
+    this->maxIterations = maxIter;
+    requestRecalc();
+}
+
+
+void MandelWidget::requestRecalc()
+{
+    emit needsUpdate(MandelInfo{ viewport, this->width(), this->height(), maxIterations });
+}
+
+
 void MandelWidget::resizeGL(int width, int height)
 {
+    glViewport(0, 0, (GLint) width, (GLint) height);
 }
 
 
@@ -252,7 +368,7 @@ void MandelWidget::resizeEvent(QResizeEvent* re)
     //else
     //    viewport.width = (viewport.height * aspect);
 
-    emit needsUpdate(MandelInfo{ viewport, this->width(), this->height(), 2000 });
+    requestRecalc();
     //redraw();
 }
 
@@ -289,14 +405,16 @@ void MandelWidget::mouseReleaseEvent(QMouseEvent* me)
     viewport.height *= double(rect.height()) / full.height();
     viewport.normalize();
     rubberbandDragging = false;
-    emit needsUpdate(MandelInfo{ viewport, this->width(), this->height(), 2000 });
+    requestRecalc();
 }
 
 
-void MandelWidget::viewUpdated(Texture* bitmap)
+void MandelWidget::viewUpdated(Bitmap<RGBColor>* bitmap)
 {
-    tex = std::unique_ptr<Texture>(bitmap);//std::make_unique<Texture>(*bitmap);
-    //delete bitmap;
-    printf("viewUpdated\n");
-    emit repaint();
+    if (bitmap != nullptr) {
+        tex = std::make_unique<Texture>(*bitmap);
+        delete bitmap;
+        printf("viewUpdated\n");
+        emit repaint();
+    }
 }

+ 21 - 3
MandelWidget.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <QGLWidget>
+#include <QThread>
 #include <qopengl.h>
 #include <qopenglfunctions.h>
 #include <qopenglcontext.h>
@@ -13,6 +14,8 @@
 #include <Mandel.h>
 
 #include <future>
+#include <thread>
+#include <mutex>
 #include <atomic>
 
 class MandelWidget;
@@ -42,20 +45,29 @@ class MandelView : public QObject
     Q_OBJECT
 private:
     std::future<void> calc;
+    QThread calcThread;
+    std::mutex mut;
+    std::condition_variable condVar;
     std::atomic<mnd::MandelInfo> toCalc;
     std::atomic_bool hasToCalc;
+    std::atomic_bool finish;
     mnd::Generator* generator;
     MandelWidget* mWidget;
-    QOpenGLContext* context;
+    //QOpenGLContext* context;
 public:
     MandelView(mnd::Generator& generator, MandelWidget* mWidget);
+    ~MandelView(void);
 
     void setGenerator(mnd::Generator &value);
 
+    void start();
+private slots:
+    void loop();
+
 public slots:
     void adaptViewport(const mnd::MandelInfo vp);
 signals:
-    void updated(Texture* bitmap);
+    void updated(Bitmap<RGBColor>* bitmap);
 };
 
 class MandelWidget : public QGLWidget
@@ -67,6 +79,7 @@ private:
     mnd::MandelContext& mndContext;
 
     bool initialized = false;
+    int maxIterations = 2000;
 
     bool rubberbandDragging = false;
     QRectF rubberband;
@@ -93,8 +106,13 @@ public:
 
     void drawRubberband(void);
 
+    void zoom(float scale);
+    void setMaxIterations(int maxIter);
+
     //void redraw();
 
+    void requestRecalc(void);
+
     void resizeEvent(QResizeEvent* re) override;
     void mousePressEvent(QMouseEvent* me) override;
     void mouseMoveEvent(QMouseEvent* me) override;
@@ -104,6 +122,6 @@ public:
 signals:
     void needsUpdate(const mnd::MandelInfo vp);
 public slots:
-    void viewUpdated(Texture* bitmap);
+    void viewUpdated(Bitmap<RGBColor>* bitmap);
 };
 

+ 4 - 3
VideoStream.cpp

@@ -1,6 +1,7 @@
+#include "VideoStream.h"
+
 #ifdef FFMPEG_ENABLED
 
-#include "VideoStream.h"
 
 #include <iostream>
 
@@ -32,7 +33,7 @@ VideoStream::VideoStream(::size_t width, ::size_t height, const std::string& fil
     if (!pkt)
         exit(1);
 
-    codecContext->bit_rate = 500000 * 100;
+    codecContext->bit_rate = 100 * 1000 * 1000;
     codecContext->width = width;
     codecContext->height = height;
     codecContext->time_base = AVRational{ 1, 60 };
@@ -91,7 +92,7 @@ static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
             exit(1);
         }
 
-        printf("encoded frame %3\"PRId64\" (size=%5d)\n", pkt->pts, pkt->size);
+        printf("encoded frame %3d\"PRId64\" (size=%5d)\n", pkt->pts, pkt->size);
         fwrite(pkt->data, 1, pkt->size, outfile);
         av_packet_unref(pkt);
     }

+ 1 - 0
VideoStream.h

@@ -1,4 +1,5 @@
 #pragma once
+#define FFMPEG_ENABLED
 #ifdef FFMPEG_ENABLED
 
 

+ 3 - 0
benchmarkdialog.cpp

@@ -168,6 +168,7 @@ BenchmarkDialog::BenchmarkDialog(mnd::MandelContext& mndContext, QWidget *parent
     benchmarker{ mndContext }
 {
     ui.setupUi(this);
+    printf("bench!\n"); fflush(stdout);
 
     auto& devices = mndContext.getDevices();
     int nDevices = devices.size() + 1;
@@ -196,6 +197,8 @@ BenchmarkDialog::BenchmarkDialog(mnd::MandelContext& mndContext, QWidget *parent
     connect(&benchThread, &QThread::started, &benchmarker, &Benchmarker::start);
     connect(&benchmarker, SIGNAL (finished()), &benchThread, SLOT (quit()));
     connect(&benchmarker, SIGNAL (update(BenchmarkResult)), this, SLOT (update(BenchmarkResult)));
+
+    ui.tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
 }
 
 

+ 169 - 0
exportdialogs.cpp

@@ -0,0 +1,169 @@
+#include "exportdialogs.h"
+
+#include <QIntValidator>
+#include <QFileDialog>
+#include <QMessageBox>
+
+#include "Mandel.h"
+#include "VideoStream.h"
+
+static bool exportVideo(const ExportVideoInfo& evi);
+
+ExportImageDialog::ExportImageDialog(QWidget* parent) :
+    QDialog{ parent }
+{
+    eid.setupUi(this);
+    eid.maxIterations->setValidator(new QIntValidator(1, 1000000000, this));
+    eid.imgWidth->setValidator(new QIntValidator(1, 10000000, this));
+    eid.imgHeight->setValidator(new QIntValidator(1, 10000000, this));
+}
+
+
+int ExportImageDialog::getMaxIterations(void) const
+{
+    return std::stoi(eid.maxIterations->text().toStdString());
+}
+
+
+int ExportImageDialog::getWidth(void) const
+{
+    return std::stoi(eid.imgWidth->text().toStdString());
+}
+
+
+int ExportImageDialog::getHeight(void) const
+{
+    return std::stoi(eid.imgHeight->text().toStdString());
+}
+
+
+QString ExportImageDialog::getPath(void) const
+{
+    return eid.savePath->text();
+}
+
+void ExportImageDialog::on_pushButton_clicked()
+{
+    QString saveAs = QFileDialog::getSaveFileName(this,
+            tr("Save exported image"), "",
+            tr("PNG image (*.png);;JPEG image (*.jpg);;All Files (*)"));
+    eid.savePath->setText(saveAs);
+    this->repaint();
+}
+
+void ExportImageDialog::on_buttonBox_accepted()
+{
+    if (eid.savePath->text() == "") {
+        QMessageBox msgBox;
+        msgBox.setText("Please specify a path.");
+        msgBox.show();
+        msgBox.exec();
+        reject();
+    }
+}
+
+ExportVideoDialog::ExportVideoDialog(QWidget* parent, const ExportVideoInfo& evi) :
+    QDialog{ parent },
+    evi{ evi }
+{
+    evd.setupUi(this);
+    evd.maxIterations->setValidator(new QIntValidator(1, 1000000000, this));
+    evd.vidWidth->setValidator(new QIntValidator(1, 10000000, this));
+    evd.vidHeight->setValidator(new QIntValidator(1, 10000000, this));
+
+    evd.startX->setText(QString::number(evi.start.x));
+    evd.startY->setText(QString::number(evi.start.y));
+    evd.startW->setText(QString::number(evi.start.width));
+    evd.startH->setText(QString::number(evi.start.height));
+
+    evd.endX->setText(QString::number(evi.end.x));
+    evd.endY->setText(QString::number(evi.end.y));
+    evd.endW->setText(QString::number(evi.end.width));
+    evd.endH->setText(QString::number(evi.end.height));
+}
+
+const ExportVideoInfo& ExportVideoDialog::getExportVideoInfo(void) const
+{
+    return evi;
+}
+
+void ExportVideoDialog::on_buttonBox_accepted()
+{
+    if (evd.savePath->text() == "") {
+        QMessageBox* msgBox = new QMessageBox;
+        msgBox->setText("Please specify a path.");
+        msgBox->exec();
+        emit reject();
+    }
+
+    evi.path = evd.savePath->text();
+    evi.width = evd.vidWidth->text().toInt();
+    evi.height = evd.vidHeight->text().toInt();
+    evi.maxIterations = evd.maxIterations->text().toInt();
+    /*evi.start = mnd::MandelViewport {
+        evd.startX->text().toDouble(),
+        evd.startY->text().toDouble(),
+        evd.startW->text().toDouble(),
+        evd.startH->text().toDouble(),
+    };
+    evi.end = mnd::MandelViewport {
+        evd.endX->text().toDouble(),
+        evd.endY->text().toDouble(),
+        evd.endW->text().toDouble(),
+        evd.endH->text().toDouble(),
+    };*/
+
+    evi.start.adjustAspectRatio(evi.width, evi.height);
+    evi.end.adjustAspectRatio(evi.width, evi.height);
+
+    if (exportVideo(evi)) {
+        QMessageBox* msgBox = new QMessageBox;
+        msgBox->setText("Video successfully exported.");
+        msgBox->exec();
+    }
+}
+
+void ExportVideoDialog::on_pushButton_clicked()
+{
+    QString saveAs = QFileDialog::getSaveFileName(this,
+            tr("Save exported image"), "",
+            tr("MPEG video (*.mp4);;All Files (*)"));
+    evd.savePath->setText(saveAs);
+    this->repaint();
+}
+
+
+bool exportVideo(const ExportVideoInfo& evi)
+{
+    auto lerp = [] (double a, double b, double v) {
+        return a * (1 - v) + b * v;
+    };
+
+    mnd::MandelContext ctxt = mnd::initializeContext();
+    mnd::Generator& gen = *ctxt.getDevices()[0].getGeneratorDouble();
+    mnd::MandelInfo mi;
+    mi.bWidth = evi.width;
+    mi.bHeight = evi.height;
+    mi.maxIter = evi.maxIterations;
+
+    VideoStream vs(evi.width, evi.height, evi.path.toStdString());
+
+    double x = evi.end.x + evi.end.width / 2;
+    double y = evi.end.y + evi.end.height / 2;
+    double w = evi.start.width;
+    double h = evi.start.height;
+
+    while(w > evi.end.width || h > evi.end.height) {
+        mi.view = mnd::MandelViewport{ x - w/2, y - h/2, w, h };
+
+        Bitmap<float> raw{ evi.width, evi.height };
+        gen.generate(mi, raw.pixels.get());
+        vs.addFrame(raw.map<RGBColor>([] (float x) { return
+            RGBColor{ uint8_t(::sin(x / 100) * 127 + 127), uint8_t(::sin(x / 213) * 127 + 127), uint8_t(::cos(x / 173) * 127 + 127) };
+        }));
+
+        w *= 0.975;
+        h *= 0.975;
+    }
+    return true;
+}

+ 57 - 0
exportdialogs.h

@@ -0,0 +1,57 @@
+#ifndef EXPORTDIALOGS_H
+#define EXPORTDIALOGS_H
+
+#include "Mandel.h"
+
+#include <QtWidgets/QDialog>
+
+#include "ui_exportimagedialog.h"
+#include "ui_exportvideodialog.h"
+
+class ExportImageDialog : public QDialog
+{
+    Q_OBJECT
+private:
+    Ui::ExportImageDialog eid;
+public:
+    ExportImageDialog(QWidget* parent);
+
+    int getMaxIterations(void) const;
+    int getWidth(void) const;
+    int getHeight(void) const;
+    QString getPath(void) const;
+private slots:
+    void on_pushButton_clicked();
+    void on_buttonBox_accepted();
+};
+
+
+struct ExportVideoInfo {
+    mnd::MandelViewport start;
+    mnd::MandelViewport end;
+
+    int width;
+    int height;
+    int maxIterations;
+
+    QString path;
+};
+
+
+class ExportVideoDialog : public QDialog
+{
+    Q_OBJECT
+private:
+    Ui::ExportVideoDialog evd;
+
+    ExportVideoInfo evi;
+public:
+    ExportVideoDialog(QWidget* parent, const ExportVideoInfo& evi);
+
+    const ExportVideoInfo& getExportVideoInfo(void) const;
+private slots:
+    void on_buttonBox_accepted();
+    void on_pushButton_clicked();
+};
+
+#endif // EXPORTDIALOGS_H

+ 3 - 3
exportimagedialog.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>400</width>
-    <height>184</height>
+    <height>170</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -17,7 +17,7 @@
    <property name="geometry">
     <rect>
      <x>40</x>
-     <y>140</y>
+     <y>130</y>
      <width>341</width>
      <height>32</height>
     </rect>
@@ -35,7 +35,7 @@
      <x>10</x>
      <y>10</y>
      <width>371</width>
-     <height>121</height>
+     <height>111</height>
     </rect>
    </property>
    <layout class="QFormLayout" name="formLayout">

+ 178 - 0
exportvideodialog.ui

@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportVideoDialog</class>
+ <widget class="QDialog" name="ExportVideoDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>393</width>
+    <height>219</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <widget class="QDialogButtonBox" name="buttonBox">
+   <property name="geometry">
+    <rect>
+     <x>40</x>
+     <y>180</y>
+     <width>341</width>
+     <height>32</height>
+    </rect>
+   </property>
+   <property name="orientation">
+    <enum>Qt::Horizontal</enum>
+   </property>
+   <property name="standardButtons">
+    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+   </property>
+  </widget>
+  <widget class="QWidget" name="formLayoutWidget">
+   <property name="geometry">
+    <rect>
+     <x>10</x>
+     <y>10</y>
+     <width>371</width>
+     <height>161</height>
+    </rect>
+   </property>
+   <layout class="QFormLayout" name="formLayout">
+    <item row="0" column="0">
+     <widget class="QLabel" name="label">
+      <property name="text">
+       <string>Maximum iterations</string>
+      </property>
+     </widget>
+    </item>
+    <item row="0" column="1">
+     <widget class="QLineEdit" name="maxIterations">
+      <property name="text">
+       <string>5000</string>
+      </property>
+     </widget>
+    </item>
+    <item row="1" column="0">
+     <widget class="QLabel" name="label_2">
+      <property name="text">
+       <string>Video Width</string>
+      </property>
+     </widget>
+    </item>
+    <item row="2" column="0">
+     <widget class="QLabel" name="label_3">
+      <property name="text">
+       <string>Video Height</string>
+      </property>
+     </widget>
+    </item>
+    <item row="1" column="1">
+     <widget class="QLineEdit" name="vidWidth">
+      <property name="text">
+       <string>1920</string>
+      </property>
+     </widget>
+    </item>
+    <item row="2" column="1">
+     <widget class="QLineEdit" name="vidHeight">
+      <property name="text">
+       <string>1080</string>
+      </property>
+     </widget>
+    </item>
+    <item row="5" column="0">
+     <widget class="QPushButton" name="pushButton">
+      <property name="text">
+       <string>Save As</string>
+      </property>
+     </widget>
+    </item>
+    <item row="5" column="1">
+     <widget class="QLineEdit" name="savePath"/>
+    </item>
+    <item row="3" column="0">
+     <widget class="QLabel" name="label_4">
+      <property name="text">
+       <string>Start View</string>
+      </property>
+     </widget>
+    </item>
+    <item row="3" column="1">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QLineEdit" name="startX"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="startY"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="startW"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="startH"/>
+      </item>
+     </layout>
+    </item>
+    <item row="4" column="0">
+     <widget class="QLabel" name="label_5">
+      <property name="text">
+       <string>End View</string>
+      </property>
+     </widget>
+    </item>
+    <item row="4" column="1">
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <item>
+       <widget class="QLineEdit" name="endX"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="endY"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="endW"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="endH"/>
+      </item>
+     </layout>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ExportVideoDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ExportVideoDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

+ 10 - 0
libmandel/include/MandelUtil.h

@@ -32,6 +32,16 @@ struct mnd::MandelViewport
      * \brief make sure width and height are positive
      */
     void normalize(void);
+
+    /*!
+     * \brief zoom in around the center by a factor specified
+     */
+    void zoomCenter(float scale);
+
+    /*!
+     * \brief returns a viewport where the whole mandelbrot set can be observed
+     */
+    static MandelViewport standardView(void);
 };
 
 struct mnd::MandelInfo

+ 18 - 0
libmandel/src/MandelUtil.cpp

@@ -24,3 +24,21 @@ void MandelViewport::normalize(void)
     }
 }
 
+
+void MandelViewport::zoomCenter(float scale)
+{
+    x += width * (1 - scale) / 2;
+    y += height * (1 - scale) / 2;
+    width *= scale;
+    height *= scale;
+}
+
+MandelViewport MandelViewport::standardView(void)
+{
+    return MandelViewport{
+        -2.25,
+        -1.5,
+        3,
+        3
+    };
+}