Browse Source

Merge branch 'master' of https://git.winfor.ch/nicolas/Almond

Nicolas Winkler 5 years ago
parent
commit
0bc2b6558e
76 changed files with 3146 additions and 1797 deletions
  1. 213 17
      Almond.cpp
  2. 11 0
      Almond.desktop
  3. 29 3
      Almond.h
  4. 0 0
      Almond.png
  5. 13 4
      Almond.pro
  6. 2 2
      Almond.qrc
  7. 81 69
      Almond.ui
  8. 229 0
      AlmondMenuWidget.cpp
  9. 67 0
      AlmondMenuWidget.h
  10. 15 8
      BackgroundTask.cpp
  11. 3 2
      BackgroundTask.h
  12. 13 4
      CMakeLists.txt
  13. 0 66
      CubicSpline.cpp
  14. 0 19
      CubicSpline.h
  15. 71 0
      ExportImageMenu.cpp
  16. 30 0
      ExportImageMenu.h
  17. 99 0
      ExportImageMenu.ui
  18. 123 0
      ExportVideoMenu.cpp
  19. 31 0
      ExportVideoMenu.h
  20. 252 0
      ExportVideoMenu.ui
  21. 0 164
      Gradient.cpp
  22. 0 38
      Gradient.h
  23. 32 0
      GradientMenu.cpp
  24. 27 0
      GradientMenu.h
  25. 58 0
      GradientMenu.ui
  26. 372 1
      GradientWidget.cpp
  27. 46 1
      GradientWidget.h
  28. 17 0
      LICENSE
  29. 5 9
      MandelWidget.cpp
  30. 1 1
      MandelWidget.h
  31. 8 0
      README.md
  32. 3 0
      choosegenerators.cpp
  33. 112 0
      debian/copyright
  34. 15 2
      exportdialogs.cpp
  35. 1 1
      gradientchoosedialog.cpp
  36. 0 674
      installer/packages/almond/meta/gpl3.txt
  37. 1 1
      installer/packages/almond/meta/package.xml
  38. 17 0
      installer/packages/almond/meta/zlib.txt
  39. 17 4
      libalmond/CMakeLists.txt
  40. 1 1
      libalmond/include/CubicSpline.h
  41. 3 0
      libalmond/include/Gradient.h
  42. 21 3
      libalmond/include/ImageExport.h
  43. 12 4
      libalmond/src/CubicSpline.cpp
  44. 10 3
      libalmond/src/Gradient.cpp
  45. 313 2
      libalmond/src/ImageExport.cpp
  46. 0 5
      libalmond/src/VideoStream.cpp
  47. 23 20
      libmandel/CMakeLists.txt
  48. 14 12
      libmandel/asmjit/CMakeLists.txt
  49. 2 1
      libmandel/include/Mandel.h
  50. 10 8
      libmandel/include/OpenClCode.h
  51. 31 8
      libmandel/include/Types.h
  52. 0 0
      libmandel/include_cl/CL/cl.hpp
  53. 0 0
      libmandel/include_cl/CL/cl2.hpp
  54. 23 17
      libmandel/src/CpuGeneratorsAVX.cpp
  55. 18 11
      libmandel/src/CpuGeneratorsAVX512.cpp
  56. 44 37
      libmandel/src/CpuGeneratorsAVXFMA.cpp
  57. 17 13
      libmandel/src/CpuGeneratorsSSE2.cpp
  58. 16 7
      libmandel/src/Mandel.cpp
  59. 16 16
      libmandel/src/OpenClCode.cpp
  60. 2 3
      libmandel/src/opencl/double.cl
  61. 9 9
      libmandel/src/opencl/double.h
  62. 1 1
      libmandel/src/opencl/doubledouble.cl
  63. 231 223
      libmandel/src/opencl/doubledouble.h
  64. 2 2
      libmandel/src/opencl/doublefloat.cl
  65. 16 16
      libmandel/src/opencl/doublefloat.h
  66. 2 2
      libmandel/src/opencl/fixed128.cl
  67. 12 12
      libmandel/src/opencl/fixed128.h
  68. 2 2
      libmandel/src/opencl/fixed512.cl
  69. 12 12
      libmandel/src/opencl/fixed512.h
  70. 2 2
      libmandel/src/opencl/fixed64.cl
  71. 10 10
      libmandel/src/opencl/fixed64.h
  72. 18 16
      libmandel/src/opencl/float.cl
  73. 208 202
      libmandel/src/opencl/float.h
  74. 1 1
      libmandel/src/opencl/quaddouble.cl
  75. 15 15
      libmandel/src/opencl/quaddouble.h
  76. 15 11
      mandelvid/src/main.cpp

+ 213 - 17
Almond.cpp

@@ -3,7 +3,9 @@
 #include <QIcon>
 #include <QFileDialog>
 #include <QMessageBox>
+#include <QStatusBar>
 #include <QGradient>
+#include <QWindow>
 #include "gradientchoosedialog.h"
 
 #include "GridFlowLayout.h"
@@ -11,7 +13,7 @@
 #include <cmath>
 
 Almond::Almond(QWidget* parent) :
-    QMainWindow{ parent },
+    QMainWindow{ parent, Qt::WindowFlags() },
     mandelContext{ mnd::initializeContext() }
 {
     ui.setupUi(this);
@@ -25,21 +27,55 @@ Almond::Almond(QWidget* parent) :
     on_maxIterations_editingFinished();
     mw->setSmoothColoring(ui.smooth->isChecked());
 
-
     currentView = MANDELBROT;
     mandelGenerator = &mandelContext.getDefaultGenerator();
     mandelViewSave = mw->getViewport();
 
     QObject::connect(mw.get(), &MandelWidget::pointSelected, this, &Almond::pointSelected);
-    ui.mainContainer->addWidget(mw.get());
+    ui.mandel_container->addWidget(mw.get());
     ui.maxIterations->setValidator(new QIntValidator(1, 1000000000, this));
-    ui.backgroundProgress->setVisible(false);
+
+    ui.backgroundProgress->setEnabled(false);
+    ui.cancelProgress->setEnabled(false);
+
+    amw = new AlmondMenuWidget(this);
+    amw->setMainMenu(ui.dockWidgetContents_2);
+    eim = new ExportImageMenu();
+    evm = new ExportVideoMenu();
+    gradientMenu = new GradientMenu();
+    AlmondSubMenu* imageSm = amw->addSubMenu(eim);
+    AlmondSubMenu* videoSm = amw->addSubMenu(evm);
+    AlmondSubMenu* gradientSm = amw->addSubMenu(gradientMenu);
+    ui.dockWidget_2->setWidget(amw);
+
+    connect(amw, &AlmondMenuWidget::submenuCancel, [this] (int) { amw->showMainMenu(); });
+    //connect(amw, &AlmondMenuWidget::submenuOK, this, &Almond::submenuOK);
+    connect(imageSm, &AlmondSubMenu::accepted, this, &Almond::imageExportOk);
+    connect(videoSm, &AlmondSubMenu::accepted, this, &Almond::videoExportOk);
+    connect(gradientSm, &AlmondSubMenu::accepted, this, &Almond::gradientEditOk);
+
+
+    /*QStatusBar* bar = new QStatusBar(this);
+    bar->addWidget(new QLabel("ayay"));
+    auto* p = new QPushButton("About");
+    bar->addPermanentWidget(p);
+    QObject::connect(p, &QPushButton::clicked, [this]() {
+        toggleFullscreen();
+    });
+    bar->setFixedHeight(bar->sizeHint().height());
+    //ui.mainContainer->addWidget(bar);
+    this->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
+    this->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);*/
+
+    installEventFilter(this);
 
     backgroundTasks.setMaxThreadCount(1);
     QIcon icon{ ":/icons/icon" };
     icon.addFile(":/icons/icon@2x");
     this->setWindowIcon(icon);
 
+
+
     // replace vertical layout with gridflowlayout
     /*GridFlowLayout* gfl = new GridFlowLayout(nullptr);
     //ui.horizontalLayout_4->addItem(gfl);
@@ -65,12 +101,147 @@ void Almond::submitBackgroundTask(BackgroundTask* task)
     backgroundTasks.start(task);
     //if (taken) {
         ui.backgroundProgress->setRange(0, 0);
-        ui.backgroundProgress->setVisible(true);
         ui.backgroundProgress->setFormat("");
+        ui.backgroundProgress->setEnabled(true);
+        ui.cancelProgress->setEnabled(true);
     //}
 }
 
 
+void Almond::stopBackgroundTask(void)
+{
+    stoppingBackgroundTasks = true;
+}
+
+bool Almond::eventFilter(QObject *target, QEvent *event)
+{
+    if (event->type() == QEvent::KeyPress) {
+        QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+        if (keyEvent->key() == Qt::Key_F11) {
+            emit toggleFullscreen();
+        }
+    }
+    return QObject::eventFilter(target, event);
+}
+
+
+void Almond::submenuOK(int smIndex)
+{
+    switch(smIndex) {
+    case 0:
+        emit imageExportOk();
+        break;
+    case 1:
+        emit videoExportOk();
+        break;
+    }
+}
+
+void Almond::imageExportOk(void)
+{
+    mnd::MandelInfo mi;
+    mi.maxIter = eim->getMaxIterations();
+    mi.view = mw->getViewport();
+    mi.bWidth = eim->getWidth();
+    mi.bHeight = eim->getHeight();
+    mi.view.adjustAspectRatio(mi.bWidth, mi.bHeight);
+    mi.smooth = mw->getSmoothColoring();
+    if (currentView == JULIA) {
+        mi.julia = mw->getMandelInfo().julia;
+        mi.juliaX = mw->getJuliaX();
+        mi.juliaY = mw->getJuliaY();
+    }
+
+    mnd::MandelGenerator* currentGenerator = mw->getGenerator();
+    mnd::MandelGenerator& g = currentGenerator ? *currentGenerator : mandelContext.getDefaultGenerator();
+
+    alm::ImageExportInfo iei;
+    iei.drawInfo = mi;
+    iei.generator = &g;
+    iei.gradient = mw->getGradient();
+    iei.path = eim->getPath().toStdString();
+    iei.options.jpegQuality = 95;
+    submitBackgroundTask(new ImageExportTask(iei, [this] () { return stoppingBackgroundTasks; }));
+    amw->showMainMenu();
+}
+
+
+void Almond::videoExportOk(void)
+{
+    ExportVideoInfo evi = evm->getInfo();
+    evi.gradient = mw->getGradient();
+    evi.mi.smooth = mw->getSmoothColoring();
+    if (currentView == JULIA) {
+        evi.mi.julia = mw->getMandelInfo().julia;
+        evi.mi.juliaX = mw->getMandelInfo().juliaX;
+        evi.mi.juliaY = mw->getMandelInfo().juliaY;
+    }
+    if (evi.path == "") {
+        QMessageBox errMsg = QMessageBox(QMessageBox::Icon::Critical, "Error", "No path specified.");
+        errMsg.exec();
+    }
+    else {
+        MandelVideoGenerator mvg(evi);
+        mnd::MandelGenerator& g = *mw->getGenerator();
+        printf("wii: %ld\n", evi.mi.bWidth);
+        fflush(stdout);
+        submitBackgroundTask(new VideoExportTask(std::move(mvg), g));
+        amw->showMainMenu();
+    }
+}
+
+
+void Almond::gradientEditOk(void)
+{
+    const auto& points = gradientMenu->getGradient();
+
+    // convert from QVector<QPair<float, QColor>> to
+    //           -> std::vector<std::pair<RGBColor, float>>
+    std::vector<std::pair<RGBColor, float>> np;
+    std::transform(points.begin(), points.end(), std::back_inserter(np),
+        [](auto& qp) -> std::pair<RGBColor, float> {
+        auto& [pos, col] = qp;
+        return { RGBColor{ uint8_t(col.red()), uint8_t(col.green()), uint8_t(col.blue()) },
+            pos };
+    });
+    std::sort(np.begin(), np.end(), [](auto& a, auto& b) { return a.second < b.second; });
+    if (!np.empty()) {
+        auto& first = np.at(0);
+        if (first.second > 0) {
+            np.insert(np.begin(), { first.first, 0.0f });
+        }
+        auto& last = np.at(np.size() - 1);
+        if (last.second < 1) {
+            np.insert(np.begin(), { last.first, 1.0f });
+        }
+    }
+
+    std::for_each(np.begin(), np.end(), [](auto& x) { x.second *= 300; });
+
+    Gradient g{ np, true };
+    mw->setGradient(std::move(g));
+    amw->showMainMenu();
+}
+
+
+void Almond::toggleFullscreen(void)
+{
+    if (fullscreenMode) {
+        auto* m = this->takeCentralWidget();
+        ui.mandel_container->addWidget(m);
+        this->setCentralWidget(cw);
+        emit this->showNormal();
+        fullscreenMode = false;
+    }
+    else {
+        cw = this->takeCentralWidget();
+        this->setCentralWidget(mw.get());
+        emit this->showFullScreen();
+        fullscreenMode = true;
+    }
+}
+
+
 void Almond::backgroundTaskFinished(bool succ, QString message)
 {
     if (succ) {
@@ -84,8 +255,12 @@ void Almond::backgroundTaskFinished(bool succ, QString message)
         emit info.exec();
     }
 
-    ui.backgroundProgress->setVisible(false);
-    ui.backgroundProgress->setFormat("");
+    ui.backgroundProgress->setFormat(tr("Export Progress"));
+    ui.backgroundProgress->setEnabled(false);
+    ui.backgroundProgress->setRange(0, 100);
+    ui.backgroundProgress->setValue(0);
+    ui.cancelProgress->setEnabled(false);
+    stoppingBackgroundTasks = false;
 }
 
 
@@ -129,15 +304,30 @@ void Almond::on_maxIterations_editingFinished()
 
 void Almond::on_chooseGradient_clicked()
 {
-    gcd.exec();
-    auto gradient = gcd.getGradient();
-    if (gradient)
-        mw->setGradient(std::move(*gradient));
+    const auto& gradient = mw->getGradient();
+    auto points = gradient.getPoints();
+    std::for_each(points.begin(), points.end(), [](auto& x) { x.second /= 300; });
+
+    QVector<QPair<float, QColor>> np;
+    std::transform(points.begin(), points.end(), std::back_inserter(np),
+        [](auto& qp) -> QPair<float, QColor> {
+        auto& [col, pos] = qp;
+        return { pos, QColor{ (col.r), (col.g), (col.b) } };
+    });
+    this->gradientMenu->setGradient(std::move(np));
+    emit this->amw->showSubMenu(2);
+    //gcd.exec();
+    //auto gradient = gcd.getGradient();
+    //if (gradient)
+    //    mw->setGradient(std::move(*gradient));
 }
 
 
 void Almond::on_exportVideo_clicked()
 {
+    evm->setEndViewport(mw->getViewport());
+    emit this->amw->showSubMenu(1);
+    return;
     ExportVideoInfo evi;
     evi.start = mnd::MandelViewport::standardView();
     evi.end = mw->getViewport();
@@ -178,6 +368,9 @@ void Almond::on_smooth_stateChanged(int checked)
 
 void Almond::on_exportImage_clicked()
 {
+    this->amw->showSubMenu(0);
+    return;
+
     ExportImageDialog dialog(this);
     dialog.setMaxIterations(mw->getMaxIterations());
     //dialog.show();
@@ -203,7 +396,8 @@ void Almond::on_exportImage_clicked()
         iei.generator = &g;
         iei.gradient = mw->getGradient();
         iei.path = dialog.getPath().toStdString();
-        submitBackgroundTask(new ImageExportTask(iei));
+        iei.options.jpegQuality = 95;
+        submitBackgroundTask(new ImageExportTask(iei, [this] () { return stoppingBackgroundTasks; }));
 
         /*auto exprt = [iei, path = dialog.getPath().toStdString()]() {
             alm::exportPng(path, iei);
@@ -285,11 +479,6 @@ void Almond::pointSelected(mnd::Real x, mnd::Real y)
 }
 
 
-void Almond::on_groupBox_toggled(bool arg1)
-{
-    printf("arg1: %i\n", int(arg1)); fflush(stdout);
-}
-
 void Almond::on_wMandel_clicked()
 {
 
@@ -378,6 +567,7 @@ void Almond::on_radioButton_2_toggled(bool checked)
     }
 }
 
+
 void Almond::on_createCustom_clicked()
 {
     auto response = customGeneratorDialog->exec();
@@ -391,3 +581,9 @@ void Almond::on_createCustom_clicked()
         setViewType(CUSTOM);
     }
 }
+
+
+void Almond::on_cancelProgress_clicked()
+{
+    stopBackgroundTask();
+}

+ 11 - 0
Almond.desktop

@@ -0,0 +1,11 @@
+[Desktop Entry]
+
+Type=Application
+Encoding=UTF-8
+Name=Almond
+Version=1.0
+Comment=Fractal Generation Software
+Exec=/usr/bin/Almond
+Icon=Almond.png
+Terminal=false
+Categories=Education;Art;

+ 29 - 3
Almond.h

@@ -1,4 +1,6 @@
 #pragma once
+#ifndef ALMOND_H
+#define ALMOND_H
 
 #include <QtWidgets/QMainWindow>
 #include "ui_Almond.h"
@@ -10,7 +12,12 @@
 #include "gradientchoosedialog.h"
 #include "choosegenerators.h"
 #include "customgenerator.h"
-//#include "benchmarkdialog.h"
+
+#include "AlmondMenuWidget.h"
+#include "ExportImageMenu.h"
+#include "ExportVideoMenu.h"
+#include "GradientMenu.h"
+
 
 #include <memory>
 
@@ -35,6 +42,15 @@ class Almond : public QMainWindow
 private:
     mnd::MandelContext mandelContext;
     QThreadPool backgroundTasks;
+    bool stoppingBackgroundTasks = false;
+
+    AlmondMenuWidget* amw;
+    ExportImageMenu* eim;
+    ExportVideoMenu* evm;
+    GradientMenu* gradientMenu;
+
+    bool fullscreenMode = false;
+    QWidget* cw;
 public:
     std::unique_ptr<MandelWidget> mw;
 private:
@@ -58,7 +74,16 @@ public:
     ~Almond(void);
 
     void submitBackgroundTask(BackgroundTask* task);
+    void stopBackgroundTask();
 
+    bool eventFilter(QObject *target, QEvent *event);
+
+    void submenuOK(int smIndex);
+    void imageExportOk(void);
+    void videoExportOk(void);
+    void gradientEditOk(void);
+public slots:
+    void toggleFullscreen(void);
 private slots:
     void on_zoom_out_clicked();
     void on_zoom_in_clicked();
@@ -80,8 +105,6 @@ private slots:
 
     void on_wMandel_toggled(bool checked);
 
-    void on_groupBox_toggled(bool arg1);
-
     void saveView(void);
     void setViewType(ViewType v);
 
@@ -89,8 +112,11 @@ private slots:
     void on_radioButton_toggled(bool checked);
     void on_radioButton_2_toggled(bool checked);
     void on_createCustom_clicked();
+    void on_cancelProgress_clicked();
 
 private:
     Ui::AlmondClass ui;
 };
 
+
+#endif // ALMOND_H

+ 0 - 0
icon.png → Almond.png


+ 13 - 4
Almond.pro

@@ -27,10 +27,13 @@ CONFIG += c++17
 
 SOURCES += \
         Almond.cpp \
+        AlmondMenuWidget.cpp \
         BackgroundTask.cpp \
         Color.cpp \
         CubicSpline.cpp \
-        Gradient.cpp \
+        ExportImageMenu.cpp \
+        ExportVideoMenu.cpp \
+        GradientMenu.cpp \
         GradientWidget.cpp \
         MandelWidget.cpp \
         choosegenerators.cpp \
@@ -42,10 +45,13 @@ SOURCES += \
 
 HEADERS += \
         Almond.h \
+        AlmondMenuWidget.h \
         BackgroundTask.h \
         Color.h \
         CubicSpline.h \
-        Gradient.h \
+        ExportImageMenu.h \
+        ExportVideoMenu.h \
+        GradientMenu.h \
         GradientWidget.h \
         MandelWidget.h \
         choosegenerators.h \
@@ -56,6 +62,9 @@ HEADERS += \
 
 FORMS += \
         Almond.ui \
+        ExportImageMenu.ui \
+        ExportVideoMenu.ui \
+        GradientMenu.ui \
         choosegenerators.ui \
         customgenerator.ui \
         exportimagedialog.ui \
@@ -128,9 +137,9 @@ unix|win32: LIBS += -L$FFMPEGPATH -lswscale
 RESOURCES += Almond.qrc \
     splash.qrc
 
-win32:LIBS += -llibpng16_static -lzlibstatic
+win32:LIBS += -llibpng16_static -lzlibstatic -ljpeg
 unix|win32: LIBS += -L$$PWD/libmandel/ -L$$PWD/libalmond/ -lmandel -lqd -lasmjit -lalmond
-unix: LIBS += -lrt -lpng -lavcodec -lavdevice -lavformat -lavutil -lswscale -lavfilter
+unix: LIBS += -lrt -lpng -ljpeg -lavcodec -lavdevice -lavformat -lavutil -lswscale -lavfilter
 
 INCLUDEPATH += $$PWD/libmandel/include $$PWD/libmandel/qd-2.3.22/include $$PWD/libalmond/include
 DEPENDPATH += $$PWD/libmandel/include $$PWD/libmandel/qd-2.3.22/include $$PWD/libalmond/include

+ 2 - 2
Almond.qrc

@@ -1,7 +1,7 @@
 <RCC>
     <qresource prefix="/icons">
-        <file alias="icon">icon.png</file>
-        <file alias="icon@2x">icon.png</file>
+        <file alias="icon">Almond.png</file>
+        <file alias="icon@2x">Almond.png</file>
     </qresource>
     <qresource prefix="/gradients">
         <file alias="default">gradients/default.xml</file>

+ 81 - 69
Almond.ui

@@ -10,7 +10,7 @@
     <x>0</x>
     <y>0</y>
     <width>1202</width>
-    <height>1188</height>
+    <height>1192</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -19,16 +19,23 @@
   <widget class="QWidget" name="centralWidget">
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
-     <layout class="QVBoxLayout" name="mainContainer"/>
+     <layout class="QVBoxLayout" name="mainContainer">
+      <item>
+       <layout class="QHBoxLayout" name="mandel_container"/>
+      </item>
+     </layout>
     </item>
    </layout>
   </widget>
   <widget class="QDockWidget" name="dockWidget_2">
-   <property name="floating">
-    <bool>false</bool>
+   <property name="sizePolicy">
+    <sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
+     <horstretch>0</horstretch>
+     <verstretch>0</verstretch>
+    </sizepolicy>
    </property>
    <property name="features">
-    <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
+    <set>QDockWidget::AllDockWidgetFeatures</set>
    </property>
    <property name="allowedAreas">
     <set>Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea</set>
@@ -45,6 +52,12 @@
       <layout class="QVBoxLayout" name="verticalLayout_right">
        <item>
         <widget class="QGroupBox" name="grp_zoom">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="title">
           <string>Zoom</string>
          </property>
@@ -88,12 +101,25 @@
        </item>
        <item>
         <widget class="QGroupBox" name="grp_display_opts">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="title">
           <string>Display/Calculation Options</string>
          </property>
          <layout class="QVBoxLayout" name="verticalLayout_3">
           <item>
            <layout class="QFormLayout" name="formLayout">
+            <item row="0" column="0" colspan="2">
+             <widget class="QPushButton" name="chooseGradient">
+              <property name="text">
+               <string>Choose Gradient</string>
+              </property>
+             </widget>
+            </item>
             <item row="1" column="0">
              <widget class="QLabel" name="label">
               <property name="text">
@@ -101,14 +127,18 @@
               </property>
              </widget>
             </item>
+            <item row="1" column="1">
+             <widget class="QLineEdit" name="maxIterations">
+              <property name="text">
+               <string>500</string>
+              </property>
+              <property name="maxLength">
+               <number>32</number>
+              </property>
+             </widget>
+            </item>
             <item row="2" column="0" colspan="2">
              <widget class="QCheckBox" name="smooth">
-              <property name="sizePolicy">
-               <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
-                <horstretch>0</horstretch>
-                <verstretch>0</verstretch>
-               </sizepolicy>
-              </property>
               <property name="text">
                <string>smooth coloring</string>
               </property>
@@ -119,12 +149,6 @@
             </item>
             <item row="3" column="0" colspan="2">
              <widget class="QCheckBox" name="displayInfo">
-              <property name="sizePolicy">
-               <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
-                <horstretch>0</horstretch>
-                <verstretch>0</verstretch>
-               </sizepolicy>
-              </property>
               <property name="text">
                <string>display scale</string>
               </property>
@@ -137,35 +161,6 @@
               </property>
              </widget>
             </item>
-            <item row="1" column="1">
-             <widget class="QLineEdit" name="maxIterations">
-              <property name="minimumSize">
-               <size>
-                <width>70</width>
-                <height>0</height>
-               </size>
-              </property>
-              <property name="text">
-               <string>500</string>
-              </property>
-              <property name="maxLength">
-               <number>32</number>
-              </property>
-             </widget>
-            </item>
-            <item row="0" column="0" colspan="2">
-             <widget class="QPushButton" name="chooseGradient">
-              <property name="sizePolicy">
-               <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-                <horstretch>0</horstretch>
-                <verstretch>0</verstretch>
-               </sizepolicy>
-              </property>
-              <property name="text">
-               <string>Choose Gradient</string>
-              </property>
-             </widget>
-            </item>
            </layout>
           </item>
          </layout>
@@ -186,6 +181,12 @@
        </item>
        <item>
         <widget class="QGroupBox" name="grp_fractal">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="title">
           <string>Fractal</string>
          </property>
@@ -243,35 +244,21 @@
        </item>
        <item>
         <widget class="QGroupBox" name="grp_export_box">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
          <property name="title">
           <string>Export</string>
          </property>
-         <layout class="QVBoxLayout" name="verticalLayout_2">
-          <item>
-           <widget class="QPushButton" name="exportVideo">
-            <property name="text">
-             <string>Export Video</string>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <widget class="QPushButton" name="exportImage">
-            <property name="text">
-             <string>Export Image</string>
-            </property>
-           </widget>
-          </item>
-          <item>
+         <layout class="QGridLayout" name="gridLayout">
+          <item row="2" column="0">
            <widget class="QProgressBar" name="backgroundProgress">
             <property name="enabled">
              <bool>true</bool>
             </property>
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
             <property name="maximum">
              <number>1</number>
             </property>
@@ -282,7 +269,32 @@
              <set>Qt::AlignCenter</set>
             </property>
             <property name="format">
-             <string>Background Tasks</string>
+             <string>Export Progress</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QPushButton" name="cancelProgress">
+            <property name="text">
+             <string/>
+            </property>
+            <property name="icon">
+             <iconset theme="cancel">
+              <normaloff>.</normaloff>.</iconset>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="0" colspan="2">
+           <widget class="QPushButton" name="exportVideo">
+            <property name="text">
+             <string>Export Video</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0" colspan="2">
+           <widget class="QPushButton" name="exportImage">
+            <property name="text">
+             <string>Export Image</string>
             </property>
            </widget>
           </item>

+ 229 - 0
AlmondMenuWidget.cpp

@@ -0,0 +1,229 @@
+#include "AlmondMenuWidget.h"
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QResizeEvent>
+#include <QPropertyAnimation>
+#include <QParallelAnimationGroup>
+
+AlmondSubMenu::AlmondSubMenu(QWidget* widget) :
+    w{ widget }
+{
+}
+
+
+QWidget* AlmondSubMenu::widget(void)
+{
+    return w;
+}
+
+
+AlmondMenuWidget::AlmondMenuWidget(QWidget* parent) :
+    QFrame{ parent },
+    mainMenu{ nullptr }
+{
+    rightWidget = new QWidget(this);
+    subMenuContainer = new QStackedWidget(rightWidget);
+    subMenuContainer->setContentsMargins(0, 0, 0, 0);
+    rightOK = new QPushButton("OK", rightWidget);
+    rightCancel = new QPushButton("Cancel", rightWidget);
+
+    leftWidget = new QWidget(this);
+    QVBoxLayout* mainLayout = new QVBoxLayout(leftWidget);
+    mainLayout->setMargin(0);
+    mainLayout->setContentsMargins(0, 0, 0, 0);
+
+    QVBoxLayout* smlayout = new QVBoxLayout(rightWidget);
+    smlayout->addWidget(subMenuContainer, 1);
+    smlayout->addWidget(rightOK);
+    smlayout->addWidget(rightCancel);
+
+    connect(rightOK, &QPushButton::clicked,
+            this, &AlmondMenuWidget::clickedRightOK);
+    connect(rightCancel, &QPushButton::clicked,
+            this, &AlmondMenuWidget::clickedRightCancel);
+
+    //layout->addWidget(rightWidget);
+    //layout->addWidget(leftWidget);
+    rightWidget->setVisible(false);
+
+    /*states = new QStateMachine(this);
+    QState* mainMenuShow = new QState(states);
+    QState* subMenuShow = new QState(states);
+
+    mainMenuShow->assignProperty(leftWidget, "visible", true);
+    mainMenuShow->assignProperty(rightWidget, "visible", false);
+
+    subMenuShow->assignProperty(leftWidget, "visible", false);
+    subMenuShow->assignProperty(leftWidget, "pos", QPoint(100, 100));
+    subMenuShow->assignProperty(rightWidget, "visible", true);
+
+    mainMenuShow->addTransition(this, SIGNAL(showLeft()), subMenuShow);
+    subMenuShow->addTransition(this, SIGNAL(showRight()), mainMenuShow);
+    states->start();*/
+}
+
+
+AlmondMenuWidget::~AlmondMenuWidget(void)
+{
+    for (auto& a : subMenus) {
+        delete a;
+    }
+}
+
+
+void AlmondMenuWidget::setMainMenu(QWidget* mainMenu)
+{
+    //mainMenu->setParent(this);
+    this->mainMenu = mainMenu;
+    leftWidget->layout()->addWidget(mainMenu);
+}
+
+
+AlmondSubMenu* AlmondMenuWidget::addSubMenu(QWidget* subMenu)
+{
+    subMenuContainer->addWidget(subMenu);
+    AlmondSubMenu* almsm = new AlmondSubMenu(subMenu);
+    this->subMenus.append(almsm);
+    return almsm;
+}
+
+
+QSize AlmondMenuWidget::sizeHint(void) const
+{
+    QSize hint{ 0, 0 };
+    hint = leftWidget->sizeHint();
+    for (auto& subMenu : subMenus) {
+        const auto& widget = subMenu->widget();
+        QSize widgetHint = widget->sizeHint();
+        if (hint.width() < widgetHint.width())
+            hint.setWidth(widgetHint.width());
+        if (hint.height() < widgetHint.height())
+            hint.setHeight(widgetHint.height());
+    }
+    return hint;
+}
+
+
+QSize AlmondMenuWidget::minimumSizeHint(void) const
+{
+    QSize hint{ 0, 0 };
+    hint = leftWidget->minimumSizeHint();
+    for (auto& subMenu : subMenus) {
+        const auto& widget = subMenu->widget();
+        QSize widgetHint = widget->minimumSizeHint();
+        if (hint.width() < widgetHint.width())
+            hint.setWidth(widgetHint.width());
+        if (hint.height() < widgetHint.height())
+            hint.setHeight(widgetHint.height());
+    }
+    return hint;
+}
+
+
+void AlmondMenuWidget::resizeEvent(QResizeEvent* event)
+{
+    QRect leftGeom = contentsRect();
+    QRect rightGeom = contentsRect();
+    //leftGeom.setSize(event->size());
+    //rightGeom.setSize(event->size());
+    leftWidget->setGeometry(leftGeom);
+    rightWidget->setGeometry(rightGeom);
+}
+
+
+void AlmondMenuWidget::clickedRightOK(void)
+{
+    int index = subMenuContainer->currentIndex();
+    emit submenuOK(index);
+    emit subMenus.at(index)->accepted();
+}
+
+
+void AlmondMenuWidget::clickedRightCancel(void)
+{
+    int index = subMenuContainer->currentIndex();
+    emit submenuCancel(subMenuContainer->currentIndex());
+    emit subMenus.at(index)->cancelled();
+}
+
+
+void AlmondMenuWidget::showMainMenu(void)
+{
+    emit showLeft();
+    if (!mainMenu)
+        return;
+    QParallelAnimationGroup* ag = new QParallelAnimationGroup(this);
+    QPropertyAnimation* mm = new QPropertyAnimation(leftWidget, "geometry", this);
+    QPropertyAnimation* rw = new QPropertyAnimation(rightWidget, "geometry", this);
+
+    mm->setStartValue(contentsRect().adjusted(-width(), 0, -width(), 0));
+    mm->setEndValue(contentsRect());
+
+    rw->setEndValue(contentsRect().adjusted(width(), 0, width(), 0));
+    rw->setStartValue(contentsRect());
+
+    mm->setEasingCurve(QEasingCurve::InOutSine);
+    rw->setEasingCurve(QEasingCurve::InOutSine);
+    mm->setDuration(200);
+    rw->setDuration(200);
+
+    ag->addAnimation(mm);
+    ag->addAnimation(rw);
+
+    connect(ag, &QParallelAnimationGroup::finished, [this] () {
+        emit rightWidget->hide();
+        leftWidget->setGeometry(contentsRect());
+    });
+
+    leftWidget->setGeometry(rightWidget->geometry().adjusted(-width(), 0, -width(), 0));
+    emit leftWidget->show();
+    emit ag->start(QAbstractAnimation::DeleteWhenStopped);
+}
+
+
+void AlmondMenuWidget::showSubMenu(int index)
+{
+    subMenuContainer->setCurrentIndex(index);
+    /*while (subMenuContainer->layout()->count() > 0)
+        subMenuContainer->layout()->takeAt(0);
+    subMenuContainer->layout()->update();
+    rightWidget->setGeometry(leftWidget->geometry().adjusted(width(), 0, width(), 0));
+    //subMenuContainer->layout()->addWidget(subMenus.at(index));
+    subMenuContainer->layout()->update();*/
+
+    showSubMenu();
+}
+
+
+void AlmondMenuWidget::showSubMenu(void)
+{
+    emit showRight();
+    if (!mainMenu)
+    return;
+    QParallelAnimationGroup* ag = new QParallelAnimationGroup(this);
+    QPropertyAnimation* mm = new QPropertyAnimation(leftWidget, "geometry", this);
+    QPropertyAnimation* rw = new QPropertyAnimation(rightWidget, "geometry", this);
+
+    mm->setStartValue(contentsRect());
+    mm->setEndValue(contentsRect().adjusted(-width(), 0, -width(), 0));
+
+    rw->setStartValue(contentsRect().adjusted(width(), 0, width(), 0));
+    rw->setEndValue(contentsRect());
+
+    mm->setEasingCurve(QEasingCurve::InOutSine);
+    rw->setEasingCurve(QEasingCurve::InOutSine);
+    mm->setDuration(200);
+    rw->setDuration(200);
+
+    ag->addAnimation(mm);
+    ag->addAnimation(rw);
+
+    connect(ag, &QParallelAnimationGroup::finished, [this] () {
+        leftWidget->hide();
+        rightWidget->setGeometry(contentsRect());
+    });
+
+    rightWidget->setGeometry(mainMenu->geometry().adjusted(width(), 0, width(), 0));
+    emit rightWidget->setVisible(true);
+    emit ag->start(QAbstractAnimation::DeleteWhenStopped);
+}

+ 67 - 0
AlmondMenuWidget.h

@@ -0,0 +1,67 @@
+#ifndef ALMONDMENUWIDGET_H
+#define ALMONDMENUWIDGET_H
+
+#include <QFrame>
+#include <QList>
+#include <QStackedWidget>
+#include <QPushButton>
+#include <QStateMachine>
+
+class AlmondSubMenu :
+    public QObject
+{
+    Q_OBJECT
+
+    QWidget* w;
+public:
+    AlmondSubMenu(QWidget* widget);
+    QWidget* widget(void);
+
+signals:
+    void accepted(void);
+    void cancelled(void);
+};
+
+class AlmondMenuWidget :
+    public QFrame
+{
+    Q_OBJECT
+        
+    QWidget* mainMenu;
+    QWidget* rightWidget;
+    QWidget* leftWidget;
+    QPushButton* rightOK;
+    QPushButton* rightCancel;
+    QStackedWidget* subMenuContainer;
+    QList<AlmondSubMenu*> subMenus;
+    QStateMachine* states;
+public:
+    AlmondMenuWidget(QWidget* parent = nullptr);
+    ~AlmondMenuWidget(void);
+
+    void setMainMenu(QWidget* mainMenu);
+    AlmondSubMenu* addSubMenu(QWidget* subMenu);
+
+    virtual QSize sizeHint(void) const override;
+    virtual QSize minimumSizeHint(void) const override;
+protected:
+    virtual void resizeEvent(QResizeEvent* event) override;
+
+protected slots:
+    void clickedRightOK(void);
+    void clickedRightCancel(void);
+
+public slots:
+    void showMainMenu(void);
+    void showSubMenu(int index);
+    void showSubMenu(void);
+
+signals:
+    void submenuOK(int index);
+    void submenuCancel(int index);
+
+    void showLeft();
+    void showRight();
+};
+
+#endif // ALMONDMENUWIDGET_H

+ 15 - 8
BackgroundTask.cpp

@@ -1,13 +1,14 @@
 #include "BackgroundTask.h"
 
-BackgroundTask::BackgroundTask(const std::string& shortDescription) :
-    shortDescription{ shortDescription }
+BackgroundTask::BackgroundTask(const std::string& shortDescription, std::function<bool(void)> stopCallback) :
+    shortDescription{ shortDescription },
+    stopCallback{ std::move(stopCallback) }
 {
 }
 
 
-ImageExportTask::ImageExportTask(const alm::ImageExportInfo& iei) :
-    BackgroundTask{ "Exporting Image" },
+ImageExportTask::ImageExportTask(const alm::ImageExportInfo& iei, std::function<bool(void)> stopCallback) :
+    BackgroundTask{ "Exporting Image", std::move(stopCallback) },
     iei{ iei }
 {
 }
@@ -16,10 +17,13 @@ ImageExportTask::ImageExportTask(const alm::ImageExportInfo& iei) :
 void ImageExportTask::run(void)
 {
     try {
-        alm::exportPng(iei, [this](float percentage) {
+        alm::exportImage(iei, [this](float percentage) {
             emit progress(percentage);
-        });
-        emit finished(true, "Image successfully exported.");
+        }, stopCallback);
+        if (!stopCallback())
+            emit finished(true, "Image successfully exported.");
+        else
+            emit finished(false, "Image export cancelled.");
     }
     catch (alm::ImageExportException& ex) {
         emit finished(false, QString("Error during image export: ") + ex.what());
@@ -45,7 +49,10 @@ void VideoExportTask::run(void)
             emit progress(mvpi.progress);
         });
         mvg.generate(generator);
-        emit finished(true, "Video successfully exported.");
+        if (!stopCallback())
+            emit finished(true, "Video successfully exported.");
+        else
+            emit finished(false, "Video export cancelled.");
     }
     catch (alm::VideoExportException& ex) {
         emit finished(false, QString("Error during video export: ") + ex.what());

+ 3 - 2
BackgroundTask.h

@@ -13,8 +13,9 @@ class BackgroundTask : public QObject, public QRunnable
     Q_OBJECT
 protected:
     std::string shortDescription;
+    std::function<bool(void)> stopCallback;
 public:
-    BackgroundTask(const std::string& shortDescription);
+    BackgroundTask(const std::string& shortDescription, std::function<bool(void)> stopCallback = [] () { return false; });
 
     void run(void) = 0;
 
@@ -32,7 +33,7 @@ class ImageExportTask : public BackgroundTask
 private:
     const alm::ImageExportInfo iei;
 public:
-    ImageExportTask(const alm::ImageExportInfo& iei);
+    ImageExportTask(const alm::ImageExportInfo& iei, std::function<bool(void)> stopCallback = [] () { return false; });
 
     void run(void) override;
 };

+ 13 - 4
CMakeLists.txt

@@ -24,11 +24,13 @@ FILE(GLOB AlmondHeaders *.h)
 
 
 IF (WIN32)
-    add_executable(Almond WIN32 ${AlmondSources} Almond.qrc splash.qrc icon.rc)
+    add_executable(Almond WIN32 ${AlmondSources} Almond.qrc splash.qrc icon.rc ${AlmondHeaders})
 ELSE()
-    add_executable(Almond ${AlmondSources} Almond.qrc splash.qrc)
+    add_executable(Almond ${AlmondSources} Almond.qrc splash.qrc ${AlmondHeaders})
 ENDIF()
 
+target_include_directories(Almond PUBLIC .)
+
 add_subdirectory(libalmond)
 
 target_include_directories(Almond SYSTEM PUBLIC ${FFMPEG_INCLUDE_DIRS})
@@ -69,11 +71,18 @@ IF (WIN32)
 
 ELSEIF (UNIX AND NOT APPLE)
     install(TARGETS Almond RUNTIME DESTINATION "bin")
+    install(FILES Almond.desktop DESTINATION "share/applications")
+    install(FILES Almond.png DESTINATION "share/pixmaps")
+    install(FILES debian/copyright DESTINATION "share/doc/almond/" RENAME "copyright")
     set(CPACK_GENERATOR "DEB")
+    set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE)
+    set(CPACK_STRIP_FILES TRUE)
     set(CPACK_SOURCE_GENERATOR "DEB")
     set(CPACK_COMPONENTS_ALL Almond)
-    set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Nicolas Winkler")
-    set(CPACK_DEBIAN_PACKAGE_DEPENDS "qt5-default,libavformat58,libavdevice58,libavfilter7,libavutil56,libswscale5,libgl1,ocl-icd-libopencl1,libpng16-16")
+    set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A fractal viewer")
+    set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE amd64)
+    set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Nicolas Winkler <nicolas.winkler@gmx.ch>")
+    set(CPACK_DEBIAN_PACKAGE_DEPENDS "qtbase5-dev,libavformat58,libavdevice58,libavfilter7,libavutil56,libswscale5,ocl-icd-libopencl1,libpng16-16,libc6")
     set(CPACK_SET_DESTDIR True)
     set(CPACK_INSTALL_PREFIX "/usr")
     include(CPack)

+ 0 - 66
CubicSpline.cpp

@@ -1,66 +0,0 @@
-#include "CubicSpline.h"
-
-CubicSpline::CubicSpline(const std::vector<std::pair<float, float> >& dataPoints, bool useSlopes) :
-    useSlopes{ useSlopes }
-{
-    if (dataPoints.size() < 2) {
-        return;
-    }
-
-    points.push_back({ dataPoints[0].first, dataPoints[0].second,
-                       (dataPoints[1].second - dataPoints[0].second) /
-                       (dataPoints[1].first - dataPoints[0].first) });
-    for (size_t i = 1; i < dataPoints.size() - 1; i++) {
-
-        auto& dp1 = dataPoints[i - 1];
-        auto& dp2 = dataPoints[i];
-        auto& dp3 = dataPoints[i + 1];
-
-        float w1 = dp2.first - dp1.first;
-        float w2 = dp3.first - dp2.first;
-        float h1 = dp2.second - dp1.second;
-        float h2 = dp3.second - dp2.second;
-
-        float s1 = h1 / w1;
-        float s2 = h2 / w2;
-
-        float avgSlope = (s1 + s2) / 2;
-        points.push_back({ dp2.first, dp2.second, avgSlope });
-    }
-    points.push_back({ dataPoints[dataPoints.size() - 1].first, dataPoints[dataPoints.size() - 1].second,
-                       (dataPoints[dataPoints.size() - 2].second - dataPoints[dataPoints.size() - 1].second) /
-                       (dataPoints[dataPoints.size() - 2].first - dataPoints[dataPoints.size() - 1].first) });
-}
-
-
-float CubicSpline::interpolateAt(float x)
-{
-    const static auto h00 = [] (float t) { return (1 + 2 * t) * (1 - t) * (1 - t); };
-    const static auto h01 = [] (float t) { return t * t * (3 - 2 * t); };
-    const static auto h10 = [] (float t) { return t * (1 - t) * (1 - t); };
-    const static auto h11 = [] (float t) { return t * t * (t - 1); };
-    for (auto it = points.begin(); it != points.end() && (it + 1) != points.end(); ++it) {
-        auto& left = *it;
-        auto& right = *(it + 1);
-        float xleft = std::get<0>(left);
-        float xright = std::get<0>(right);
-        if (xleft < x && xright >= x) {
-            float w = (xright - xleft);
-            float t = (x - xleft) / w;
-            float yleft = std::get<1>(left);
-            float yright = std::get<1>(right);
-            float sleft = std::get<2>(left);
-            float sright = std::get<2>(right);
-
-            float inter = h00(t) * yleft +
-                          h01(t) * yright;
-
-            if (useSlopes)
-                inter += h10(t) * w * sleft +
-                         h11(t) * w * sright;
-
-            return inter;
-        }
-    }
-    return std::get<1>(points[points.size() - 1]);
-}

+ 0 - 19
CubicSpline.h

@@ -1,19 +0,0 @@
-#ifndef CUBICSPLINE_H
-#define CUBICSPLINE_H
-
-#include <vector>
-#include <utility>
-#include <tuple>
-
-class CubicSpline
-{
-    /// contains x, y and y' of each interpolation point
-    std::vector<std::tuple<float, float, float>> points;
-    bool useSlopes;
-public:
-    CubicSpline(const std::vector<std::pair<float, float>>& dataPoints, bool useSlopes);
-
-    float interpolateAt(float x);
-};
-
-#endif // CUBICSPLINE_H

+ 71 - 0
ExportImageMenu.cpp

@@ -0,0 +1,71 @@
+#include "ExportImageMenu.h"
+#include "ui_ExportImageMenu.h"
+
+#include "ImageExport.h"
+
+#include <QIntValidator>
+#include <QFileDialog>
+
+ExportImageMenu::ExportImageMenu(QWidget *parent) :
+    QWidget(parent),
+    ui(new Ui::ExportImageMenu)
+{
+    ui->setupUi(this);
+    ui->maxIterTxt->setValidator(new QIntValidator(1, 1000000000, this));
+    ui->widthTxt->setValidator(new QIntValidator(1, 10000000, this));
+    ui->heightTxt->setValidator(new QIntValidator(1, 10000000, this));
+}
+
+
+ExportImageMenu::~ExportImageMenu()
+{
+    delete ui;
+}
+
+
+int ExportImageMenu::getMaxIterations(void) const
+{
+    return ui->maxIterTxt->text().toInt();
+}
+
+
+int ExportImageMenu::getWidth(void) const
+{
+    return ui->widthTxt->text().toInt();
+}
+
+
+int ExportImageMenu::getHeight(void) const
+{
+    return ui->heightTxt->text().toInt();
+}
+
+
+QString ExportImageMenu::getPath(void) const
+{
+    return ui->pathTxt->text();
+}
+
+
+void ExportImageMenu::on_pathBtn_clicked()
+{
+    std::string formatString = "";
+    if (alm::supportsImageFormat(alm::ImageFormat::PNG)) {
+        formatString += "PNG image (*.png);;";
+    }
+    if (alm::supportsImageFormat(alm::ImageFormat::JPEG)) {
+        formatString += "JPEG image (*.jpg *.jpeg);;";
+    }
+    if (alm::supportsImageFormat(alm::ImageFormat::BMP)) {
+        formatString += "BMP image (*.bmp *.dib);;";
+    }
+    if (!formatString.empty()) {
+        formatString.erase(formatString.end() - 2, formatString.end());
+    }
+
+    QString saveAs = QFileDialog::getSaveFileName(this,
+            tr("Save exported image"), "",
+            tr(formatString.c_str()));
+    if(!saveAs.isEmpty() && !saveAs.isNull())
+        ui->pathTxt->setText(saveAs);
+}

+ 30 - 0
ExportImageMenu.h

@@ -0,0 +1,30 @@
+#ifndef EXPORTIMAGEMENU_H
+#define EXPORTIMAGEMENU_H
+
+#include <QWidget>
+
+namespace Ui {
+class ExportImageMenu;
+}
+
+class ExportImageMenu : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit ExportImageMenu(QWidget *parent = nullptr);
+    ~ExportImageMenu();
+
+    int getMaxIterations(void) const;
+    int getWidth(void) const;
+    int getHeight(void) const;
+    QString getPath(void) const;
+
+private slots:
+    void on_pathBtn_clicked();
+
+private:
+    Ui::ExportImageMenu *ui;
+};
+
+#endif // EXPORTIMAGEMENU_H

+ 99 - 0
ExportImageMenu.ui

@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportImageMenu</class>
+ <widget class="QWidget" name="ExportImageMenu">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>285</width>
+    <height>330</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QFormLayout" name="formLayout">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item row="0" column="0" colspan="2">
+    <widget class="QLabel" name="titleLbl">
+     <property name="font">
+      <font>
+       <pointsize>14</pointsize>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>Export Image</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="maxIterLbl">
+     <property name="text">
+      <string>Max. Iterations</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QLineEdit" name="maxIterTxt">
+     <property name="text">
+      <string>5000</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="widthLbl">
+     <property name="text">
+      <string>Width</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1">
+    <widget class="QLineEdit" name="widthTxt">
+     <property name="text">
+      <string>1920</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0">
+    <widget class="QLabel" name="heightLbl">
+     <property name="text">
+      <string>Height</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="1">
+    <widget class="QLineEdit" name="heightTxt">
+     <property name="text">
+      <string>1080</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="0">
+    <widget class="QPushButton" name="pathBtn">
+     <property name="text">
+      <string>Save As:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="4" column="1">
+    <widget class="QLineEdit" name="pathTxt"/>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 123 - 0
ExportVideoMenu.cpp

@@ -0,0 +1,123 @@
+#include "ExportVideoMenu.h"
+#include "ui_ExportVideoMenu.h"
+#include <QIntValidator>
+#include <QDoubleValidator>
+#include <QFileDialog>
+
+ExportVideoMenu::ExportVideoMenu(QWidget *parent) :
+    QWidget{ parent },
+    ui{ new Ui::ExportVideoMenu }
+{
+    ui->setupUi(this);
+    this->adjustSize();
+    ui->maxIterTxt->setValidator(new QIntValidator(1, 1000000000, this));
+    ui->widthTxt->setValidator(new QIntValidator(1, 10000000, this));
+    ui->heightTxt->setValidator(new QIntValidator(1, 10000000, this));
+    ui->bitrate->setValidator(new QIntValidator(1, 10000000, this));
+    ui->fps->setValidator(new QIntValidator(1, 8000, this));
+    ui->zoomSpeed->setValidator(new QDoubleValidator(0.0, 100.0, -1, this));
+
+
+    /*
+    evd.startX->setText(QString::fromStdString(evi.start.x.str()));
+    evd.startY->setText(QString::fromStdString(evi.start.y.str()));
+    evd.startW->setText(QString::fromStdString(evi.start.width.str()));
+    evd.startH->setText(QString::fromStdString(evi.start.height.str()));
+
+    evd.endX->setText(QString::fromStdString(evi.end.x.str()));
+    evd.endY->setText(QString::fromStdString(evi.end.y.str()));
+    evd.endW->setText(QString::fromStdString(evi.end.width.str()));
+    evd.endH->setText(QString::fromStdString(evi.end.height.str()));
+    */
+
+
+    const auto presets = {
+        "ultrafast",
+        "superfast",
+        "veryfast",
+        "faster",
+        "fast",
+        "medium",
+        "slow",
+        "slower",
+        "veryslow",
+    };
+    for (auto& preset : presets) {
+        ui->encodingPresetBox->addItem(preset);
+    }
+    ui->encodingPresetBox->setCurrentText("medium");
+    setStartViewport(mnd::MandelViewport::standardView());
+}
+
+
+ExportVideoMenu::~ExportVideoMenu()
+{
+    delete ui;
+}
+
+
+ExportVideoInfo ExportVideoMenu::getInfo(void) const
+{
+    ExportVideoInfo evi;
+    /*if (ui->pathTxt->text() == "") {
+        QMessageBox* msgBox = new QMessageBox;
+        msgBox->setText("Please specify a path.");
+        msgBox->exec();
+        emit reject();
+    }*/
+
+    evi.path = ui->pathTxt->text().toStdString();
+
+    evi.mi.bWidth = ui->widthTxt->text().toInt();
+    evi.mi.bHeight = ui->heightTxt->text().toInt();
+    evi.mi.maxIter = ui->maxIterTxt->text().toInt();
+
+    evi.bitrate = ui->bitrate->text().toInt();
+    evi.preset = ui->encodingPresetBox->currentText().toStdString();
+    evi.fps = ui->fps->text().toInt();
+    evi.zoomSpeed = QLocale::system().toDouble(ui->zoomSpeed->text());
+    evi.start = mnd::MandelViewport {
+        mnd::Real(ui->startX->text().toStdString().c_str()),
+        mnd::Real(ui->startY->text().toStdString().c_str()),
+        mnd::Real(ui->startW->text().toStdString().c_str()),
+        mnd::Real(ui->startH->text().toStdString().c_str())
+    };
+    evi.end = mnd::MandelViewport {
+        mnd::Real(ui->endX->text().toStdString().c_str()),
+        mnd::Real(ui->endY->text().toStdString().c_str()),
+        mnd::Real(ui->endW->text().toStdString().c_str()),
+        mnd::Real(ui->endH->text().toStdString().c_str())
+    };
+
+    evi.start.adjustAspectRatio(evi.mi.bWidth, evi.mi.bHeight);
+    evi.end.adjustAspectRatio(evi.mi.bWidth, evi.mi.bHeight);
+
+    return evi;
+}
+
+
+void ExportVideoMenu::setStartViewport(const mnd::MandelViewport& mv)
+{
+    ui->startX->setText(QString::fromStdString(mnd::toString(mv.x)));
+    ui->startY->setText(QString::fromStdString(mnd::toString(mv.y)));
+    ui->startW->setText(QString::fromStdString(mnd::toString(mv.width)));
+    ui->startH->setText(QString::fromStdString(mnd::toString(mv.height)));
+}
+
+
+void ExportVideoMenu::setEndViewport(const mnd::MandelViewport& mv)
+{
+    ui->endX->setText(QString::fromStdString(mnd::toString(mv.x)));
+    ui->endY->setText(QString::fromStdString(mnd::toString(mv.y)));
+    ui->endW->setText(QString::fromStdString(mnd::toString(mv.width)));
+    ui->endH->setText(QString::fromStdString(mnd::toString(mv.height)));
+}
+
+
+void ExportVideoMenu::on_pathBtn_clicked()
+{
+    QString saveAs = QFileDialog::getSaveFileName(this,
+            tr("Save exported image"), "",
+            tr("AVI video (*.avi);;MP4 video (*.mp4);;All Files (*)"));
+    ui->pathTxt->setText(saveAs);
+}

+ 31 - 0
ExportVideoMenu.h

@@ -0,0 +1,31 @@
+#ifndef EXPORTVIDEOMENU_H
+#define EXPORTVIDEOMENU_H
+
+#include <QWidget>
+
+#include "MandelVideoGenerator.h"
+
+namespace Ui
+{
+    class ExportVideoMenu;
+}
+
+class ExportVideoMenu : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit ExportVideoMenu(QWidget *parent = nullptr);
+    ~ExportVideoMenu();
+
+    ExportVideoInfo getInfo(void) const;
+    void setStartViewport(const mnd::MandelViewport& mv);
+    void setEndViewport(const mnd::MandelViewport& mv);
+private slots:
+    void on_pathBtn_clicked();
+
+private:
+    Ui::ExportVideoMenu *ui;
+};
+
+#endif // EXPORTVIDEOMENU_H

+ 252 - 0
ExportVideoMenu.ui

@@ -0,0 +1,252 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportVideoMenu</class>
+ <widget class="QWidget" name="ExportVideoMenu">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>614</width>
+    <height>543</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Frame</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QLabel" name="titleLbl">
+     <property name="font">
+      <font>
+       <pointsize>14</pointsize>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>Export Video</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QToolBox" name="toolBox">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>1</verstretch>
+      </sizepolicy>
+     </property>
+     <widget class="QWidget" name="toolBoxPage1">
+      <property name="geometry">
+       <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>586</width>
+        <height>640</height>
+       </rect>
+      </property>
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <attribute name="label">
+       <string>General</string>
+      </attribute>
+      <layout class="QFormLayout" name="formLayout_2">
+       <item row="0" column="0">
+        <widget class="QLabel" name="maxIterLbl">
+         <property name="text">
+          <string>Max. Iterations</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="QLineEdit" name="maxIterTxt">
+         <property name="text">
+          <string>5000</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <widget class="QLabel" name="widthLbl">
+         <property name="text">
+          <string>Video Width</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <widget class="QLineEdit" name="widthTxt">
+         <property name="text">
+          <string>1920</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0">
+        <widget class="QLabel" name="heightLbl">
+         <property name="text">
+          <string>Video Height</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1">
+        <widget class="QLineEdit" name="heightTxt">
+         <property name="text">
+          <string>1080</string>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="0" colspan="2">
+        <widget class="QGroupBox" name="groupBox">
+         <property name="title">
+          <string>Start View</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_2">
+          <item row="1" column="0">
+           <widget class="QLineEdit" name="startX"/>
+          </item>
+          <item row="0" column="0">
+           <widget class="QLineEdit" name="startW"/>
+          </item>
+          <item row="1" column="1">
+           <widget class="QLineEdit" name="startY"/>
+          </item>
+          <item row="0" column="1">
+           <widget class="QLineEdit" name="startH"/>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item row="4" column="0" colspan="2">
+        <widget class="QGroupBox" name="groupBox_2">
+         <property name="title">
+          <string>End View</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout">
+          <item row="0" column="0">
+           <widget class="QLineEdit" name="endX"/>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLineEdit" name="endW"/>
+          </item>
+          <item row="1" column="1">
+           <widget class="QLineEdit" name="endH"/>
+          </item>
+          <item row="0" column="1">
+           <widget class="QLineEdit" name="endY"/>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item row="5" column="0">
+        <widget class="QPushButton" name="pathBtn">
+         <property name="text">
+          <string>Save As</string>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="1">
+        <widget class="QLineEdit" name="pathTxt"/>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="toolBoxPage2">
+      <property name="geometry">
+       <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>614</width>
+        <height>373</height>
+       </rect>
+      </property>
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <attribute name="label">
+       <string>Encoding</string>
+      </attribute>
+      <layout class="QFormLayout" name="formLayout_4">
+       <item row="0" column="0">
+        <widget class="QLabel" name="label_6">
+         <property name="text">
+          <string>Bitrate [kbps]</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="QLineEdit" name="bitrate">
+         <property name="text">
+          <string>5000</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <widget class="QLabel" name="label_7">
+         <property name="text">
+          <string>Encoding preset</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <widget class="QComboBox" name="encodingPresetBox">
+         <property name="editable">
+          <bool>false</bool>
+         </property>
+         <property name="currentText">
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0">
+        <widget class="QLabel" name="label_8">
+         <property name="text">
+          <string>Fps</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1">
+        <widget class="QLineEdit" name="fps">
+         <property name="text">
+          <string>60</string>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="0">
+        <widget class="QLabel" name="label_9">
+         <property name="text">
+          <string>Zoom Speed</string>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="1">
+        <widget class="QLineEdit" name="zoomSpeed">
+         <property name="text">
+          <string>1</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 0 - 164
Gradient.cpp

@@ -1,164 +0,0 @@
-#include "Gradient.h"
-
-#include "CubicSpline.h"
-
-#include <cmath>
-#include <algorithm>
-#include <functional>
-#include <QtXml/QDomDocument>
-#include <QFile>
-
-
-Gradient::Gradient(void) :
-    max{ 1.0 }
-{
-}
-
-
-Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat, int precalcSteps) :
-    repeat{ repeat }
-{
-    if(colors.empty() || colors.size() < 2)
-        return;
-    std::sort(colors.begin(), colors.end(),
-        [] (const auto& a, const auto& b) {
-            return a.second < b.second;
-        });
-
-    max = colors.at(colors.size() - 1).second;
-
-    std::vector<std::pair<RGBColorf, float>> linearColors;
-    std::transform(colors.begin(), colors.end(), std::back_inserter(linearColors),
-                   [] (auto c) { return c; });
-
-    std::vector<std::pair<float, float>> rs;
-    std::vector<std::pair<float, float>> gs;
-    std::vector<std::pair<float, float>> bs;
-
-    std::transform(linearColors.begin(), linearColors.end(), std::back_inserter(rs),
-                   [] (auto p) { return std::pair{ p.second, p.first.r }; });
-    std::transform(linearColors.begin(), linearColors.end(), std::back_inserter(gs),
-                   [] (auto p) { return std::pair{ p.second, p.first.g }; });
-    std::transform(linearColors.begin(), linearColors.end(), std::back_inserter(bs),
-                   [] (auto p) { return std::pair{ p.second, p.first.b }; });
-
-    CubicSpline rsp(rs, false);
-    CubicSpline gsp(gs, false);
-    CubicSpline bsp(bs, false);
-
-    if(precalcSteps <= 0) {
-        precalcSteps = int(max * 15) + 10;
-    }
-
-    for (int i = 0; i < precalcSteps; i++) {
-        float position = i * max / precalcSteps;
-        RGBColorf at = {
-            rsp.interpolateAt(position),
-            gsp.interpolateAt(position),
-            bsp.interpolateAt(position)
-        };
-        this->colors.push_back(at);
-    }
-}
-
-
-Gradient Gradient::defaultGradient(void)
-{
-    QFile res(":/gradients/default");
-    res.open(QIODevice::ReadOnly);
-    QString str = QString::fromUtf8(res.readAll());
-    return readXml(str);
-}
-
-Gradient Gradient::readXml(const QString& xml)
-{
-    QDomDocument xsr;
-    xsr.setContent(xml);
-    auto elem = xsr.documentElement();
-    auto repeatAttr = elem.attributeNode("repeat");
-    bool repeat = !repeatAttr.isNull() && repeatAttr.value().toLower() == "true";
-    auto colors = xsr.elementsByTagName("color");
-    std::vector<std::pair<RGBColor, float>> colorArr;
-    for (int i = 0; i < colors.length(); ++i) {
-        auto child = colors.item(i).toElement();
-        uint8_t r = uint8_t(child.attributeNode("r").value().toInt());
-        uint8_t g = uint8_t(child.attributeNode("g").value().toInt());
-        uint8_t b = uint8_t(child.attributeNode("b").value().toInt());
-        float p = child.attributeNode("p").value().toInt();
-
-        //printf("rgb (%s): %d, %d, %d\n", child.text().toUtf8().data(), r, g, b);
-        colorArr.push_back({ { r, g, b }, p });
-    }
-
-    return Gradient(std::move(colorArr), repeat);
-}
-
-
-RGBColor Gradient::get(float x) const
-{
-    if (colors.empty() || std::isnan(x) || std::isinf(x))
-        return RGBColor();
-    /*const auto [left, right, lerp] = getNeighbors(x);
-    RGBColor lerped = lerpColors(left, right, lerp);
-    return lerped;*/
-
-    if (x < 0)
-        return colors[0];
-    if (x > this->max) {
-        if (repeat)
-            x = ::fmodf(x, this->max);
-        else
-            x = this->max;
-    }
-    float pos = x * colors.size() / max;
-    if (pos < 0) {
-        pos = 0;
-    }
-    if (pos > colors.size() - 1) {
-        pos = colors.size() - 1;
-    }
-
-    int left = int(pos);
-    int right = int(pos + 1);
-    float lerp = pos - left;
-
-    if (lerp < 1e-5f) {
-        return colors[left];
-    }
-    else {
-        return lerpColors(colors[left], colors[right], lerp);
-    }
-}
-
-
-RGBColorf Gradient::lerpColors(RGBColorf a, RGBColorf b, float val)
-{
-    return RGBColorf {
-        b.r * val + a.r * (1 - val),
-        b.g * val + a.g * (1 - val),
-        b.b * val + a.b * (1 - val)
-    };
-}
-
-
-RGBColor Gradient::lerpColors(RGBColor a, RGBColor b, float val)
-{
-    return RGBColor{ lerpColors(RGBColorf{ a }, RGBColorf{ b }, val) };
-}
-
-
-/*std::tuple<RGBColor, RGBColor, float> Gradient::getNeighbors(float x) const
-{
-    for (auto it = colors.begin(); it != colors.end(); ++it) {
-        if (it->second > x) {
-            if (it == colors.begin()) {
-                return { it->first, it->first, 0 };
-            }
-            else {
-                float lerp = (x - (it - 1)->second) / (it->second - (it - 1)->second);
-                return { (it - 1)->first, it->first, lerp };
-            }
-        }
-    }
-    return { (colors.end() - 1)->first, (colors.end() - 1)->first, 0 };
-}*/

+ 0 - 38
Gradient.h

@@ -1,38 +0,0 @@
-#ifndef GRADIENT_H
-#define GRADIENT_H
-
-#include <QString>
-#include <vector>
-#include "Color.h"
-#include <tuple>
-#include <cinttypes>
-
-
-class Gradient
-{
-    /// the colors of this gradient stored in linear RGB format
-    /// so they can be easily interpolated
-    std::vector<RGBColorf> colors;
-    float max;
-    bool repeat;
-public:
-    Gradient(void);
-    Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat = false, int precalcSteps = -1);
-
-    static Gradient defaultGradient(void);
-
-    static Gradient readXml(const QString& xml);
-
-    /*!
-     * \brief get a color at a specific position in this gradient
-     * \param x the position
-     * \return the color in sRGB format
-     */
-    RGBColor get(float x) const;
-private:
-    static RGBColorf lerpColors(RGBColorf a, RGBColorf b, float val);
-    static RGBColor lerpColors(RGBColor a, RGBColor b, float val);
-    std::tuple<RGBColor, RGBColor, float> getNeighbors(float x) const;
-};
-
-#endif // GRADIENT_H

+ 32 - 0
GradientMenu.cpp

@@ -0,0 +1,32 @@
+#include "GradientMenu.h"
+#include "ui_GradientMenu.h"
+
+GradientMenu::GradientMenu(QWidget *parent) :
+    QWidget(parent),
+    ui(new Ui::GradientMenu)
+{
+    ui->setupUi(this);
+    ui->gradientWidget->setGradient(
+        {
+            { 0.1, QColor{ 10, 200, 20 } },
+            { 0.7, QColor{ 100, 20, 120 } }
+        }
+    );
+}
+
+
+GradientMenu::~GradientMenu()
+{
+    delete ui;
+}
+
+
+const QVector<QPair<float, QColor>>& GradientMenu::getGradient(void)
+{
+    return ui->gradientWidget->getGradient();
+}
+
+void GradientMenu::setGradient(QVector<QPair<float, QColor>> grad)
+{
+    ui->gradientWidget->setGradient(std::move(grad));
+}

+ 27 - 0
GradientMenu.h

@@ -0,0 +1,27 @@
+#ifndef GRADIENTMENU_H
+#define GRADIENTMENU_H
+
+#include <QWidget>
+#include <QVector>
+#include <QPair>
+
+namespace Ui {
+class GradientMenu;
+}
+
+class GradientMenu : public QWidget
+{
+    Q_OBJECT
+
+public:
+    explicit GradientMenu(QWidget *parent = nullptr);
+    ~GradientMenu(void);
+
+    const QVector<QPair<float, QColor>>& getGradient(void);
+    void setGradient(QVector<QPair<float, QColor>> grad);
+
+private:
+    Ui::GradientMenu *ui;
+};
+
+#endif // GRADIENTMENU_H

+ 58 - 0
GradientMenu.ui

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>GradientMenu</class>
+ <widget class="QWidget" name="GradientMenu">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>302</width>
+    <height>508</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Select Gradient</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
+   <property name="leftMargin">
+    <number>0</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QLabel" name="titleLbl">
+     <property name="font">
+      <font>
+       <pointsize>14</pointsize>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
+     <property name="text">
+      <string>Select Gradient</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="GradientWidget" name="gradientWidget" native="true"/>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>GradientWidget</class>
+   <extends>QWidget</extends>
+   <header>GradientWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

+ 372 - 1
GradientWidget.cpp

@@ -1,6 +1,377 @@
 #include "GradientWidget.h"
 
-GradientWidget::GradientWidget(QWidget *parent) : QWidget(parent)
+#include <QPaintEvent>
+#include <QPainter>
+#include <QStyleOptionSlider>
+#include <QColorDialog>
+#include <QStyle>
+
+#include <algorithm>
+#include <cmath>
+
+GradientWidget::GradientWidget(QWidget* parent) :
+    QWidget{ parent }
+{
+    dragging = false;
+    selectedHandle = -1;
+    mouseOver = -1;
+
+    setMouseTracking(true);
+}
+
+
+const QVector<QPair<float, QColor>>& GradientWidget::getGradient(void) const
+{
+    return points;
+}
+
+
+void GradientWidget::setGradient(QVector<QPair<float, QColor>> vec)
+{
+    points = std::move(vec);
+}
+
+
+QColor lerp(const QColor& a, const QColor& b, float v)
+{
+    float ar = a.redF();
+    float ag = a.greenF();
+    float ab = a.blueF();
+
+    float br = b.redF();
+    float bg = b.greenF();
+    float bb = b.blueF();
+
+    const float gamma = 2.2;
+
+    ar = std::pow(ar, gamma);
+    ag = std::pow(ag, gamma);
+    ab = std::pow(ab, gamma);
+
+    br = std::pow(br, gamma);
+    bg = std::pow(bg, gamma);
+    bb = std::pow(bb, gamma);
+
+    float nr = br * v + (1 - v) * ar;
+    float ng = bg * v + (1 - v) * ag;
+    float nb = bb * v + (1 - v) * ab;
+
+    nr = std::pow(nr, 1/gamma);
+    ng = std::pow(ng, 1/gamma);
+    nb = std::pow(nb, 1/gamma);
+
+    return QColor{ int(255 * nr), int(255 * ng), int(255 * nb) };
+}
+
+
+QColor GradientWidget::colorAtY(float y)
+{
+    float v = handleYToGradVal(y);
+    QColor up = QColor(QColor::Invalid);
+    QColor down = QColor(QColor::Invalid);
+    float upv = 0;
+    float downv = 1;
+    for (const auto& [val, color] : points) {
+        if (val >= upv && val < v) {
+            upv = val;
+            up = color;
+        }
+        if (val <= downv && val > v) {
+            downv = val;
+            down = color;
+        }
+    }
+
+    if (!up.isValid())
+        return down;
+    if (!down.isValid())
+        return up;
+    return lerp(up, down, (v - upv) / (downv - upv));
+}
+
+void GradientWidget::paintEvent(QPaintEvent* e)
+{
+    QPainter painter{ this };
+
+
+    QRect gradientRect = getGradientRect();
+    QStyleOption frameOptions;
+    frameOptions.init(this);
+    frameOptions.rect = gradientRect;
+    frameOptions.state |= QStyle::State_Sunken;
+    style()->drawPrimitive(
+                QStyle::PrimitiveElement::PE_Frame, &frameOptions, &painter, this);
+
+    int fhmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameHMargin);
+    int fvmargins = style()->pixelMetric(QStyle::PixelMetric::PM_FocusFrameVMargin);
+    if (fhmargins == -1) {
+        fhmargins = fvmargins = 3;
+    }
+
+    QLinearGradient gradient;
+    gradient.setStart(0, gradientRect.top());
+    gradient.setFinalStop(0, gradientRect.bottom());
+
+    // adjust rect to have small margins, so the frame
+    // around the gradient is visible
+    gradientRect.adjust(fhmargins, fvmargins, -fhmargins, -fvmargins);
+    float lastPoint = 0;
+    QColor lastColor = QColor{ 0, 0, 0 };
+
+
+
+    std::vector<int> orderedIndices(points.size());
+    for (int i = 0; i < points.size(); i++)
+        orderedIndices.push_back(i);
+
+    std::sort(orderedIndices.begin(), orderedIndices.end(),
+        [this] (int l, int r) {
+        return points[l].first < points[r].first;
+    });
+
+    // traverse gradient in order and interpolate in linear
+    // RGB space to avoid interpolating in sRGB
+    for (int i = 0; i < orderedIndices.size(); i++) {
+        int index = orderedIndices[i];
+        auto& [point, color] = points[index];
+        int m = 17;
+        if (i > 0) {
+            for (int i = 0; i < m; i++) {
+                float v = float(i) / m;
+                gradient.setColorAt(lastPoint + (point - lastPoint) / m * i,
+                                    lerp(lastColor, color, v));
+            }
+        }
+        gradient.setColorAt(point, color);
+        lastPoint = point;
+        lastColor = color;
+    }
+
+    QBrush brush{ gradient };
+    painter.fillRect(gradientRect, brush);
+
+    int index = 0;
+    for (auto& [point, color] : points) {
+        QRect r = getHandleRect(index);
+        QStyleOptionButton so;
+        so.init(this);
+        so.rect = r;
+        if (dragging && selectedHandle == index)
+            so.state |= QStyle::State_Sunken;
+        else if (selectedHandle == index)
+            so.state |= QStyle::State_HasFocus;
+        else
+            so.state &= ~QStyle::State_Sunken & ~QStyle::State_HasFocus;
+        if (mouseOver == index)
+            so.state |= QStyle::State_MouseOver;
+        else
+            so.state &= ~QStyle::State_MouseOver;
+        
+        so.palette.setColor(QPalette::ColorRole::Button, color);
+        style()->drawControl(QStyle::ControlElement::CE_PushButton, &so, &painter, this);
+        index++;
+    }
+    /*for (auto&[point, color] : points) {
+        QStyleOptionSlider qsos;
+        qsos.rect = QRect{ 100, static_cast<int>(point * height() - 10), 20, 20 };
+        qsos.orientation = Qt::Vertical;
+        qsos.subControls = QStyle::SC_SliderHandle;
+        if (selectedHandle == index) {
+            qsos.state |= QStyle::State_Sunken;
+        }
+        else {
+            qsos.state &= ~QStyle::State_Sunken;
+        }
+        style()->drawComplexControl(QStyle::CC_Slider, &qsos, &painter, this);
+        index++;
+    }*/
+    /*
+
+    QPen pen(Qt::red);
+    pen.setWidth(10);
+    painter.setPen(pen);
+    painter.drawRect(30, 30, 50, 50);
+
+    painter.fillRect(QRect(0, 0, width(), height()), QColor{ 100, 20, 20 });
+    qDebug(std::to_string(width()).c_str());
+    */
+}
+
+
+void GradientWidget::mousePressEvent(QMouseEvent* e)
+{
+    int handle = handleAtPos(e->pos());
+
+    if (handle != -1) {
+        selectedHandle = handle;
+        dragging = true;
+        selectOffsetY = e->y() - gradValToHandleY(
+                    points[handle].first);
+        update();
+        e->accept();
+    }
+    else {
+        e->ignore();
+    }
+}
+
+
+void GradientWidget::mouseReleaseEvent(QMouseEvent* e)
+{
+    if (dragging) {
+        dragging = false;
+        update();
+        e->accept();
+    }
+    else {
+        e->ignore();
+    }
+}
+
+
+void GradientWidget::mouseMoveEvent(QMouseEvent* e)
+{
+    if (dragging) {
+        float newVal = handleYToGradVal(e->y() - selectOffsetY);
+        newVal = std::clamp(newVal, 0.0f, 1.0f);
+        points[selectedHandle].first = newVal;
+        update();
+        e->accept();
+    }
+    else {
+        int handle = handleAtPos(e->pos());
+        bool needsUpdate = false;
+        if (mouseOver != handle)
+            needsUpdate = true;
+        mouseOver = handle;
+        e->accept();
+        if (needsUpdate)
+            update();
+    }
+}
+
+
+void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
+{
+    QRect handleArea = getHandleArea();
+    int handle = handleAtPos(e->pos());
+    if (handle != -1) {
+        QColor current = points.at(handle).second;
+        QColor newColor = QColorDialog::getColor(current,
+                                                 this,
+                                                 tr("Pick Color"));
+        if (newColor.isValid()) {
+            points[handle].second = newColor;
+            update();
+        }
+        e->accept();
+    }
+    else if (handleArea.contains(e->pos())) {
+        float v = handleYToGradVal(e->pos().y());
+        points.append({ v, colorAtY(e->pos().y()) });
+        e->accept();
+        update();
+    }
+    else {
+        e->ignore();
+    }
+}
+
+
+QSize GradientWidget::minimumSizeHint(void) const
+{
+    int spacing = this->style()->pixelMetric(
+                QStyle::PM_LayoutHorizontalSpacing);
+    if (spacing == -1) {
+        spacing = this->style()->layoutSpacing(
+                    QSizePolicy::Frame,
+                    QSizePolicy::PushButton,
+                    Qt::Horizontal);
+    }
+    return QSize{ int(handleWidth * 1.5 + spacing), handleHeight * 5 };
+}
+
+
+QSize GradientWidget::sizeHint(void) const
 {
+    int spacing = this->style()->pixelMetric(
+                QStyle::PM_LayoutHorizontalSpacing);
+    if (spacing == -1) {
+        spacing = this->style()->layoutSpacing(
+                    QSizePolicy::Frame,
+                    QSizePolicy::PushButton,
+                    Qt::Horizontal);
+    }
+    return QSize{ int(handleWidth * 1.1 + spacing), handleHeight };
+}
+
+
+QRect GradientWidget::getGradientRect(void) const
+{
+    int left, top, right, bottom;
+    getContentsMargins(&left, &top, &right, &bottom);
+    int spacing = this->style()->pixelMetric(
+                QStyle::PM_LayoutHorizontalSpacing);
+    if (spacing == -1) {
+        spacing = this->style()->layoutSpacing(
+                    QSizePolicy::Frame,
+                    QSizePolicy::PushButton,
+                    Qt::Horizontal);
+    }
+    top += handleHeight / 2;
+    bottom += handleHeight / 2;
+    return  QRect{ left, top,
+                width() - left - right - handleWidth - spacing,
+                height() - bottom - top };
+}
+
 
+QRect GradientWidget::getHandleRect(int index) const
+{
+    QRect handleArea = getHandleArea();
+    float y = handleArea.top() + points.at(index).first * handleArea.height();
+    return QRect {
+        handleArea.x(), int(y - handleHeight / 2),
+        handleWidth, handleHeight
+    };
+}
+
+
+QRect GradientWidget::getHandleArea(void) const
+{
+    int left, top, right, bottom;
+    getContentsMargins(&left, &top, &right, &bottom);
+    top += handleHeight / 2;
+    bottom += handleHeight / 2;
+    float y = top;
+    float x = width() - handleWidth - right;
+    return QRect {
+        int(x), top, handleWidth, height() - top - bottom
+    };
 }
+
+
+int GradientWidget::handleAtPos(QPoint pos) const
+{
+    for (int i = points.size() - 1; i >= 0; i--) {
+        QRect rect = getHandleRect(i);
+        if (rect.contains(pos)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+float GradientWidget::handleYToGradVal(float y) const
+{
+    QRect area = getHandleArea();
+    return (y - area.top()) / area.height();
+}
+
+
+float GradientWidget::gradValToHandleY(float v) const
+{
+    QRect area = getHandleArea();
+    return area.top() + v * area.height();
+}
+

+ 46 - 1
GradientWidget.h

@@ -2,15 +2,60 @@
 #define GRADIENTWIDGET_H
 
 #include <QWidget>
+#include <QLinearGradient>
+#include <QVector>
+#include <QPair>
 
-class GradientWidget : public QWidget
+class GradientWidget :
+    public QWidget
 {
     Q_OBJECT
+    QVector<QPair<float, QColor>> points;
+
+    bool dragging;
+    int selectedHandle;
+    float selectOffsetY;
+
+    int mouseOver;
+
+    int handleWidth = 40;
+    int handleHeight = 24;
 public:
     explicit GradientWidget(QWidget *parent = nullptr);
 
+    const QVector<QPair<float, QColor>>& getGradient(void) const;
+    void setGradient(QVector<QPair<float, QColor>>);
+
+    QColor colorAtY(float y);
+
+    void paintEvent(QPaintEvent* e) override;
+
+    void mousePressEvent(QMouseEvent* e) override;
+    void mouseReleaseEvent(QMouseEvent* e) override;
+    void mouseMoveEvent(QMouseEvent* e) override;
+    void mouseDoubleClickEvent(QMouseEvent* e) override;
+
+    QSize minimumSizeHint(void) const override;
+    QSize sizeHint(void) const override;
+
+protected:
+    /// \brief the area in which the gradient is displayed
+    QRect getGradientRect(void) const;
+
+    /// \brief the area in which the handle with index
+    /// \c index is displayed
+    QRect getHandleRect(int index) const;
+
+    /// \brief the area in which the handles can move around
+    QRect getHandleArea(void) const;
+
+    int handleAtPos(QPoint pos) const;
+
+    float handleYToGradVal(float y) const;
+    float gradValToHandleY(float v) const;
 signals:
 
 };
 
 #endif // GRADIENTWIDGET_H
+

+ 17 - 0
LICENSE

@@ -0,0 +1,17 @@
+Copyright (c) 2020 Nicolas Winkler
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+   claim that you wrote the original software. If you use this software
+   in a product, an acknowledgment in the product documentation would be
+   appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+   misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.

+ 5 - 9
MandelWidget.cpp

@@ -2,6 +2,9 @@
 #include <cmath>
 #include <sstream>
 
+#include <QStyle>
+#include <QStyleOption>
+
 using namespace mnd;
 
 #include <cstdio>
@@ -48,15 +51,6 @@ Texture::Texture(Texture&& other) :
 }
 
 
-Texture& Texture::operator=(Texture&& other)
-{
-    this->id = other.id;
-    this->gl = other.gl;
-    other.id = 0;
-    return *this;
-}
-
-
 void Texture::bind(void) const
 {
     gl.glBindTexture(GL_TEXTURE_2D, id);
@@ -766,6 +760,8 @@ void MandelWidget::drawRubberband(void)
     rubberbandPainter.setPen(pen);
 
     rubberbandPainter.drawRect(rubberband);
+    //QStyleOption so;
+    //style()->drawControl(QStyle::CE_RubberBand, &so, &rubberbandPainter, this);
 }
 
 

+ 1 - 1
MandelWidget.h

@@ -45,7 +45,7 @@ public:
     Texture& operator=(const Texture& other) = delete;
 
     Texture(Texture&& other);
-    Texture& operator=(Texture&& other);
+    Texture& operator=(Texture&& other) = delete;
 
 private:
     void bind(void) const;

+ 8 - 0
README.md

@@ -0,0 +1,8 @@
+# Almond
+
+Almond is a fast and simple fractal viewer.
+
+## Building
+
+The preferred way of building Almond is using cmake.
+

+ 3 - 0
choosegenerators.cpp

@@ -129,8 +129,11 @@ double Benchmarker::benchmarkResult(mnd::MandelGenerator& mg) const
         else if (time < std::chrono::milliseconds(20)) {
             i += 3;
         }
+        QThread::msleep(1);
     }
 
+    QThread::msleep(10);
+
     try {
         const mnd::MandelInfo& mi = benches[(testIndex >= benches.size()) ? (benches.size() - 1) : testIndex];
         Bitmap<float> bmp(mi.bWidth, mi.bHeight);

+ 112 - 0
debian/copyright

@@ -0,0 +1,112 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: Almond
+Source: https://git.winfor.ch/nicolas/Almond
+
+Files: *
+Copyright: 2019-2020 Nicolas Winkler
+License: Zlib
+
+Files: libmandel/asmjit/*
+Copyright: 2008-2020 The AsmJit Authors
+License: Zlib
+
+Files: libmandel/qd-2.3.22/*
+Copyright: 
+License: BSD-LBNL
+
+Files: libalmond/lpng1637/*
+Copyright: 
+License: libpng
+
+Files: libalmond/zlib-1.2.11/
+Copyright: 1995-2017 Jean-loup Gailly and Mark Adler
+License: Zlib
+
+License: Zlib
+This software is provided 'as-is', without any express or implied warranty. In
+no event will the authors be held liable for any damages arising from the use
+of this software.
+
+Permission is granted to anyone to use this software for any purpose, including
+commercial applications, and to alter it and redistribute it freely, subject to
+the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not claim
+   that you wrote the original software. If you use this software in a product,
+   an acknowledgment in the product documentation would be appreciated but is
+   not required.
+2. Altered source versions must be plainly marked as such, and must not be
+   misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+
+License: BSD-LBNL
+1. Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+(1) Redistributions of source code must retain the copyright notice, this list
+of conditions and the following disclaimer.
+
+(2) Redistributions in binary form must reproduce the copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+(3) Neither the name of the University of California, Lawrence Berkeley
+National Laboratory, U.S. Dept. of Energy nor the names of its contributors may
+be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+3. You are under no obligation whatsoever to provide any bug fixes, patches, or
+upgrades to the features, functionality or performance of the source code
+("Enhancements") to anyone; however, if you choose to make your Enhancements
+available either publicly, or directly to Lawrence Berkeley National
+Laboratory, without imposing a separate written license agreement for such
+Enhancements, then you hereby grant the following license: a non-exclusive,
+royalty-free perpetual license to install, use, modify, prepare derivative
+works, incorporate into other computer software, distribute, and sublicense
+such enhancements or derivative works thereof, in binary and source code form.
+
+License: libpng
+ * Copyright (c) 1995-2019 The PNG Reference Library Authors.
+ * Copyright (c) 2018-2019 Cosmin Truta.
+ * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson.
+ * Copyright (c) 1996-1997 Andreas Dilger.
+ * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
+
+The software is supplied "as is", without warranty of any kind,
+express or implied, including, without limitation, the warranties
+of merchantability, fitness for a particular purpose, title, and
+non-infringement.  In no event shall the Copyright owners, or
+anyone distributing the software, be liable for any damages or
+other liability, whether in contract, tort or otherwise, arising
+from, out of, or in connection with the software, or the use or
+other dealings in the software, even if advised of the possibility
+of such damage.
+
+Permission is hereby granted to use, copy, modify, and distribute
+this software, or portions hereof, for any purpose, without fee,
+subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you
+    must not claim that you wrote the original software.  If you
+    use this software in a product, an acknowledgment in the product
+    documentation would be appreciated, but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must
+    not be misrepresented as being the original software.
+
+ 3. This Copyright notice may not be removed or altered from any
+    source or altered source distribution.
+
+

+ 15 - 2
exportdialogs.cpp

@@ -52,10 +52,23 @@ QString ExportImageDialog::getPath(void) const
 
 void ExportImageDialog::on_pushButton_clicked()
 {
+    std::string formatString = "";
+    if (alm::supportsImageFormat(alm::ImageFormat::PNG)) {
+        formatString += "PNG image (*.png);;";
+    }
+    if (alm::supportsImageFormat(alm::ImageFormat::JPEG)) {
+        formatString += "JPEG image (*.jpg *.jpeg);;";
+    }
+    if (alm::supportsImageFormat(alm::ImageFormat::BMP)) {
+        formatString += "BMP image (*.bmp *.dib);;";
+    }
+    if (!formatString.empty()) {
+        formatString.erase(formatString.end() - 2, formatString.end());
+    }
+
     QString saveAs = QFileDialog::getSaveFileName(this,
             tr("Save exported image"), "",
-            //tr("PNG image (*.png);;JPEG image (*.jpg);;All Files (*)"));
-            tr("PNG image (*.png)"));
+            tr(formatString.c_str()));
     if(!saveAs.isEmpty() && !saveAs.isNull())
         eid.savePath->setText(saveAs);
     this->repaint();

+ 1 - 1
gradientchoosedialog.cpp

@@ -23,7 +23,7 @@ GradientChooseDialog::GradientChooseDialog()
 
 void GradientChooseDialog::on_buttonBox_accepted()
 {
-    chosenGradient = std::make_unique<Gradient>(Gradient::readXml(gcd.plainTextEdit->toPlainText()));
+    //chosenGradient = std::make_unique<Gradient>(Gradient::readXml(gcd.plainTextEdit->toPlainText()));
 }
 
 

+ 0 - 674
installer/packages/almond/meta/gpl3.txt

@@ -1,674 +0,0 @@
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The GNU General Public License is a free, copyleft license for
-software and other kinds of works.
-
-  The licenses for most software and other practical works are designed
-to take away your freedom to share and change the works.  By contrast,
-the GNU General Public License is intended to guarantee your freedom to
-share and change all versions of a program--to make sure it remains free
-software for all its users.  We, the Free Software Foundation, use the
-GNU General Public License for most of our software; it applies also to
-any other work released this way by its authors.  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-them if you wish), that you receive source code or can get it if you
-want it, that you can change the software or use pieces of it in new
-free programs, and that you know you can do these things.
-
-  To protect your rights, we need to prevent others from denying you
-these rights or asking you to surrender the rights.  Therefore, you have
-certain responsibilities if you distribute copies of the software, or if
-you modify it: responsibilities to respect the freedom of others.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must pass on to the recipients the same
-freedoms that you received.  You must make sure that they, too, receive
-or can get the source code.  And you must show them these terms so they
-know their rights.
-
-  Developers that use the GNU GPL protect your rights with two steps:
-(1) assert copyright on the software, and (2) offer you this License
-giving you legal permission to copy, distribute and/or modify it.
-
-  For the developers' and authors' protection, the GPL clearly explains
-that there is no warranty for this free software.  For both users' and
-authors' sake, the GPL requires that modified versions be marked as
-changed, so that their problems will not be attributed erroneously to
-authors of previous versions.
-
-  Some devices are designed to deny users access to install or run
-modified versions of the software inside them, although the manufacturer
-can do so.  This is fundamentally incompatible with the aim of
-protecting users' freedom to change the software.  The systematic
-pattern of such abuse occurs in the area of products for individuals to
-use, which is precisely where it is most unacceptable.  Therefore, we
-have designed this version of the GPL to prohibit the practice for those
-products.  If such problems arise substantially in other domains, we
-stand ready to extend this provision to those domains in future versions
-of the GPL, as needed to protect the freedom of users.
-
-  Finally, every program is threatened constantly by software patents.
-States should not allow patents to restrict development and use of
-software on general-purpose computers, but in those that do, we wish to
-avoid the special danger that patents applied to a free program could
-make it effectively proprietary.  To prevent this, the GPL assures that
-patents cannot be used to render the program non-free.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                       TERMS AND CONDITIONS
-
-  0. Definitions.
-
-  "This License" refers to version 3 of the GNU General Public License.
-
-  "Copyright" also means copyright-like laws that apply to other kinds of
-works, such as semiconductor masks.
-
-  "The Program" refers to any copyrightable work licensed under this
-License.  Each licensee is addressed as "you".  "Licensees" and
-"recipients" may be individuals or organizations.
-
-  To "modify" a work means to copy from or adapt all or part of the work
-in a fashion requiring copyright permission, other than the making of an
-exact copy.  The resulting work is called a "modified version" of the
-earlier work or a work "based on" the earlier work.
-
-  A "covered work" means either the unmodified Program or a work based
-on the Program.
-
-  To "propagate" a work means to do anything with it that, without
-permission, would make you directly or secondarily liable for
-infringement under applicable copyright law, except executing it on a
-computer or modifying a private copy.  Propagation includes copying,
-distribution (with or without modification), making available to the
-public, and in some countries other activities as well.
-
-  To "convey" a work means any kind of propagation that enables other
-parties to make or receive copies.  Mere interaction with a user through
-a computer network, with no transfer of a copy, is not conveying.
-
-  An interactive user interface displays "Appropriate Legal Notices"
-to the extent that it includes a convenient and prominently visible
-feature that (1) displays an appropriate copyright notice, and (2)
-tells the user that there is no warranty for the work (except to the
-extent that warranties are provided), that licensees may convey the
-work under this License, and how to view a copy of this License.  If
-the interface presents a list of user commands or options, such as a
-menu, a prominent item in the list meets this criterion.
-
-  1. Source Code.
-
-  The "source code" for a work means the preferred form of the work
-for making modifications to it.  "Object code" means any non-source
-form of a work.
-
-  A "Standard Interface" means an interface that either is an official
-standard defined by a recognized standards body, or, in the case of
-interfaces specified for a particular programming language, one that
-is widely used among developers working in that language.
-
-  The "System Libraries" of an executable work include anything, other
-than the work as a whole, that (a) is included in the normal form of
-packaging a Major Component, but which is not part of that Major
-Component, and (b) serves only to enable use of the work with that
-Major Component, or to implement a Standard Interface for which an
-implementation is available to the public in source code form.  A
-"Major Component", in this context, means a major essential component
-(kernel, window system, and so on) of the specific operating system
-(if any) on which the executable work runs, or a compiler used to
-produce the work, or an object code interpreter used to run it.
-
-  The "Corresponding Source" for a work in object code form means all
-the source code needed to generate, install, and (for an executable
-work) run the object code and to modify the work, including scripts to
-control those activities.  However, it does not include the work's
-System Libraries, or general-purpose tools or generally available free
-programs which are used unmodified in performing those activities but
-which are not part of the work.  For example, Corresponding Source
-includes interface definition files associated with source files for
-the work, and the source code for shared libraries and dynamically
-linked subprograms that the work is specifically designed to require,
-such as by intimate data communication or control flow between those
-subprograms and other parts of the work.
-
-  The Corresponding Source need not include anything that users
-can regenerate automatically from other parts of the Corresponding
-Source.
-
-  The Corresponding Source for a work in source code form is that
-same work.
-
-  2. Basic Permissions.
-
-  All rights granted under this License are granted for the term of
-copyright on the Program, and are irrevocable provided the stated
-conditions are met.  This License explicitly affirms your unlimited
-permission to run the unmodified Program.  The output from running a
-covered work is covered by this License only if the output, given its
-content, constitutes a covered work.  This License acknowledges your
-rights of fair use or other equivalent, as provided by copyright law.
-
-  You may make, run and propagate covered works that you do not
-convey, without conditions so long as your license otherwise remains
-in force.  You may convey covered works to others for the sole purpose
-of having them make modifications exclusively for you, or provide you
-with facilities for running those works, provided that you comply with
-the terms of this License in conveying all material for which you do
-not control copyright.  Those thus making or running the covered works
-for you must do so exclusively on your behalf, under your direction
-and control, on terms that prohibit them from making any copies of
-your copyrighted material outside their relationship with you.
-
-  Conveying under any other circumstances is permitted solely under
-the conditions stated below.  Sublicensing is not allowed; section 10
-makes it unnecessary.
-
-  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
-
-  No covered work shall be deemed part of an effective technological
-measure under any applicable law fulfilling obligations under article
-11 of the WIPO copyright treaty adopted on 20 December 1996, or
-similar laws prohibiting or restricting circumvention of such
-measures.
-
-  When you convey a covered work, you waive any legal power to forbid
-circumvention of technological measures to the extent such circumvention
-is effected by exercising rights under this License with respect to
-the covered work, and you disclaim any intention to limit operation or
-modification of the work as a means of enforcing, against the work's
-users, your or third parties' legal rights to forbid circumvention of
-technological measures.
-
-  4. Conveying Verbatim Copies.
-
-  You may convey verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice;
-keep intact all notices stating that this License and any
-non-permissive terms added in accord with section 7 apply to the code;
-keep intact all notices of the absence of any warranty; and give all
-recipients a copy of this License along with the Program.
-
-  You may charge any price or no price for each copy that you convey,
-and you may offer support or warranty protection for a fee.
-
-  5. Conveying Modified Source Versions.
-
-  You may convey a work based on the Program, or the modifications to
-produce it from the Program, in the form of source code under the
-terms of section 4, provided that you also meet all of these conditions:
-
-    a) The work must carry prominent notices stating that you modified
-    it, and giving a relevant date.
-
-    b) The work must carry prominent notices stating that it is
-    released under this License and any conditions added under section
-    7.  This requirement modifies the requirement in section 4 to
-    "keep intact all notices".
-
-    c) You must license the entire work, as a whole, under this
-    License to anyone who comes into possession of a copy.  This
-    License will therefore apply, along with any applicable section 7
-    additional terms, to the whole of the work, and all its parts,
-    regardless of how they are packaged.  This License gives no
-    permission to license the work in any other way, but it does not
-    invalidate such permission if you have separately received it.
-
-    d) If the work has interactive user interfaces, each must display
-    Appropriate Legal Notices; however, if the Program has interactive
-    interfaces that do not display Appropriate Legal Notices, your
-    work need not make them do so.
-
-  A compilation of a covered work with other separate and independent
-works, which are not by their nature extensions of the covered work,
-and which are not combined with it such as to form a larger program,
-in or on a volume of a storage or distribution medium, is called an
-"aggregate" if the compilation and its resulting copyright are not
-used to limit the access or legal rights of the compilation's users
-beyond what the individual works permit.  Inclusion of a covered work
-in an aggregate does not cause this License to apply to the other
-parts of the aggregate.
-
-  6. Conveying Non-Source Forms.
-
-  You may convey a covered work in object code form under the terms
-of sections 4 and 5, provided that you also convey the
-machine-readable Corresponding Source under the terms of this License,
-in one of these ways:
-
-    a) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by the
-    Corresponding Source fixed on a durable physical medium
-    customarily used for software interchange.
-
-    b) Convey the object code in, or embodied in, a physical product
-    (including a physical distribution medium), accompanied by a
-    written offer, valid for at least three years and valid for as
-    long as you offer spare parts or customer support for that product
-    model, to give anyone who possesses the object code either (1) a
-    copy of the Corresponding Source for all the software in the
-    product that is covered by this License, on a durable physical
-    medium customarily used for software interchange, for a price no
-    more than your reasonable cost of physically performing this
-    conveying of source, or (2) access to copy the
-    Corresponding Source from a network server at no charge.
-
-    c) Convey individual copies of the object code with a copy of the
-    written offer to provide the Corresponding Source.  This
-    alternative is allowed only occasionally and noncommercially, and
-    only if you received the object code with such an offer, in accord
-    with subsection 6b.
-
-    d) Convey the object code by offering access from a designated
-    place (gratis or for a charge), and offer equivalent access to the
-    Corresponding Source in the same way through the same place at no
-    further charge.  You need not require recipients to copy the
-    Corresponding Source along with the object code.  If the place to
-    copy the object code is a network server, the Corresponding Source
-    may be on a different server (operated by you or a third party)
-    that supports equivalent copying facilities, provided you maintain
-    clear directions next to the object code saying where to find the
-    Corresponding Source.  Regardless of what server hosts the
-    Corresponding Source, you remain obligated to ensure that it is
-    available for as long as needed to satisfy these requirements.
-
-    e) Convey the object code using peer-to-peer transmission, provided
-    you inform other peers where the object code and Corresponding
-    Source of the work are being offered to the general public at no
-    charge under subsection 6d.
-
-  A separable portion of the object code, whose source code is excluded
-from the Corresponding Source as a System Library, need not be
-included in conveying the object code work.
-
-  A "User Product" is either (1) a "consumer product", which means any
-tangible personal property which is normally used for personal, family,
-or household purposes, or (2) anything designed or sold for incorporation
-into a dwelling.  In determining whether a product is a consumer product,
-doubtful cases shall be resolved in favor of coverage.  For a particular
-product received by a particular user, "normally used" refers to a
-typical or common use of that class of product, regardless of the status
-of the particular user or of the way in which the particular user
-actually uses, or expects or is expected to use, the product.  A product
-is a consumer product regardless of whether the product has substantial
-commercial, industrial or non-consumer uses, unless such uses represent
-the only significant mode of use of the product.
-
-  "Installation Information" for a User Product means any methods,
-procedures, authorization keys, or other information required to install
-and execute modified versions of a covered work in that User Product from
-a modified version of its Corresponding Source.  The information must
-suffice to ensure that the continued functioning of the modified object
-code is in no case prevented or interfered with solely because
-modification has been made.
-
-  If you convey an object code work under this section in, or with, or
-specifically for use in, a User Product, and the conveying occurs as
-part of a transaction in which the right of possession and use of the
-User Product is transferred to the recipient in perpetuity or for a
-fixed term (regardless of how the transaction is characterized), the
-Corresponding Source conveyed under this section must be accompanied
-by the Installation Information.  But this requirement does not apply
-if neither you nor any third party retains the ability to install
-modified object code on the User Product (for example, the work has
-been installed in ROM).
-
-  The requirement to provide Installation Information does not include a
-requirement to continue to provide support service, warranty, or updates
-for a work that has been modified or installed by the recipient, or for
-the User Product in which it has been modified or installed.  Access to a
-network may be denied when the modification itself materially and
-adversely affects the operation of the network or violates the rules and
-protocols for communication across the network.
-
-  Corresponding Source conveyed, and Installation Information provided,
-in accord with this section must be in a format that is publicly
-documented (and with an implementation available to the public in
-source code form), and must require no special password or key for
-unpacking, reading or copying.
-
-  7. Additional Terms.
-
-  "Additional permissions" are terms that supplement the terms of this
-License by making exceptions from one or more of its conditions.
-Additional permissions that are applicable to the entire Program shall
-be treated as though they were included in this License, to the extent
-that they are valid under applicable law.  If additional permissions
-apply only to part of the Program, that part may be used separately
-under those permissions, but the entire Program remains governed by
-this License without regard to the additional permissions.
-
-  When you convey a copy of a covered work, you may at your option
-remove any additional permissions from that copy, or from any part of
-it.  (Additional permissions may be written to require their own
-removal in certain cases when you modify the work.)  You may place
-additional permissions on material, added by you to a covered work,
-for which you have or can give appropriate copyright permission.
-
-  Notwithstanding any other provision of this License, for material you
-add to a covered work, you may (if authorized by the copyright holders of
-that material) supplement the terms of this License with terms:
-
-    a) Disclaiming warranty or limiting liability differently from the
-    terms of sections 15 and 16 of this License; or
-
-    b) Requiring preservation of specified reasonable legal notices or
-    author attributions in that material or in the Appropriate Legal
-    Notices displayed by works containing it; or
-
-    c) Prohibiting misrepresentation of the origin of that material, or
-    requiring that modified versions of such material be marked in
-    reasonable ways as different from the original version; or
-
-    d) Limiting the use for publicity purposes of names of licensors or
-    authors of the material; or
-
-    e) Declining to grant rights under trademark law for use of some
-    trade names, trademarks, or service marks; or
-
-    f) Requiring indemnification of licensors and authors of that
-    material by anyone who conveys the material (or modified versions of
-    it) with contractual assumptions of liability to the recipient, for
-    any liability that these contractual assumptions directly impose on
-    those licensors and authors.
-
-  All other non-permissive additional terms are considered "further
-restrictions" within the meaning of section 10.  If the Program as you
-received it, or any part of it, contains a notice stating that it is
-governed by this License along with a term that is a further
-restriction, you may remove that term.  If a license document contains
-a further restriction but permits relicensing or conveying under this
-License, you may add to a covered work material governed by the terms
-of that license document, provided that the further restriction does
-not survive such relicensing or conveying.
-
-  If you add terms to a covered work in accord with this section, you
-must place, in the relevant source files, a statement of the
-additional terms that apply to those files, or a notice indicating
-where to find the applicable terms.
-
-  Additional terms, permissive or non-permissive, may be stated in the
-form of a separately written license, or stated as exceptions;
-the above requirements apply either way.
-
-  8. Termination.
-
-  You may not propagate or modify a covered work except as expressly
-provided under this License.  Any attempt otherwise to propagate or
-modify it is void, and will automatically terminate your rights under
-this License (including any patent licenses granted under the third
-paragraph of section 11).
-
-  However, if you cease all violation of this License, then your
-license from a particular copyright holder is reinstated (a)
-provisionally, unless and until the copyright holder explicitly and
-finally terminates your license, and (b) permanently, if the copyright
-holder fails to notify you of the violation by some reasonable means
-prior to 60 days after the cessation.
-
-  Moreover, your license from a particular copyright holder is
-reinstated permanently if the copyright holder notifies you of the
-violation by some reasonable means, this is the first time you have
-received notice of violation of this License (for any work) from that
-copyright holder, and you cure the violation prior to 30 days after
-your receipt of the notice.
-
-  Termination of your rights under this section does not terminate the
-licenses of parties who have received copies or rights from you under
-this License.  If your rights have been terminated and not permanently
-reinstated, you do not qualify to receive new licenses for the same
-material under section 10.
-
-  9. Acceptance Not Required for Having Copies.
-
-  You are not required to accept this License in order to receive or
-run a copy of the Program.  Ancillary propagation of a covered work
-occurring solely as a consequence of using peer-to-peer transmission
-to receive a copy likewise does not require acceptance.  However,
-nothing other than this License grants you permission to propagate or
-modify any covered work.  These actions infringe copyright if you do
-not accept this License.  Therefore, by modifying or propagating a
-covered work, you indicate your acceptance of this License to do so.
-
-  10. Automatic Licensing of Downstream Recipients.
-
-  Each time you convey a covered work, the recipient automatically
-receives a license from the original licensors, to run, modify and
-propagate that work, subject to this License.  You are not responsible
-for enforcing compliance by third parties with this License.
-
-  An "entity transaction" is a transaction transferring control of an
-organization, or substantially all assets of one, or subdividing an
-organization, or merging organizations.  If propagation of a covered
-work results from an entity transaction, each party to that
-transaction who receives a copy of the work also receives whatever
-licenses to the work the party's predecessor in interest had or could
-give under the previous paragraph, plus a right to possession of the
-Corresponding Source of the work from the predecessor in interest, if
-the predecessor has it or can get it with reasonable efforts.
-
-  You may not impose any further restrictions on the exercise of the
-rights granted or affirmed under this License.  For example, you may
-not impose a license fee, royalty, or other charge for exercise of
-rights granted under this License, and you may not initiate litigation
-(including a cross-claim or counterclaim in a lawsuit) alleging that
-any patent claim is infringed by making, using, selling, offering for
-sale, or importing the Program or any portion of it.
-
-  11. Patents.
-
-  A "contributor" is a copyright holder who authorizes use under this
-License of the Program or a work on which the Program is based.  The
-work thus licensed is called the contributor's "contributor version".
-
-  A contributor's "essential patent claims" are all patent claims
-owned or controlled by the contributor, whether already acquired or
-hereafter acquired, that would be infringed by some manner, permitted
-by this License, of making, using, or selling its contributor version,
-but do not include claims that would be infringed only as a
-consequence of further modification of the contributor version.  For
-purposes of this definition, "control" includes the right to grant
-patent sublicenses in a manner consistent with the requirements of
-this License.
-
-  Each contributor grants you a non-exclusive, worldwide, royalty-free
-patent license under the contributor's essential patent claims, to
-make, use, sell, offer for sale, import and otherwise run, modify and
-propagate the contents of its contributor version.
-
-  In the following three paragraphs, a "patent license" is any express
-agreement or commitment, however denominated, not to enforce a patent
-(such as an express permission to practice a patent or covenant not to
-sue for patent infringement).  To "grant" such a patent license to a
-party means to make such an agreement or commitment not to enforce a
-patent against the party.
-
-  If you convey a covered work, knowingly relying on a patent license,
-and the Corresponding Source of the work is not available for anyone
-to copy, free of charge and under the terms of this License, through a
-publicly available network server or other readily accessible means,
-then you must either (1) cause the Corresponding Source to be so
-available, or (2) arrange to deprive yourself of the benefit of the
-patent license for this particular work, or (3) arrange, in a manner
-consistent with the requirements of this License, to extend the patent
-license to downstream recipients.  "Knowingly relying" means you have
-actual knowledge that, but for the patent license, your conveying the
-covered work in a country, or your recipient's use of the covered work
-in a country, would infringe one or more identifiable patents in that
-country that you have reason to believe are valid.
-
-  If, pursuant to or in connection with a single transaction or
-arrangement, you convey, or propagate by procuring conveyance of, a
-covered work, and grant a patent license to some of the parties
-receiving the covered work authorizing them to use, propagate, modify
-or convey a specific copy of the covered work, then the patent license
-you grant is automatically extended to all recipients of the covered
-work and works based on it.
-
-  A patent license is "discriminatory" if it does not include within
-the scope of its coverage, prohibits the exercise of, or is
-conditioned on the non-exercise of one or more of the rights that are
-specifically granted under this License.  You may not convey a covered
-work if you are a party to an arrangement with a third party that is
-in the business of distributing software, under which you make payment
-to the third party based on the extent of your activity of conveying
-the work, and under which the third party grants, to any of the
-parties who would receive the covered work from you, a discriminatory
-patent license (a) in connection with copies of the covered work
-conveyed by you (or copies made from those copies), or (b) primarily
-for and in connection with specific products or compilations that
-contain the covered work, unless you entered into that arrangement,
-or that patent license was granted, prior to 28 March 2007.
-
-  Nothing in this License shall be construed as excluding or limiting
-any implied license or other defenses to infringement that may
-otherwise be available to you under applicable patent law.
-
-  12. No Surrender of Others' Freedom.
-
-  If conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot convey a
-covered work so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may
-not convey it at all.  For example, if you agree to terms that obligate you
-to collect a royalty for further conveying from those to whom you convey
-the Program, the only way you could satisfy both those terms and this
-License would be to refrain entirely from conveying the Program.
-
-  13. Use with the GNU Affero General Public License.
-
-  Notwithstanding any other provision of this License, you have
-permission to link or combine any covered work with a work licensed
-under version 3 of the GNU Affero General Public License into a single
-combined work, and to convey the resulting work.  The terms of this
-License will continue to apply to the part which is the covered work,
-but the special requirements of the GNU Affero General Public License,
-section 13, concerning interaction through a network will apply to the
-combination as such.
-
-  14. Revised Versions of this License.
-
-  The Free Software Foundation may publish revised and/or new versions of
-the GNU General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-  Each version is given a distinguishing version number.  If the
-Program specifies that a certain numbered version of the GNU General
-Public License "or any later version" applies to it, you have the
-option of following the terms and conditions either of that numbered
-version or of any later version published by the Free Software
-Foundation.  If the Program does not specify a version number of the
-GNU General Public License, you may choose any version ever published
-by the Free Software Foundation.
-
-  If the Program specifies that a proxy can decide which future
-versions of the GNU General Public License can be used, that proxy's
-public statement of acceptance of a version permanently authorizes you
-to choose that version for the Program.
-
-  Later license versions may give you additional or different
-permissions.  However, no additional obligations are imposed on any
-author or copyright holder as a result of your choosing to follow a
-later version.
-
-  15. Disclaimer of Warranty.
-
-  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
-IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. Limitation of Liability.
-
-  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGES.
-
-  17. Interpretation of Sections 15 and 16.
-
-  If the disclaimer of warranty and limitation of liability provided
-above cannot be given local legal effect according to their terms,
-reviewing courts shall apply local law that most closely approximates
-an absolute waiver of all civil liability in connection with the
-Program, unless a warranty or assumption of liability accompanies a
-copy of the Program in return for a fee.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-state the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-Also add information on how to contact you by electronic and paper mail.
-
-  If the program does terminal interaction, make it output a short
-notice like this when it starts in an interactive mode:
-
-    <program>  Copyright (C) <year>  <name of author>
-    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, your program's commands
-might be different; for a GUI interface, you would use an "about box".
-
-  You should also get your employer (if you work as a programmer) or school,
-if any, to sign a "copyright disclaimer" for the program, if necessary.
-For more information on this, and how to apply and follow the GNU GPL, see
-<https://www.gnu.org/licenses/>.
-
-  The GNU General Public License does not permit incorporating your program
-into proprietary programs.  If your program is a subroutine library, you
-may consider it more useful to permit linking proprietary applications with
-the library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.  But first, please read
-<https://www.gnu.org/licenses/why-not-lgpl.html>.

+ 1 - 1
installer/packages/almond/meta/package.xml

@@ -5,7 +5,7 @@
     <Version>1.0.0</Version>
     <ReleaseDate>2020-05-04</ReleaseDate>
     <Licenses>
-        <License name="GNU General Public License 3" file="gpl3.txt" />
+        <License name="zlib/libpng License" file="zlib.txt" />
     </Licenses>
     <Default>true</Default>
     <!--<Default>script</Default>

+ 17 - 0
installer/packages/almond/meta/zlib.txt

@@ -0,0 +1,17 @@
+Copyright (c) 2020 Nicolas Winkler
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+   claim that you wrote the original software. If you use this software
+   in a product, an acknowledgment in the product documentation would be
+   appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+   misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.

+ 17 - 4
libalmond/CMakeLists.txt

@@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.13)
 
 project(libalmond VERSION 1.0.0 DESCRIPTION "almond functionality")
 
+option(LIBALMOND_LIBJPEG "use libjpeg to export jpeg images" ON)
+option(LIBALMOND_LIBPNG "use libpng to export png images" ON)
+
 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../CMakeModules)
 
 find_package(FFmpeg COMPONENTS AVCODEC AVDEVICE AVFORMAT AVUTIL SWSCALE REQUIRED)
@@ -12,6 +15,7 @@ add_subdirectory(../libmandel ./libmandel)
 set(Boost_USE_STATIC_LIBS ON)
 find_package(Boost 1.65 REQUIRED)
 find_package(PNG)
+find_package (JPEG)
 
 set(CMAKE_CXX_STANDARD 17)
 
@@ -27,10 +31,10 @@ set_target_properties(libalmond PROPERTIES OUTPUT_NAME almond)
 target_include_directories(libalmond SYSTEM PUBLIC ${FFMPEG_INCLUDE_DIRS})
 target_link_libraries(libalmond PUBLIC ${FFMPEG_LIBRARIES})
 
-if (PNG_FOUND)
-    target_include_directories(libalmond SYSTEM PUBLIC ${PNG_INCLUDE_DIRS})
-    target_link_libraries(libalmond PUBLIC ${PNG_LIBRARIES})
-else()
+if (PNG_FOUND AND LIBALMOND_LIBPNG)
+    target_link_libraries(libalmond PUBLIC PNG::PNG)
+    target_compile_definitions(libalmond PUBLIC WITH_LIBPNG)
+elseif(LIBALMOND_LIBPNG)
     set(PNG_BUILD_ZLIB ON CACHE BOOL "build zlib ourselves")
     add_subdirectory(zlib-1.2.11)
     set(ZLIB_LIBRARY zlibstatic)
@@ -40,13 +44,22 @@ else()
     endforeach()
     set(ZLIB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zlib-1.2.11 ${ZLIB_PUB_INCLUDE} )
     
+    set(SKIP_INSTALL_ALL ON)
     add_subdirectory(lpng1637)
     foreach(header ${libpng_public_hdrs})
         get_filename_component(the_incluude ${header} DIRECTORY)
         list(APPEND PNG_PUB_INCLUDE ${the_incluude})
     endforeach()
+    #target_link_libraries(png_static PRIVATE zlibstatic)
     target_include_directories(libalmond PRIVATE ${PNG_PUB_INCLUDE})
     target_include_directories(libalmond PRIVATE ${ZLIB_INCLUDE_DIR})
     target_link_libraries(libalmond PRIVATE png_static)
+    target_compile_definitions(libalmond PUBLIC WITH_LIBPNG)
+endif()
+
+if (JPEG_FOUND AND LIBALMOND_LIBJPEG)
+    target_link_libraries(libalmond PUBLIC JPEG::JPEG)
+    target_compile_definitions(libalmond PUBLIC WITH_LIBJPEG)
 endif()
 
+

+ 1 - 1
libalmond/include/CubicSpline.h

@@ -11,7 +11,7 @@ class CubicSpline
     std::vector<std::tuple<float, float, float>> points;
     bool useSlopes;
 public:
-    CubicSpline(const std::vector<std::pair<float, float>>& dataPoints, bool useSlopes);
+    CubicSpline(const std::vector<std::pair<float, float>>& dataPoints, bool useSlopes, bool minSlopes);
 
     float interpolateAt(float x);
 };

+ 3 - 0
libalmond/include/Gradient.h

@@ -10,6 +10,7 @@
 
 class Gradient
 {
+    std::vector<std::pair<RGBColor, float>> points;
     /// the colors of this gradient stored in linear RGB format
     /// so they can be easily interpolated
     std::vector<RGBColorf> colors;
@@ -19,6 +20,8 @@ public:
     Gradient(void);
     Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat = false, int precalcSteps = -1);
 
+    const std::vector<std::pair<RGBColor, float>>& getPoints(void) const;
+
     static Gradient defaultGradient(void);
 
     //static Gradient readXml(const std::string& xml);

+ 21 - 3
libalmond/include/ImageExport.h

@@ -4,15 +4,30 @@
 #include "Mandel.h"
 #include "Gradient.h"
 #include <functional>
+#include <vector>
 #include <stdexcept>
 
 namespace alm
 {
+    enum class ImageFormat
+    {
+        BMP,
+        PNG,
+        JPEG
+    };
+    bool supportsImageFormat(ImageFormat imgf);
+
+    struct ImageOptions
+    {
+        int jpegQuality = 80;
+    };
+
     struct ImageExportInfo
     {
         mnd::MandelInfo drawInfo;
         mnd::MandelGenerator* generator;
         Gradient gradient;
+        ImageOptions options;
         std::string path;
     };
 
@@ -23,15 +38,18 @@ namespace alm
     };
 
     /**
-     * \brief generates and saves a fractal image in png format.
+     * \brief generates and saves a fractal image. The format
+     *        will be guessed by the file extension
      * 
      * \param iei               info to generate the image
      * \param progressCallback  optional function that is called to
      *                          report progress; the float parameter
      *                          contains a value from 0 to 100
      */
-    void exportPng(const ImageExportInfo& iei,
-        std::function<void(float)> progressCallback = [](float){});
+    void exportImage(const ImageExportInfo& iei,
+        std::function<void(float)> progressCallback = [](float){},
+        std::function<bool(void)> cancelCallback = [](void){ return false; }
+        );
 }
 
 

+ 12 - 4
libalmond/src/CubicSpline.cpp

@@ -1,6 +1,7 @@
 #include "CubicSpline.h"
+#include <cmath>
 
-CubicSpline::CubicSpline(const std::vector<std::pair<float, float> >& dataPoints, bool useSlopes) :
+CubicSpline::CubicSpline(const std::vector<std::pair<float, float> >& dataPoints, bool useSlopes, bool minSlopes) :
     useSlopes{ useSlopes }
 {
     if (dataPoints.size() < 2) {
@@ -24,8 +25,15 @@ CubicSpline::CubicSpline(const std::vector<std::pair<float, float> >& dataPoints
         float s1 = h1 / w1;
         float s2 = h2 / w2;
 
-        float avgSlope = (s1 + s2) / 2;
-        points.push_back({ dp2.first, dp2.second, avgSlope });
+        float slope;
+        if (minSlopes) {
+            if (fabs(s1) > fabs(s2))
+                slope = s2;
+            else
+                slope = s1;
+        } else
+            slope = (s1 + s2) / 2;
+        points.push_back({ dp2.first, dp2.second, slope });
     }
     points.push_back({ dataPoints[dataPoints.size() - 1].first, dataPoints[dataPoints.size() - 1].second,
                        (dataPoints[dataPoints.size() - 2].second - dataPoints[dataPoints.size() - 1].second) /
@@ -44,7 +52,7 @@ float CubicSpline::interpolateAt(float x)
         auto& right = *(it + 1);
         float xleft = std::get<0>(left);
         float xright = std::get<0>(right);
-        if (xleft < x && xright >= x) {
+        if (xleft <= x && xright >= x) {
             float w = (xright - xleft);
             float t = (x - xleft) / w;
             float yleft = std::get<1>(left);

+ 10 - 3
libalmond/src/Gradient.cpp

@@ -23,6 +23,7 @@ Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat,
             return a.second < b.second;
         });
 
+    points = colors;
     max = colors.at(colors.size() - 1).second;
 
     std::vector<std::pair<RGBColorf, float>> linearColors;
@@ -40,9 +41,9 @@ Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat,
     std::transform(linearColors.begin(), linearColors.end(), std::back_inserter(bs),
                    [] (auto p) { return std::pair{ p.second, p.first.b }; });
 
-    CubicSpline rsp(rs, false);
-    CubicSpline gsp(gs, false);
-    CubicSpline bsp(bs, false);
+    CubicSpline rsp(rs, true, true);
+    CubicSpline gsp(gs, true, true);
+    CubicSpline bsp(bs, true, true);
 
     if(precalcSteps <= 0) {
         precalcSteps = int(max * 15) + 10;
@@ -60,6 +61,12 @@ Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat,
 }
 
 
+const std::vector<std::pair<RGBColor, float>>& Gradient::getPoints(void) const
+{
+    return points;
+}
+
+
 Gradient Gradient::defaultGradient(void)
 {
     /*QFile res(":/gradients/default");

+ 313 - 2
libalmond/src/ImageExport.cpp

@@ -1,17 +1,100 @@
 #include "ImageExport.h"
 #include "Bitmap.h"
-#include <png.h>
+#include <boost/endian/buffers.hpp>
 #include <cstdio>
 
+#ifdef WITH_LIBPNG
+extern "C" {
+#   include <png.h>
+}
+#endif // WITH_LIBPNG
+
+#ifdef WITH_LIBJPEG
+extern "C" {
+#   include <jpeglib.h>
+}
+#endif // WITH_LIBJPEG
+
+
 namespace alm
 {
 
+#ifdef WITH_LIBPNG
+void exportPng(const ImageExportInfo& iei,
+    std::function<void(float)> progressCallback,
+    std::function<bool(void)> cancelCallback);
+#endif // WITH_LIBPNG
+
+#ifdef WITH_LIBJPEG
+void exportJpeg(const ImageExportInfo& iei,
+    std::function<void(float)> progressCallback,
+    std::function<bool(void)> cancelCallback);
+#endif // WITH_LIBJPEG
+
+void exportBmp(const ImageExportInfo& iei,
+    std::function<void(float)> progressCallback,
+    std::function<bool(void)> cancelCallback);
+
+
+const std::vector<ImageFormat> supportedImageFormats = {
+    ImageFormat::BMP,
+#ifdef WITH_LIBPNG
+    ImageFormat::PNG,
+#endif // WITH_LIBPNG
+#ifdef WITH_LIBJPEG
+    ImageFormat::JPEG,
+#endif // WITH_LIBJPEG
+};
+
+bool supportsImageFormat(ImageFormat imgf)
+{
+    for (auto f : supportedImageFormats) {
+        if (imgf == f)
+            return true;
+    }
+    return false;
+}
+
 ImageExportException::ImageExportException(const std::string& err) :
     std::runtime_error{ err }
 {
 }
 
-void exportPng(const ImageExportInfo& iei, std::function<void(float)> progressCallback)
+void exportImage(const ImageExportInfo& iei,
+                 std::function<void(float)> progressCallback,
+                 std::function<bool(void)> cancelCallback)
+{
+    auto hasSuffix = [] (const std::string& path, const std::string& suffix) -> bool
+    {
+        return path.size() >= suffix.size() &&
+               path.compare(path.size() - suffix.size(),
+                        suffix.size(), suffix) == 0;
+    };
+    if (hasSuffix(iei.path, ".bmp") ||
+        hasSuffix(iei.path, ".dib")) {
+        exportBmp(iei, progressCallback, cancelCallback);
+    }
+#ifdef WITH_LIBPNG
+    else if (hasSuffix(iei.path, ".png")) {
+        exportPng(iei, progressCallback, cancelCallback);
+    }
+#endif // WITH_LIBPNG
+#ifdef WITH_LIBJPEG
+    else if (hasSuffix(iei.path, ".jpg") ||
+             hasSuffix(iei.path, ".jpeg")) {
+        exportJpeg(iei, progressCallback, cancelCallback);
+    }
+#endif // WITH_LIBJPEG
+    else {
+        throw ImageExportException{ "Could not guess image format" };
+    }
+}
+
+
+#ifdef WITH_LIBPNG
+void exportPng(const ImageExportInfo& iei,
+               std::function<void(float)> progressCallback,
+               std::function<bool(void)> cancelCallback)
 {
     if (iei.generator == nullptr) {
         throw "no generator";
@@ -79,6 +162,12 @@ void exportPng(const ImageExportInfo& iei, std::function<void(float)> progressCa
         png_write_rows(png, &rowPointers[0], tmpHeight);
         if (chunkY < height)
             progressCallback(100.0f * chunkY / height);
+
+        if (cancelCallback()) {
+            fclose(file);
+            png_destroy_write_struct(&png, &info);
+            return;
+        }
     }
 
     png_write_end(png, NULL);
@@ -88,8 +177,230 @@ void exportPng(const ImageExportInfo& iei, std::function<void(float)> progressCa
     png_destroy_write_struct(&png, &info);
     progressCallback(100.0f);
 }
+#endif // WITH_LIBPNG
+
+
+#ifdef WITH_LIBJPEG
+void exportJpeg(const ImageExportInfo& iei,
+                std::function<void(float)> progressCallback,
+                std::function<bool(void)> cancelCallback)
+{
+    if (iei.generator == nullptr) {
+        throw "no generator";
+    }
+
+    progressCallback(0.0f);
+
+    mnd::MandelGenerator& generator = *iei.generator;
+    FILE* file = fopen(iei.path.c_str(), "wb");
+    if(!file)
+        throw ImageExportException{ std::string("could not open file '") + iei.path + "'" };
+
+    const long width = iei.drawInfo.bWidth;
+    const long height = iei.drawInfo.bHeight;
+
+    struct jpeg_compress_struct jpegInfo;
+    struct jpeg_error_mgr jerr;
+
+    int rowLength = width * 3;
+
+    jpegInfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = [](j_common_ptr cinfo) {
+        auto errmsg = std::make_unique<char[]>(JMSG_LENGTH_MAX);
+        (cinfo->err->format_message)(cinfo, errmsg.get());
+        throw ImageExportException{ std::string("libjpeg error: ") + errmsg.get() };
+    };
+    jpeg_create_compress(&jpegInfo);
+
+    jpeg_stdio_dest(&jpegInfo, file);
+
+    jpegInfo.image_width = width;
+    jpegInfo.image_height = height;
+    jpegInfo.input_components = 3;
+    jpegInfo.in_color_space = JCS_RGB;
+
+    jpeg_set_defaults(&jpegInfo);
+    jpeg_set_quality(&jpegInfo, iei.options.jpegQuality, TRUE);
+    jpeg_start_compress(&jpegInfo, TRUE);
+
+
+    long chunkHeight = height / 20;
+    if (chunkHeight < 1)
+        chunkHeight = 1;
+    while (width * chunkHeight > 512 * 512)
+        chunkHeight /= 2;
+
+    auto rowPointers = std::make_unique<JSAMPROW[]>(chunkHeight);
+    for (long chunkY = 0; chunkY < height; chunkY += chunkHeight) {
+        auto minimum = [] (const auto& a, const auto& b) { return a < b ? a : b; };
+        long tmpHeight = minimum(chunkHeight, height - chunkY);
+        mnd::MandelInfo chunkInfo = iei.drawInfo;
+        chunkInfo.bHeight = tmpHeight;
+        chunkInfo.view.y += chunkInfo.view.height * chunkY / height;
+        chunkInfo.view.height *= mnd::Real(tmpHeight) / height;
+
+        Bitmap<float> chunk(width, tmpHeight);
+        generator.generate(chunkInfo, chunk.pixels.get());
+        Bitmap<RGBColor> coloredChunk = chunk.map<RGBColor>([&iei] (float i) {
+            return i >= iei.drawInfo.maxIter ? RGBColor{ 0, 0, 0 } : iei.gradient.get(i);
+        });
+        for (long i = 0; i < tmpHeight; i++) {
+            rowPointers[i] = reinterpret_cast<JSAMPROW>(&coloredChunk.get(0, i));
+        }
+        int written = jpeg_write_scanlines(&jpegInfo, rowPointers.get(), tmpHeight);
+        if (written < tmpHeight) {
+            throw ImageExportException{ "error writing jpeg rows" };
+        }
+        if (chunkY < height)
+            progressCallback(100.0f * chunkY / height);
+
+        if (cancelCallback()) {
+            fclose(file);
+            jpeg_destroy_compress(&jpegInfo);
+            return;
+        }
+    }
+
+    jpeg_finish_compress(&jpegInfo);
+    fclose(file);
+    jpeg_destroy_compress(&jpegInfo);
+    progressCallback(100.0f);
+}
+
+#endif // WITH_LIBJPEG
+
+
+void exportBmp(const ImageExportInfo& iei,
+               std::function<void(float)> progressCallback,
+               std::function<bool(void)> cancelCallback)
+{
+    if (iei.generator == nullptr) {
+        throw "no generator";
+    }
+
+    progressCallback(0.0f);
+
+    mnd::MandelGenerator& generator = *iei.generator;
+    FILE* file = fopen(iei.path.c_str(), "wb");
+    if(!file)
+        throw ImageExportException{ std::string("could not open file '") + iei.path + "'" };
+
+    const long width = iei.drawInfo.bWidth;
+    const long height = iei.drawInfo.bHeight;
+
+    // bmp needs rows to be 4-byte-aligned
+    const long rowLength = (width * 3 + 3) & ~0x03;
+
+    using namespace boost::endian;
 
+    const int fileHeaderSize = 14;
+    const int infoHeaderSize = 40;
+    const int pixelSize = rowLength * height;
 
+    // file header
+    little_int8_buf_t fhType[2];
+    little_uint32_buf_t fhSize;
+    little_uint16_buf_t fhReserved1;
+    little_uint16_buf_t fhReserved2;
+    little_uint32_buf_t fhOffset;
+    fhType[0] = 0x42; // 'B'
+    fhType[1] = 0x4D; // 'M'
+    fhSize = fileHeaderSize + infoHeaderSize + pixelSize;
+    fhReserved1 = 0;
+    fhReserved1 = 2;
+    fhOffset = fileHeaderSize + infoHeaderSize;
+
+    // info header
+    little_uint32_buf_t ihSize;
+    little_int32_buf_t ihWidth;
+    little_int32_buf_t ihHeight; // for top-down, height must be negative
+    little_uint16_buf_t ihPlanes;
+    little_uint16_buf_t ihBitCount;
+    little_uint32_buf_t ihCompression; // 0 = no compression
+    little_uint32_buf_t ihSizeImage; // for uncompressed bmps 0 can be set here
+    little_int32_buf_t ihXPixelsPerMeter;
+    little_int32_buf_t ihYPixelsPerMeter;
+    little_uint32_buf_t ihClrUsed;
+    little_uint32_buf_t ihClrImportant;
+    ihSize = infoHeaderSize;
+    ihWidth = width;
+    ihHeight = -height; // for top-down, height must be negative
+    ihPlanes = 1;
+    ihBitCount = 24;
+    ihCompression = 0; // 0 = no compression
+    ihSizeImage = 0; // for uncompressed bmps 0 can be set here
+    ihXPixelsPerMeter = 0;
+    ihYPixelsPerMeter = 0;
+    ihClrUsed = 0;
+    ihClrImportant = 0;
+    
+    auto writeCmp = [&file] (auto& buf) {
+        std::fwrite(&buf, sizeof(buf), 1, file);
+    };
+
+    writeCmp(fhType[0]);
+    writeCmp(fhType[1]);
+    writeCmp(fhSize);
+    writeCmp(fhReserved1);
+    writeCmp(fhReserved2);
+    writeCmp(fhOffset);
+
+    writeCmp(ihSize);
+    writeCmp(ihWidth);
+    writeCmp(ihHeight);
+    writeCmp(ihPlanes);
+    writeCmp(ihBitCount);
+    writeCmp(ihCompression);
+    writeCmp(ihSizeImage);
+    writeCmp(ihXPixelsPerMeter);
+    writeCmp(ihYPixelsPerMeter);
+    writeCmp(ihClrUsed);
+    writeCmp(ihClrImportant);
+
+    long chunkHeight = height / 20;
+    if (chunkHeight < 1)
+        chunkHeight = 1;
+    while (width * chunkHeight > 512 * 512)
+        chunkHeight /= 2;
+
+    for (long chunkY = 0; chunkY < height; chunkY += chunkHeight) {
+        auto minimum = [] (const auto& a, const auto& b) { return a < b ? a : b; };
+        long tmpHeight = minimum(chunkHeight, height - chunkY);
+        mnd::MandelInfo chunkInfo = iei.drawInfo;
+        chunkInfo.bHeight = tmpHeight;
+        chunkInfo.view.y += chunkInfo.view.height * chunkY / height;
+        chunkInfo.view.height *= mnd::Real(tmpHeight) / height;
+
+        Bitmap<float> chunk(width, tmpHeight);
+        generator.generate(chunkInfo, chunk.pixels.get());
+        for (long i = 0; i < tmpHeight; i++) {
+            for (long j = 0; j < width; j++) {
+                float iters = chunk.get(j, i);
+                RGBColor color =
+                    iters >= iei.drawInfo.maxIter ?
+                        RGBColor{ 0, 0, 0 } :
+                        iei.gradient.get(iters);
+                uint8_t bgr[3] = { color.b, color.g, color.r };
+                fwrite(bgr, 1, 3, file);
+            }
+
+            // write line padding
+            const uint8_t zeroes[] = { 0, 0, 0, 0 };
+            fwrite(zeroes, 1, rowLength - width * 3, file);
+        }
+        if (chunkY < height)
+            progressCallback(100.0f * chunkY / height);
+
+        if (cancelCallback()) {
+            fclose(file);
+            return;
+        }
+    }
+
+    fflush(file);
+    fclose(file);
+    progressCallback(100.0f);
+}
 }
 
 

+ 0 - 5
libalmond/src/VideoStream.cpp

@@ -86,11 +86,6 @@ VideoStream::VideoStream(int width, int height, const std::string& filename, int
     if (avformat_write_header(formatContext, nullptr) < 0) {
         throw VideoExportException{ "error writing header" };
     }
-    /*file = fopen(filename.c_str(), "wb");
-    if (!file) {
-        fprintf(stderr, "could not open %s\n", filename.c_str());
-        exit(1);
-    }*/
 
     picture = av_frame_alloc();
     if (!picture)

+ 23 - 20
libmandel/CMakeLists.txt

@@ -2,16 +2,21 @@ cmake_minimum_required(VERSION 3.12)
 
 include(CheckCXXCompilerFlag)
 
-set(ARCH "X86_64" CACHE STRING "Target Architecture")
-option(AVX512 "generate code that can make use of avx-512-instructions" ON)
-option(WITH_ASMJIT "use just-in-time-compilation capabilities of asmjit" ON)
+project(mandel VERSION 1.0.0 DESCRIPTION "library for mandelbrot calculations")
+
+if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64")
+    set(MANDEL_TARGET_ARCHITECTURE "aarch64" CACHE STRING "Target Architecture")
+else()
+    set(MANDEL_TARGET_ARCHITECTURE "x86_64" CACHE STRING "Target Architecture")
+endif()
+option(MANDEL_AVX512 "generate code that can make use of avx-512-instructions" ON)
+option(MANDEL_ASMJIT "use just-in-time-compilation library asmjit" ON)
 option(MANDEL_BUILD_NATIVE
 	"use the -march=native flags if supported WARNING: when compiling with this flag, the binary might not run on machines other than the one it was compiled on"
 	OFF)
 
 #message(CMAKE_SYSTEM_PROCESSOR)
 
-project(mandel VERSION 1.0.0 DESCRIPTION "library for mandelbrot calculations")
 
 find_package(OpenCL REQUIRED)
 find_package(OpenMP)
@@ -42,12 +47,12 @@ SET(MandelSources
 )
 FILE(GLOB MandelHeaders include/*.h)
 
-if (ARCH STREQUAL "X86_64" OR ARCH STREQUAL "X86")
+if (MANDEL_TARGET_ARCHITECTURE STREQUAL "x86_64" OR MANDEL_TARGET_ARCHITECTURE STREQUAL "x86")
     list(APPEND MandelSources src/CpuGeneratorsAVX.cpp src/CpuGeneratorsAVXFMA.cpp src/CpuGeneratorsSSE2.cpp)
-    if (AVX512)
+    if (MANDEL_AVX512)
         list(APPEND MandelSources src/CpuGeneratorsAVX512.cpp)
     endif()
-elseif(ARCH STREQUAL "ARM")
+elseif(MANDEL_TARGET_ARCHITECTURE STREQUAL "aarch64")
     list(APPEND MandelSources src/CpuGeneratorsNeon.cpp)
 endif()
 
@@ -71,27 +76,25 @@ FILE(GLOB QdSources qd-2.3.22/src/*.cpp)
 
 target_compile_definitions(mandel PUBLIC WITH_QD)
 add_library(qd STATIC ${QdSources})
+target_include_directories(mandel PUBLIC "include")
 target_include_directories(qd PUBLIC qd-2.3.22/include qd-2.3.22)
 
 target_link_libraries(mandel PUBLIC qd)
 
-if(WITH_ASMJIT)
-	add_subdirectory(asmjit)
-	target_compile_definitions(mandel PUBLIC WITH_ASMJIT)
-	target_link_libraries(mandel PUBLIC asmjit)
-endif(WITH_ASMJIT)
+if(MANDEL_ASMJIT)
+    add_subdirectory(asmjit)
+    target_compile_definitions(mandel PUBLIC MANDEL_ASMJIT)
+    target_link_libraries(mandel PUBLIC AsmJit::AsmJit)
+endif(MANDEL_ASMJIT)
 
 
 if(OPENCL_FOUND)
     target_compile_definitions(mandel PUBLIC WITH_OPENCL)
-    target_include_directories(mandel SYSTEM PUBLIC
-        ${OpenCL_INCLUDE_DIRS}
-    )
-    target_include_directories(mandel PUBLIC "include")
+    target_include_directories(mandel SYSTEM PUBLIC ${OpenCL_INCLUDE_DIRS})
+    target_include_directories(mandel SYSTEM PUBLIC "include_cl")
     link_directories(${OpenCL_LIBRARY})
     target_link_libraries(mandel PUBLIC OpenCL::OpenCL)
 else(OPENCL_FOUND)
-    include_directories("include")
 endif(OPENCL_FOUND)
 
 if (APPLE AND OpenCL_FOUND)
@@ -109,8 +112,8 @@ if(Boost_FOUND)
     target_link_libraries(mandel PRIVATE ${Boost_LIBRARIES})
 endif(Boost_FOUND)
 
-if (ARCH STREQUAL "X86_64" OR ARCH STREQUAL "X86")
-    if (AVX512)
+if (MANDEL_TARGET_ARCHITECTURE STREQUAL "x86_64" OR MANDEL_TARGET_ARCHITECTURE STREQUAL "x86")
+    if (MANDEL_AVX512)
         target_compile_definitions(mandel PUBLIC WITH_AVX512)
         if (MSVC)
             set_source_files_properties(src/CpuGeneratorsAVX512.cpp PROPERTIES COMPILE_FLAGS /arch:AVX512F)
@@ -129,6 +132,6 @@ if (ARCH STREQUAL "X86_64" OR ARCH STREQUAL "X86")
         set_source_files_properties(src/CpuGeneratorsSSE2.cpp PROPERTIES COMPILE_FLAGS -msse2)
     endif(MSVC)
 
-elseif(ARCH STREQUAL "ARM")
+elseif(MANDEL_TARGET_ARCHITECTURE STREQUAL "aarch64")
     set_source_files_properties(src/CpuGeneratorsNeon.cpp PROPERTIES COMPILE_FLAGS -march=armv8-a+simd)
 endif()

+ 14 - 12
libmandel/asmjit/CMakeLists.txt

@@ -403,18 +403,20 @@ if (NOT ASMJIT_EMBED)
   # Add AsmJit::AsmJit target (alias to asmjit).
   add_library(AsmJit::AsmJit ALIAS asmjit)
 
-  # Install 'asmjit' target (shared or static).
-  install(TARGETS asmjit RUNTIME DESTINATION "bin"
-                         LIBRARY DESTINATION "lib${LIB_SUFFIX}"
-                         ARCHIVE DESTINATION "lib${LIB_SUFFIX}")
-
-  # Install 'asmjit' header files (private headers are filtered out).
-  foreach(_src_file ${ASMJIT_SRC_LIST})
-    if ("${_src_file}" MATCHES "\\.h$" AND NOT "${_src_file}" MATCHES "_p\\.h$")
-      get_filename_component(_src_dir ${_src_file} PATH)
-      install(FILES "${ASMJIT_DIR}/src/${_src_file}" DESTINATION "include/${_src_dir}")
-    endif()
-  endforeach()
+  if (MANDEL_INSTALL_ASMJIT)
+    # Install 'asmjit' target (shared or static).
+    install(TARGETS asmjit RUNTIME DESTINATION "bin"
+                           LIBRARY DESTINATION "lib${LIB_SUFFIX}"
+                           ARCHIVE DESTINATION "lib${LIB_SUFFIX}")
+  
+    # Install 'asmjit' header files (private headers are filtered out).
+    foreach(_src_file ${ASMJIT_SRC_LIST})
+      if ("${_src_file}" MATCHES "\\.h$" AND NOT "${_src_file}" MATCHES "_p\\.h$")
+        get_filename_component(_src_dir ${_src_file} PATH)
+        install(FILES "${ASMJIT_DIR}/src/${_src_file}" DESTINATION "include/${_src_dir}")
+      endif()
+    endforeach()
+  endif(MANDEL_INSTALL_ASMJIT)
 
   # Add 'asmjit' tests.
   if (ASMJIT_TEST)

+ 2 - 1
libmandel/include/Mandel.h

@@ -43,6 +43,7 @@ class mnd::MandelDevice
 private:
     friend class MandelContext;
 
+    std::string platformName;
     std::string vendor;
     std::string name;
     std::string extensions;
@@ -51,7 +52,7 @@ private:
     std::map<GeneratorType, std::unique_ptr<MandelGenerator>> mandelGenerators;
 
 public:
-    MandelDevice(ClDeviceWrapper);
+    MandelDevice(ClDeviceWrapper, const std::string& platformName);
     MandelDevice(const MandelDevice&) = delete;
     MandelDevice(MandelDevice&&) = default;
     MandelDevice& operator=(const MandelDevice&) = delete;

+ 10 - 8
libmandel/include/OpenClCode.h

@@ -1,16 +1,18 @@
 #ifndef MANDEL_OPENCLCODE_H
 #define MANDEL_OPENCLCODE_H
 
+#include <string>
+
 namespace mnd
 {
-    const char* getFloat_cl();
-    const char* getDouble_cl();
-    const char* getDoubleFloat_cl();
-    const char* getDoubleDouble_cl();
-    const char* getQuadDouble_cl();
-    const char* getFixed64_cl();
-    const char* getFixed128_cl();
-    const char* getFixed512_cl();
+    std::string getFloat_cl();
+    std::string getDouble_cl();
+    std::string getDoubleFloat_cl();
+    std::string getDoubleDouble_cl();
+    std::string getQuadDouble_cl();
+    std::string getFixed64_cl();
+    std::string getFixed128_cl();
+    std::string getFixed512_cl();
 }
 
 #endif // MANDEL_OPENCLCODE_H

+ 31 - 8
libmandel/include/Types.h

@@ -84,6 +84,22 @@ namespace mnd
 
     using Real = Float512;
     using Integer = boost::multiprecision::int512_t;
+    /*boost::multiprecision::number<
+        boost::multiprecision::backends::cpp_bin_float<
+            1500, boost::multiprecision::backends::digit_base_2, void, boost::int16_t, -16382, 16383>,
+            boost::multiprecision::et_off>;
+
+    inline Real abs(const Real& x) { return boost::multiprecision::abs(x); }
+    inline Real sqrt(const Real& x) { return boost::multiprecision::sqrt(x); }
+    inline Real floor(const Real& x) { return boost::multiprecision::floor(x); }
+    inline Real log(const Real& x) { return boost::multiprecision::log(x); }
+    inline Real log2(const Real& x) { return boost::multiprecision::log2(x); }
+    inline Real pow(const Real& x, const Real& y) { return boost::multiprecision::pow(x, y); }
+    inline Real atan2(const Real& y, const Real& x) { return boost::multiprecision::atan2(y, x); }
+    inline Real cos(const Real& x) { return boost::multiprecision::cos(x); }
+    inline Real sin(const Real& x) { return boost::multiprecision::sin(x); }
+    inline Real exp(const Real& x) { return boost::multiprecision::exp(x); }
+*/
 #else
     using Real = double;
     using Integer = int64_t;
@@ -148,16 +164,17 @@ namespace mnd
     template<>
     inline DoubleDouble convert<DoubleDouble, Real>(const Real& x)
     {
-        std::string s = x.str();
-        return DoubleDouble(s.c_str());
+        double s = static_cast<double>(x);
+        double e = static_cast<double>(x - s);
+        return DoubleDouble{ s, e };
     }
 
     template<>
     inline LightDoubleDouble convert<LightDoubleDouble, Real>(const Real& x)
     {
-        double upper = static_cast<double>(x);
-        double lower = static_cast<double>(x - upper);
-        return { upper, lower };
+        double s = static_cast<double>(x);
+        double e = static_cast<double>(x - s);
+        return LightDoubleDouble{ s, e };
     }
 
     template<>
@@ -175,8 +192,14 @@ namespace mnd
     template<>
     inline QuadDouble convert<QuadDouble, Real>(const Real& x)
     {
-        std::string s = x.str();
-        return QuadDouble(s.c_str());
+        double s = static_cast<double>(x);
+        Real tmp = x - s;
+        double e1 = static_cast<double>(tmp);
+        tmp = tmp - e1;
+        double e2 = static_cast<double>(tmp);
+        tmp = tmp - e2;
+        double e3 = static_cast<double>(tmp);
+        return QuadDouble{ s, e1, e2, e3 };
     }
 
     template<>
@@ -194,7 +217,7 @@ namespace mnd
     template<>
     inline Fixed64 convert<Fixed64, Real>(const Real& x)
     {
-        return Fixed64(double(x));
+        return static_cast<int64_t>(x * 0xFFFFFFFFFFFFLL);
     }
 
     template<>

+ 0 - 0
libmandel/include/CL/cl.hpp → libmandel/include_cl/CL/cl.hpp


+ 0 - 0
libmandel/include/CL/cl2.hpp → libmandel/include_cl/CL/cl2.hpp


+ 23 - 17
libmandel/src/CpuGeneratorsAVX.cpp

@@ -54,13 +54,9 @@ void CpuGenerator<float, mnd::X86_AVX, parallel>::generate(const mnd::MandelInfo
 
             __m256 counter = _mm256_setzero_ps();
             __m256 adder = _mm256_set1_ps(1);
-            __m256 resultsa = _mm256_setzero_ps();
-            __m256 resultsb = _mm256_setzero_ps();
 
             __m256 counter2 = _mm256_setzero_ps();
             __m256 adder2 = _mm256_set1_ps(1);
-            __m256 resultsa2 = _mm256_setzero_ps();
-            __m256 resultsb2 = _mm256_setzero_ps();
 
             __m256 threshold = _mm256_set1_ps(16);
 
@@ -73,7 +69,14 @@ void CpuGenerator<float, mnd::X86_AVX, parallel>::generate(const mnd::MandelInfo
             __m256 cx2 = info.julia ? juliaX : xs2;
             __m256 cy = info.julia ? juliaY : ys;
 
+            __m256 resultsa = a;
+            __m256 resultsb = b;
+            __m256 resultsa2 = a2;
+            __m256 resultsb2 = b2;
+
             if (info.smooth) {
+                __m256 cmp = _mm256_cmp_ps(a, a, _CMP_LE_OQ);
+                __m256 cmp2 = _mm256_cmp_ps(a, a, _CMP_LE_OQ);
                 for (int k = 0; k < info.maxIter; k++) {
                     __m256 aa = _mm256_mul_ps(a, a);
                     __m256 aa2 = _mm256_mul_ps(a2, a2);
@@ -85,12 +88,12 @@ void CpuGenerator<float, mnd::X86_AVX, parallel>::generate(const mnd::MandelInfo
                     a2 = _mm256_add_ps(_mm256_sub_ps(aa2, bb2), cx2);
                     b = _mm256_add_ps(abab, cy);
                     b2 = _mm256_add_ps(abab2, cy);
-                    __m256 cmp = _mm256_cmp_ps(_mm256_add_ps(aa, bb), threshold, _CMP_LE_OQ);
-                    __m256 cmp2 = _mm256_cmp_ps(_mm256_add_ps(aa2, bb2), threshold, _CMP_LE_OQ);
                     resultsa = _mm256_or_ps(_mm256_andnot_ps(cmp, resultsa), _mm256_and_ps(cmp, a));
                     resultsb = _mm256_or_ps(_mm256_andnot_ps(cmp, resultsb), _mm256_and_ps(cmp, b));
                     resultsa2 = _mm256_or_ps(_mm256_andnot_ps(cmp2, resultsa2), _mm256_and_ps(cmp2, a2));
                     resultsb2 = _mm256_or_ps(_mm256_andnot_ps(cmp2, resultsb2), _mm256_and_ps(cmp2, b2));
+                    cmp = _mm256_cmp_ps(_mm256_add_ps(aa, bb), threshold, _CMP_LE_OQ);
+                    cmp2 = _mm256_cmp_ps(_mm256_add_ps(aa2, bb2), threshold, _CMP_LE_OQ);
                     adder = _mm256_and_ps(adder, cmp);
                     counter = _mm256_add_ps(counter, adder);
                     adder2 = _mm256_and_ps(adder2, cmp2);
@@ -145,12 +148,12 @@ void CpuGenerator<float, mnd::X86_AVX, parallel>::generate(const mnd::MandelInfo
             _mm256_store_ps(resb + 8, resultsb2);
             for (int k = 0; k < 16 && i + k < info.bWidth; k++) {
                 if (info.smooth) {
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f);
                 }
                 else {
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter : ftRes[k];
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter : ftRes[k];
                 }
             }
         }
@@ -211,6 +214,8 @@ void CpuGenerator<double, mnd::X86_AVX, parallel>::generate(const mnd::MandelInf
             __m256d cy = info.julia ? juliaY : ys;
 
             if (info.smooth) {
+                __m256d cmp = _mm256_cmp_pd(a, a, _CMP_LE_OQ);
+                __m256d cmp2 = _mm256_cmp_pd(a, a, _CMP_LE_OQ);
                 for (int k = 0; k < info.maxIter; k++) {
                     __m256d aa = _mm256_mul_pd(a, a);
                     __m256d aa2 = _mm256_mul_pd(a2, a2);
@@ -222,12 +227,12 @@ void CpuGenerator<double, mnd::X86_AVX, parallel>::generate(const mnd::MandelInf
                     a2 = _mm256_add_pd(_mm256_sub_pd(aa2, bb2), cx2);
                     b = _mm256_add_pd(abab, cy);
                     b2 = _mm256_add_pd(abab2, cy);
-                    __m256d cmp = _mm256_cmp_pd(_mm256_add_pd(aa, bb), threshold, _CMP_LE_OQ);
-                    __m256d cmp2 = _mm256_cmp_pd(_mm256_add_pd(aa2, bb2), threshold, _CMP_LE_OQ);
                     resultsa = _mm256_or_pd(_mm256_andnot_pd(cmp, resultsa), _mm256_and_pd(cmp, a));
                     resultsb = _mm256_or_pd(_mm256_andnot_pd(cmp, resultsb), _mm256_and_pd(cmp, b));
                     resultsa2 = _mm256_or_pd(_mm256_andnot_pd(cmp2, resultsa2), _mm256_and_pd(cmp2, a2));
                     resultsb2 = _mm256_or_pd(_mm256_andnot_pd(cmp2, resultsb2), _mm256_and_pd(cmp2, b2));
+                    cmp = _mm256_cmp_pd(_mm256_add_pd(aa, bb), threshold, _CMP_LE_OQ);
+                    cmp2 = _mm256_cmp_pd(_mm256_add_pd(aa2, bb2), threshold, _CMP_LE_OQ);
                     adder = _mm256_and_pd(adder, cmp);
                     counter = _mm256_add_pd(counter, adder);
                     adder2 = _mm256_and_pd(adder2, cmp2);
@@ -278,11 +283,11 @@ void CpuGenerator<double, mnd::X86_AVX, parallel>::generate(const mnd::MandelInf
             _mm256_store_pd(ftRes, counter);
             for (int k = 0; k < 4 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? float(info.maxIter) :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? float(info.maxIter) :
                         ftRes[k] >= info.maxIter ? float(info.maxIter) :
                         float(((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f));
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] >= 0 ? float(ftRes[k]) : info.maxIter;
             }
 
             resa = (double*) &resultsa2;
@@ -291,11 +296,11 @@ void CpuGenerator<double, mnd::X86_AVX, parallel>::generate(const mnd::MandelInf
             i += 4;
             for (int k = 0; k < 4 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? float(info.maxIter) :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? float(info.maxIter) :
                         ftRes[k] >= info.maxIter ? float(info.maxIter) :
                         float(((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f));
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] >= 0 ? float(ftRes[k]) : info.maxIter;
             }
             i -= 4;
         }
@@ -473,17 +478,18 @@ void CpuGenerator<mnd::DoubleDouble, mnd::X86_AVX, parallel>::generate(const mnd
             __m256d resultsa = _mm256_set1_pd(0);
             __m256d resultsb = _mm256_set1_pd(0);
 
+            __m256d cmp = _mm256_cmp_pd(threshold, threshold, _CMP_LE_OQ);
             for (int k = 0; k < info.maxIter; k++) {
                 AvxDoubleDouble aa = a * a;
                 AvxDoubleDouble bb = b * b;
                 AvxDoubleDouble abab = a * b; abab = abab + abab;
                 a = aa - bb + cx;
                 b = abab + cy;
-                __m256d cmp = _mm256_cmp_pd(_mm256_add_pd(aa.x[0], bb.x[0]), threshold, _CMP_LE_OQ);
                 if (info.smooth) {
                     resultsa = _mm256_or_pd(_mm256_andnot_pd(cmp, resultsa), _mm256_and_pd(cmp, a.x[0]));
                     resultsb = _mm256_or_pd(_mm256_andnot_pd(cmp, resultsb), _mm256_and_pd(cmp, b.x[0]));
                 }
+                cmp = _mm256_cmp_pd(_mm256_add_pd(aa.x[0], bb.x[0]), threshold, _CMP_LE_OQ);
                 adder = _mm256_and_pd(adder, cmp);
                 counter = _mm256_add_pd(counter, adder);
                 if (_mm256_testz_si256(_mm256_castpd_si256(cmp), _mm256_castpd_si256(cmp)) != 0) {
@@ -506,11 +512,11 @@ void CpuGenerator<mnd::DoubleDouble, mnd::X86_AVX, parallel>::generate(const mnd
 
             for (int k = 0; k < 4 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = float(ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = float(ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log(float(resa[k] * resa[k] + resb[k] * resb[k])) / 2) / ::log(2.0f));
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] >= 0 ? float(ftRes[k]) : info.maxIter;
             }
         }
     }

+ 18 - 11
libmandel/src/CpuGeneratorsAVX512.cpp

@@ -82,6 +82,8 @@ void CpuGenerator<float, mnd::X86_AVX_512, parallel>::generate(const mnd::Mandel
             //__m512 b2 = ys;
 
             if (info.smooth) {
+                __mmask16 cmp0 = 0xFFFF;
+                __mmask16 cmp1 = 0xFFFF;
                 for (int k = 0; k < info.maxIter; k++) {
                     __m512 aa0 = _mm512_mul_ps(a0, a0);
                     __m512 aa1 = _mm512_mul_ps(a1, a1);
@@ -89,24 +91,28 @@ void CpuGenerator<float, mnd::X86_AVX_512, parallel>::generate(const mnd::Mandel
                     __m512 abab0 = _mm512_mul_ps(a0, b0);
                     __m512 abab1 = _mm512_mul_ps(a1, b1);
                     //__m512 abab2 = _mm512_mul_ps(a2, b2);
-                    __mmask16 cmp0 = _mm512_cmp_ps_mask(_mm512_fmadd_ps(b0, b0, aa0), threshold, _CMP_LE_OQ);
-                    __mmask16 cmp1 = _mm512_cmp_ps_mask(_mm512_fmadd_ps(b1, b1, aa1), threshold, _CMP_LE_OQ);
-                    //__mmask16 cmp2 = _mm512_cmp_ps_mask(_mm512_fmadd_ps(b2, b2, aa2), threshold, _CMP_LE_OQ);
+
                     a0 = _mm512_sub_ps(aa0, _mm512_fmsub_ps(b0, b0, cx0));
                     a1 = _mm512_sub_ps(aa1, _mm512_fmsub_ps(b1, b1, cx1));
                     //a2 = _mm512_sub_ps(aa2, _mm512_fmsub_ps(b2, b2, xs2));
                     b0 = _mm512_fmadd_ps(two, abab0, cy);
                     b1 = _mm512_fmadd_ps(two, abab1, cy);
                     //b2 = _mm512_fmadd_ps(two, abab2, ys);
-                    counter0 = _mm512_mask_add_ps(counter0, cmp0, counter0, adder0);
-                    counter1 = _mm512_mask_add_ps(counter1, cmp1, counter1, adder1);
-                    //counter2 = _mm512_mask_add_ps(counter2, cmp2, counter2, adder2);
+
                     resultsa0 = _mm512_mask_blend_ps(cmp0, resultsa0, a0);
                     resultsa1 = _mm512_mask_blend_ps(cmp1, resultsa1, a1);
                     //resultsa2 = _mm512_mask_blend_ps(cmp2, resultsa2, a2);
                     resultsb0 = _mm512_mask_blend_ps(cmp0, resultsb0, b0);
                     resultsb1 = _mm512_mask_blend_ps(cmp1, resultsb1, b1);
                     //resultsb2 = _mm512_mask_blend_ps(cmp2, resultsb2, b2);
+
+                    cmp0 = _mm512_cmp_ps_mask(_mm512_fmadd_ps(b0, b0, aa0), threshold, _CMP_LE_OQ);
+                    cmp1 = _mm512_cmp_ps_mask(_mm512_fmadd_ps(b1, b1, aa1), threshold, _CMP_LE_OQ);
+                    //__mmask16 cmp2 = _mm512_cmp_ps_mask(_mm512_fmadd_ps(b2, b2, aa2), threshold, _CMP_LE_OQ);
+
+                    counter0 = _mm512_mask_add_ps(counter0, cmp0, counter0, adder0);
+                    counter1 = _mm512_mask_add_ps(counter1, cmp1, counter1, adder1);
+                    //counter2 = _mm512_mask_add_ps(counter2, cmp2, counter2, adder2);
                     if (cmp0 == 0 && cmp1 == 0 /*&& cmp2 == 0*/) {
                         break;
                     }
@@ -162,12 +168,12 @@ void CpuGenerator<float, mnd::X86_AVX_512, parallel>::generate(const mnd::Mandel
             }
             for (int k = 0; k < 2 * 16 && i + k < info.bWidth; k++) {
                 if (info.smooth) {
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f);
                 }
                 else {
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter : ftRes[k];
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter : ftRes[k];
                 }
             }
         }
@@ -222,14 +228,15 @@ void CpuGenerator<double, mnd::X86_AVX_512, parallel>::generate(const mnd::Mande
             __m512d b = ys;
 
             if (info.smooth) {
+                __mmask8 cmp = 0xFF;
                 for (int k = 0; k < info.maxIter; k++) {
                     __m512d aa = _mm512_mul_pd(a, a);
                     __m512d ab = _mm512_mul_pd(a, b);
-                    __mmask8 cmp = _mm512_cmp_pd_mask(_mm512_fmadd_pd(b, b, aa), threshold, _CMP_LE_OQ);
                     a = _mm512_sub_pd(aa, _mm512_fmsub_pd(b, b, cx));
                     b = _mm512_fmadd_pd(two, ab, cy);
                     resultsa = _mm512_mask_blend_pd(cmp, resultsa, a);
                     resultsb = _mm512_mask_blend_pd(cmp, resultsb, b);
+                    cmp = _mm512_cmp_pd_mask(_mm512_fmadd_pd(b, b, aa), threshold, _CMP_LE_OQ);
                     counter = _mm512_mask_add_pd(counter, cmp, counter, adder);
                     if (cmp == 0) {
                         break;
@@ -268,12 +275,12 @@ void CpuGenerator<double, mnd::X86_AVX_512, parallel>::generate(const mnd::Mande
             }
             for (int k = 0; k < 8 && i + k < info.bWidth; k++) {
                 if (info.smooth) {
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log((float) (resa[k] * resa[k] + resb[k] * resb[k])) / 2) / ::log(2.0f);
                 }
                 else {
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter : ftRes[k];
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter : ftRes[k];
                 }
             }
         }

+ 44 - 37
libmandel/src/CpuGeneratorsAVXFMA.cpp

@@ -88,6 +88,9 @@ void CpuGenerator<float, mnd::X86_AVX_FMA, parallel>::generate(const mnd::Mandel
             __m256 cy = info.julia ? juliaY : ys;
 
             if (info.smooth) {
+                __m256 cmp = _mm256_cmp_ps(threshold, threshold, _CMP_LE_OQ);
+                __m256 cmp2 = _mm256_cmp_ps(threshold, threshold, _CMP_LE_OQ);
+                __m256 cmp3 = _mm256_cmp_ps(threshold, threshold, _CMP_LE_OQ);
                 for (int k = 0; k < info.maxIter; k++) {
                     __m256 bb = _mm256_mul_ps(b, b);
                     __m256 bb2 = _mm256_mul_ps(b2, b2);
@@ -95,27 +98,30 @@ void CpuGenerator<float, mnd::X86_AVX_FMA, parallel>::generate(const mnd::Mandel
                     __m256 ab = _mm256_mul_ps(a, b);
                     __m256 ab2 = _mm256_mul_ps(a2, b2);
                     __m256 ab3 = _mm256_mul_ps(a3, b3);
-                    b = _mm256_fmadd_ps(two, ab, cy);
-                    b2 = _mm256_fmadd_ps(two, ab2, cy);
-                    b3 = _mm256_fmadd_ps(two, ab3, cy);
-                    __m256 cmp = _mm256_cmp_ps(_mm256_fmadd_ps(a, a, bb), threshold, _CMP_LE_OQ);
-                    __m256 cmp2 = _mm256_cmp_ps(_mm256_fmadd_ps(a2, a2, bb2), threshold, _CMP_LE_OQ);
-                    __m256 cmp3 = _mm256_cmp_ps(_mm256_fmadd_ps(a3, a3, bb3), threshold, _CMP_LE_OQ);
+                    __m256 olda = a;
+                    __m256 olda2 = a2;
+                    __m256 olda3 = a3;
                     a = _mm256_add_ps(_mm256_fmsub_ps(a, a, bb), cx);
                     a2 = _mm256_add_ps(_mm256_fmsub_ps(a2, a2, bb2), cx2);
                     a3 = _mm256_add_ps(_mm256_fmsub_ps(a3, a3, bb3), cx3);
+                    b = _mm256_fmadd_ps(two, ab, cy);
+                    b2 = _mm256_fmadd_ps(two, ab2, cy);
+                    b3 = _mm256_fmadd_ps(two, ab3, cy);
                     /*resultsa = _mm256_or_ps(_mm256_andnot_ps(cmp, resultsa), _mm256_and_ps(cmp, a));
                     resultsb = _mm256_or_ps(_mm256_andnot_ps(cmp, resultsb), _mm256_and_ps(cmp, b));
                     resultsa2 = _mm256_or_ps(_mm256_andnot_ps(cmp2, resultsa2), _mm256_and_ps(cmp2, a2));
                     resultsb2 = _mm256_or_ps(_mm256_andnot_ps(cmp2, resultsb2), _mm256_and_ps(cmp2, b2));
                     resultsa3 = _mm256_or_ps(_mm256_andnot_ps(cmp3, resultsa3), _mm256_and_ps(cmp3, a3));
                     resultsb3 = _mm256_or_ps(_mm256_andnot_ps(cmp3, resultsb3), _mm256_and_ps(cmp3, b3));*/
-                    resultsa = _mm256_blendv_ps(resultsa, a, cmp); 
-                    resultsb = _mm256_blendv_ps(resultsb, b, cmp); 
-                    resultsa2 = _mm256_blendv_ps(resultsa2, a2, cmp2); 
-                    resultsb2 = _mm256_blendv_ps(resultsb2, b2, cmp2); 
-                    resultsa3 = _mm256_blendv_ps(resultsa3, a3, cmp3); 
-                    resultsb3 = _mm256_blendv_ps(resultsb3, b3, cmp3); 
+                    resultsa = _mm256_blendv_ps(resultsa, a, cmp);
+                    resultsb = _mm256_blendv_ps(resultsb, b, cmp);
+                    resultsa2 = _mm256_blendv_ps(resultsa2, a2, cmp2);
+                    resultsb2 = _mm256_blendv_ps(resultsb2, b2, cmp2);
+                    resultsa3 = _mm256_blendv_ps(resultsa3, a3, cmp3);
+                    resultsb3 = _mm256_blendv_ps(resultsb3, b3, cmp3);
+                    cmp = _mm256_cmp_ps(_mm256_fmadd_ps(olda, olda, bb), threshold, _CMP_LE_OQ);
+                    cmp2 = _mm256_cmp_ps(_mm256_fmadd_ps(olda2, olda2, bb2), threshold, _CMP_LE_OQ);
+                    cmp3 = _mm256_cmp_ps(_mm256_fmadd_ps(olda3, olda3, bb3), threshold, _CMP_LE_OQ);
                     adder = _mm256_and_ps(adder, cmp);
                     counter = _mm256_add_ps(counter, adder);
                     adder2 = _mm256_and_ps(adder2, cmp2);
@@ -180,12 +186,12 @@ void CpuGenerator<float, mnd::X86_AVX_FMA, parallel>::generate(const mnd::Mandel
             _mm256_store_ps(resb + 16, resultsb3);
             for (int k = 0; k < 24 && i + k < info.bWidth; k++) {
                 if (info.smooth) {
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f);
                 }
                 else {
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter : ftRes[k];
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter : ftRes[k];
                 }
             }
         }
@@ -248,13 +254,15 @@ void CpuGenerator<double, mnd::X86_AVX_FMA, parallel>::generate(const mnd::Mande
             __m256d cx2 = info.julia ? juliaX : xs2;
             //__m256d cy2 = info.julia ? juliaY : ys;
 
+            __m256d cmp = _mm256_cmp_pd(threshold, threshold, _CMP_LE_OQ);
+            __m256d cmp2 = _mm256_cmp_pd(threshold, threshold, _CMP_LE_OQ);
             for (int k = 0; k < info.maxIter; k++) {
+                __m256d aa = _mm256_mul_pd(a, a);
                 __m256d ab = _mm256_mul_pd(a, b);
                 __m256d bb = _mm256_mul_pd(b, b);
+                __m256d aa2 = _mm256_mul_pd(a2, a2);
                 __m256d ab2 = _mm256_mul_pd(a2, b2);
                 __m256d bb2 = _mm256_mul_pd(b2, b2);
-                __m256d cmp = _mm256_cmp_pd(_mm256_fmadd_pd(a, a, bb), threshold, _CMP_LE_OQ);
-                __m256d cmp2 = _mm256_cmp_pd(_mm256_fmadd_pd(a2, a2, bb2), threshold, _CMP_LE_OQ);
                 a = _mm256_fmsub_pd(a, a, bb);
                 a = _mm256_add_pd(a, cx);
                 a2 = _mm256_fmsub_pd(a2, a2, bb2);
@@ -262,16 +270,13 @@ void CpuGenerator<double, mnd::X86_AVX_FMA, parallel>::generate(const mnd::Mande
                 b = _mm256_fmadd_pd(two, ab, cy);
                 b2 = _mm256_fmadd_pd(two, ab2, cy);
                 if (info.smooth) {
-                    /*resultsa = _mm256_or_pd(_mm256_andnot_pd(cmp, resultsa), _mm256_and_pd(cmp, a));
-                    resultsb = _mm256_or_pd(_mm256_andnot_pd(cmp, resultsb), _mm256_and_pd(cmp, b));
-                    resultsa2 = _mm256_or_pd(_mm256_andnot_pd(cmp2, resultsa2), _mm256_and_pd(cmp2, a2));
-                    resultsb2 = _mm256_or_pd(_mm256_andnot_pd(cmp2, resultsb2), _mm256_and_pd(cmp2, b2));*/
-
                     resultsa = _mm256_blendv_pd(resultsa, a, cmp);
                     resultsb = _mm256_blendv_pd(resultsb, b, cmp);
                     resultsa2 = _mm256_blendv_pd(resultsa2, a2, cmp2);
                     resultsb2 = _mm256_blendv_pd(resultsb2, b2, cmp2);
                 }
+                cmp = _mm256_cmp_pd(_mm256_add_pd(aa, bb), threshold, _CMP_LE_OQ);
+                cmp2 = _mm256_cmp_pd(_mm256_add_pd(aa2, bb2), threshold, _CMP_LE_OQ);
                 adder = _mm256_and_pd(adder, cmp);
                 adder2 = _mm256_and_pd(adder2, cmp2);
                 counter = _mm256_add_pd(counter, adder);
@@ -296,11 +301,11 @@ void CpuGenerator<double, mnd::X86_AVX_FMA, parallel>::generate(const mnd::Mande
             _mm256_store_pd(ftRes, counter);
             for (int k = 0; k < 4 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f);
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter : float(ftRes[k]);
             }
 
             resa = (double*) &resultsa2;
@@ -309,11 +314,11 @@ void CpuGenerator<double, mnd::X86_AVX_FMA, parallel>::generate(const mnd::Mande
             i += 4;
             for (int k = 0; k < 4 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f);
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter : float(ftRes[k]);
             }
             i -= 4;
         }
@@ -573,17 +578,18 @@ void CpuGenerator<mnd::DoubleDouble, mnd::X86_AVX_FMA, parallel>::generate(const
             __m256d resultsa;
             __m256d resultsb;
 
+            __m256d cmp = _mm256_cmp_pd(threshold, threshold, _CMP_LE_OQ);
             for (int k = 0; k < info.maxIter; k++) {
                 AvxDoubleDouble aa = a * a;
                 AvxDoubleDouble bb = b * b;
                 AvxDoubleDouble abab = a * b; abab = abab + abab;
                 a = aa - bb + cx;
                 b = abab + cy;
-                __m256d cmp = _mm256_cmp_pd(_mm256_add_pd(aa.x[0], bb.x[0]), threshold, _CMP_LE_OQ);
                 if (info.smooth) {
                     resultsa = _mm256_blendv_pd(resultsa, a.x[0], cmp);
                     resultsb = _mm256_blendv_pd(resultsb, b.x[0], cmp);
                 }
+                cmp = _mm256_cmp_pd(_mm256_add_pd(aa.x[0], bb.x[0]), threshold, _CMP_LE_OQ);
                 adder = _mm256_and_pd(adder, cmp);
                 counter = _mm256_add_pd(counter, adder);
                 if (_mm256_testz_si256(_mm256_castpd_si256(cmp), _mm256_castpd_si256(cmp)) != 0) {
@@ -606,11 +612,11 @@ void CpuGenerator<mnd::DoubleDouble, mnd::X86_AVX_FMA, parallel>::generate(const
 
             for (int k = 0; k < 4 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f);
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] >= 0 ? float(ftRes[k]) : info.maxIter;
             }
         }
     }
@@ -694,7 +700,7 @@ void CpuGenerator<mnd::QuadDouble, mnd::X86_AVX_FMA, parallel>::generate(const m
 {
     const MandelViewport& view = info.view;
 
-    using T = mnd::Float256;
+    using T = mnd::Real;
 
     T viewx = mnd::convert<T>(view.x);
     T viewy = mnd::convert<T>(view.y);
@@ -706,9 +712,9 @@ void CpuGenerator<mnd::QuadDouble, mnd::X86_AVX_FMA, parallel>::generate(const m
     T jY = mnd::convert<T>(info.juliaY);
 
 
-    auto toQd = [] (const mnd::Float256& x) -> std::tuple<double, double, double, double> {
+    auto toQd = [] (const mnd::Real& x) -> std::tuple<double, double, double, double> {
         double a = double(x);
-        mnd::Float256 rem = x - a;
+        mnd::Real rem = x - a;
         double b = double(rem);
         rem = rem - b;
         double c = double(rem);
@@ -717,13 +723,13 @@ void CpuGenerator<mnd::QuadDouble, mnd::X86_AVX_FMA, parallel>::generate(const m
         return { a, b, c, d };
     };
 
-    auto toAvxQuadDouble = [&toQd] (const mnd::Float256& x) -> AvxQuadDouble {
+    auto toAvxQuadDouble = [&toQd] (const mnd::Real& x) -> AvxQuadDouble {
         auto [a, b, c, d] = toQd(x);
         return AvxQuadDouble{ a, b, c, d };
     };
 
-    auto toAvxQuadDouble4 = [&toQd] (const mnd::Float256& a, const mnd::Float256& b,
-                            const mnd::Float256& c, const mnd::Float256& d) -> AvxQuadDouble {
+    auto toAvxQuadDouble4 = [&toQd] (const mnd::Real& a, const mnd::Real& b,
+                            const mnd::Real& c, const mnd::Real& d) -> AvxQuadDouble {
         auto [x0, y0, z0, u0] = toQd(a);
         auto [x1, y1, z1, u1] = toQd(b);
         auto [x2, y2, z2, u2] = toQd(c);
@@ -771,17 +777,18 @@ void CpuGenerator<mnd::QuadDouble, mnd::X86_AVX_FMA, parallel>::generate(const m
             __m256d resultsa;
             __m256d resultsb;
 
+            __m256d cmp = _mm256_cmp_pd(threshold, threshold, _CMP_LE_OQ);
             for (int k = 0; k < info.maxIter; k++) {
                 AvxQuadDouble aa = a * a;
                 AvxQuadDouble bb = b * b;
                 AvxQuadDouble abab = a * b; abab = abab + abab;
                 a = aa - bb + cx;
                 b = abab + cy;
-                __m256d cmp = _mm256_cmp_pd(_mm256_add_pd(aa.x[0], bb.x[0]), threshold, _CMP_LE_OQ);
                 if (info.smooth) {
                     resultsa = _mm256_blendv_pd(resultsa, a.x[0], cmp);
                     resultsb = _mm256_blendv_pd(resultsb, b.x[0], cmp);
                 }
+                cmp = _mm256_cmp_pd(_mm256_add_pd(aa.x[0], bb.x[0]), threshold, _CMP_LE_OQ);
                 adder = _mm256_and_pd(adder, cmp);
                 counter = _mm256_add_pd(counter, adder);
                 if (_mm256_testz_si256(_mm256_castpd_si256(cmp), _mm256_castpd_si256(cmp)) != 0) {
@@ -804,11 +811,11 @@ void CpuGenerator<mnd::QuadDouble, mnd::X86_AVX_FMA, parallel>::generate(const m
 
             for (int k = 0; k < 4 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                         ftRes[k] >= info.maxIter ? info.maxIter :
                         ((float)ftRes[k]) + 1 - ::log(::log(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::log(2.0f);
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] >= 0 ? float(ftRes[k]) : info.maxIter;
             }
         }
     }

+ 17 - 13
libmandel/src/CpuGeneratorsSSE2.cpp

@@ -72,6 +72,8 @@ void CpuGenerator<float, mnd::X86_SSE2, parallel>::generate(const mnd::MandelInf
             __m128 resulta2 = { 0, 0, 0, 0 };
             __m128 resultb2 = { 0, 0, 0, 0 };
 
+            __m128 cmp = _mm_cmple_ps(threshold, threshold);
+            __m128 cmp2 = _mm_cmple_ps(threshold, threshold);
             for (int k = 0; k < info.maxIter; k++) {
                 __m128 aa = _mm_mul_ps(a, a);
                 __m128 aa2 = _mm_mul_ps(a2, a2);
@@ -83,14 +85,14 @@ void CpuGenerator<float, mnd::X86_SSE2, parallel>::generate(const mnd::MandelInf
                 b = _mm_add_ps(abab, cy);
                 a2 = _mm_add_ps(_mm_sub_ps(aa2, bb2), cx2);
                 b2 = _mm_add_ps(abab2, cy);
-                __m128 cmp = _mm_cmple_ps(_mm_add_ps(aa, bb), threshold);
-                __m128 cmp2 = _mm_cmple_ps(_mm_add_ps(aa2, bb2), threshold);
                 if (info.smooth) {
                     resulta = _mm_or_ps(_mm_andnot_ps(cmp, resulta), _mm_and_ps(cmp, a));
                     resultb = _mm_or_ps(_mm_andnot_ps(cmp, resultb), _mm_and_ps(cmp, b));
                     resulta2 = _mm_or_ps(_mm_andnot_ps(cmp2, resulta2), _mm_and_ps(cmp2, a2));
                     resultb2 = _mm_or_ps(_mm_andnot_ps(cmp2, resultb2), _mm_and_ps(cmp2, b2));
                 }
+                cmp = _mm_cmple_ps(_mm_add_ps(aa, bb), threshold);
+                cmp2 = _mm_cmple_ps(_mm_add_ps(aa2, bb2), threshold);
                 adder = _mm_and_ps(adder, cmp);
                 counter = _mm_add_ps(counter, adder);
                 adder2 = _mm_and_ps(adder2, cmp2);
@@ -121,11 +123,11 @@ void CpuGenerator<float, mnd::X86_SSE2, parallel>::generate(const mnd::MandelInf
             _mm_store_ps(resb + 4, resultb2);
             for (int k = 0; k < 8 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                     ftRes[k] >= info.maxIter ? info.maxIter :
                     ((float)ftRes[k]) + 1 - ::logf(::logf(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::logf(2.0f);
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] >= 0 ? float(ftRes[k]) : info.maxIter;
             }
         }
     }
@@ -177,17 +179,19 @@ void CpuGenerator<double, mnd::X86_SSE2, parallel>::generate(const mnd::MandelIn
             __m128d cx = xs;
             __m128d cy = ys;
             __m128d cx2 = xs2;
-	    if (info.julia) {
-		cx = juliaX;
-		cx2 = juliaX;
-		cy = juliaY;
-	    }
+	        if (info.julia) {
+		        cx = juliaX;
+		        cx2 = juliaX;
+		        cy = juliaY;
+	        }
 
             __m128d resulta = { 0, 0 };
             __m128d resultb = { 0, 0 };
             __m128d resulta2 = { 0, 0 };
             __m128d resultb2 = { 0, 0 };
 
+            __m128d cmp = _mm_cmple_pd(threshold, threshold);
+            __m128d cmp2 = _mm_cmple_pd(threshold, threshold);
             for (int k = 0; k < info.maxIter; k++) {
                 __m128d aa = _mm_mul_pd(a, a);
                 __m128d aa2 = _mm_mul_pd(a2, a2);
@@ -199,14 +203,14 @@ void CpuGenerator<double, mnd::X86_SSE2, parallel>::generate(const mnd::MandelIn
                 b = _mm_add_pd(abab, cy);
                 a2 = _mm_add_pd(_mm_sub_pd(aa2, bb2), cx2);
                 b2 = _mm_add_pd(abab2, cy);
-                __m128d cmp = _mm_cmple_pd(_mm_add_pd(aa, bb), threshold);
-                __m128d cmp2 = _mm_cmple_pd(_mm_add_pd(aa2, bb2), threshold);
                 if (info.smooth) {
                     resulta = _mm_or_pd(_mm_andnot_pd(cmp, resulta), _mm_and_pd(cmp, a));
                     resultb = _mm_or_pd(_mm_andnot_pd(cmp, resultb), _mm_and_pd(cmp, b));
                     resulta2 = _mm_or_pd(_mm_andnot_pd(cmp2, resulta2), _mm_and_pd(cmp2, a2));
                     resultb2 = _mm_or_pd(_mm_andnot_pd(cmp2, resultb2), _mm_and_pd(cmp2, b2));
                 }
+                cmp = _mm_cmple_pd(_mm_add_pd(aa, bb), threshold);
+                cmp2 = _mm_cmple_pd(_mm_add_pd(aa2, bb2), threshold);
                 adder = _mm_and_pd(adder, cmp);
                 counter = _mm_add_pd(counter, adder);
                 adder2 = _mm_and_pd(adder2, cmp2);
@@ -229,11 +233,11 @@ void CpuGenerator<double, mnd::X86_SSE2, parallel>::generate(const mnd::MandelIn
             _mm_storeu_pd(resb + 2, resultb2);
             for (int k = 0; k < 4 && i + k < info.bWidth; k++) {
                 if (info.smooth)
-                    data[i + k + j * info.bWidth] = ftRes[k] <= 0 ? info.maxIter :
+                    data[i + k + j * info.bWidth] = ftRes[k] < 0 ? info.maxIter :
                     ftRes[k] >= info.maxIter ? info.maxIter :
                     ((float)ftRes[k]) + 1 - ::logf(::logf(resa[k] * resa[k] + resb[k] * resb[k]) / 2) / ::logf(2.0f);
                 else
-                    data[i + k + j * info.bWidth] = ftRes[k] > 0 ? float(ftRes[k]) : info.maxIter;
+                    data[i + k + j * info.bWidth] = ftRes[k] >= 0 ? float(ftRes[k]) : info.maxIter;
             }
         }
     }

+ 16 - 7
libmandel/src/Mandel.cpp

@@ -83,8 +83,9 @@ MandelContext mnd::initializeContext(void)
 }
 
 
-MandelDevice::MandelDevice(mnd::ClDeviceWrapper device) :
-    clDevice{ std::make_unique<ClDeviceWrapper>(std::move(device)) }
+MandelDevice::MandelDevice(mnd::ClDeviceWrapper device, const std::string& platformName) :
+    clDevice{ std::make_unique<ClDeviceWrapper>(std::move(device)) },
+    platformName{ platformName }
 {
     extensions = clDevice->device.getInfo<CL_DEVICE_EXTENSIONS>();
     name = clDevice->device.getInfo<CL_DEVICE_NAME>();
@@ -272,17 +273,25 @@ std::vector<std::unique_ptr<MandelDevice>> MandelContext::createDevices(void)
     //platforms.erase(platforms.begin() + 1);
 
     for (auto& platform : platforms) {
-        std::string name = platform.getInfo<CL_PLATFORM_NAME>();
+        std::string platformName = platform.getInfo<CL_PLATFORM_NAME>();
         std::string profile = platform.getInfo<CL_PLATFORM_PROFILE>();
 
-        //printf("using opencl platform: %s\n", name.c_str());
+        //printf("using opencl platform: %s\n", platformName.c_str());
 
-        //std::string ext = platform.getInfo<CL_PLATFORM_EXTENSIONS>();
+        std::string ext = platform.getInfo<CL_PLATFORM_EXTENSIONS>();
         //printf("Platform extensions: %s\n", ext.c_str());
-        //printf("Platform: %s, %s\n", name.c_str(), profile.c_str());
+        //printf("Platform: %s, %s\n", platformName.c_str(), profile.c_str());
 
         std::vector<cl::Device> devices;
         platform.getDevices(CL_DEVICE_TYPE_GPU, &devices);
+        auto onError = [] (const char* errinfo, 
+            const void* private_info,
+            size_t cb, 
+            void* user_data) {
+                printf("opencl error: %s\n", errinfo);
+        };
+        
+        cl::Context context{ devices, nullptr, onError };
         for (auto& device : devices) {
             //printf("Device: %s\n", device.getInfo<CL_DEVICE_NAME>().c_str());
             //printf("preferred float width: %d\n", device.getInfo<CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT>());
@@ -291,7 +300,7 @@ std::vector<std::unique_ptr<MandelDevice>> MandelContext::createDevices(void)
 
             //printf("Device extensions: %s\n", ext.c_str());
             auto mandelDevice = std::make_unique<mnd::MandelDevice>(
-                ClDeviceWrapper{ device, cl::Context{ device } });
+                ClDeviceWrapper{ device, context }, platformName);
             MandelDevice& md = *mandelDevice;
 
             auto supportsDouble = md.supportsDouble();

+ 16 - 16
libmandel/src/OpenClCode.cpp

@@ -12,36 +12,36 @@
 
 namespace mnd
 {
-    const char* getFloat_cl() {
-        return (char*) float_cl;
+    std::string getFloat_cl() {
+        return std::string{ (char*) float_cl, float_cl_len };
     }
 
-    const char* getDouble_cl() {
-        return (char*) double_cl;
+    std::string getDouble_cl() {
+        return std::string{ (char*) double_cl, double_cl_len };
     }
 
-    const char* getDoubleFloat_cl() {
-        return (char*) doublefloat_cl;
+    std::string getDoubleFloat_cl() {
+        return std::string{ (char*) doublefloat_cl, doublefloat_cl_len };
     }
 
-    const char* getDoubleDouble_cl() {
-        return (char*) doubledouble_cl;
+    std::string getDoubleDouble_cl() {
+        return std::string{ (char*) doubledouble_cl, doubledouble_cl_len };
     }
 
-    const char* getQuadDouble_cl() {
-        return (char*) quaddouble_cl;
+    std::string getQuadDouble_cl() {
+        return std::string{ (char*) quaddouble_cl, quaddouble_cl_len };
     }
 
-    const char* getFixed64_cl() {
-        return (char*) fixed64_cl;
+    std::string getFixed64_cl() {
+        return std::string{ (char*) fixed64_cl, fixed64_cl_len };
     }
 
-    const char* getFixed128_cl() {
-        return (char*) fixed128_cl;
+    std::string getFixed128_cl() {
+        return std::string{ (char*) fixed128_cl, fixed128_cl_len };
     }
 
-    const char* getFixed512_cl() {
-        return (char*) fixed512_cl;
+    std::string getFixed512_cl() {
+        return std::string{ (char*) fixed512_cl, fixed512_cl_len };
     }
 }
 

+ 2 - 3
libmandel/src/opencl/double.cl

@@ -13,9 +13,9 @@ __kernel void iterate(__global float* A, const int width, double xl, double yt,
        double aa = a * a;
        double bb = b * b;
        double ab = a * b;
-       if (aa + bb > 16) break;
        a = aa - bb + ca;
        b = ab + ab + cb;
+       if (aa + bb > 16) break;
        n++;
    }
 // N + 1 - log (log  |Z(N)|) / log 2
@@ -27,5 +27,4 @@ __kernel void iterate(__global float* A, const int width, double xl, double yt,
        else
            A[index] = ((float)n);
    }
-}
-
+}

+ 9 - 9
libmandel/src/opencl/double.h

@@ -49,13 +49,13 @@ unsigned char double_cl[] = {
   0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f,
   0x75, 0x62, 0x6c, 0x65, 0x20, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x20,
   0x2a, 0x20, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x20, 0x2b, 0x20, 0x62, 0x62, 0x20,
-  0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20,
-  0x61, 0x61, 0x20, 0x2d, 0x20, 0x62, 0x62, 0x20, 0x2b, 0x20, 0x63, 0x61,
-  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d,
-  0x20, 0x61, 0x62, 0x20, 0x2b, 0x20, 0x61, 0x62, 0x20, 0x2b, 0x20, 0x63,
-  0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b,
+  0x61, 0x20, 0x3d, 0x20, 0x61, 0x61, 0x20, 0x2d, 0x20, 0x62, 0x62, 0x20,
+  0x2b, 0x20, 0x63, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x62, 0x20, 0x2b, 0x20, 0x61, 0x62,
+  0x20, 0x2b, 0x20, 0x63, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x20, 0x2b, 0x20, 0x62,
+  0x62, 0x20, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61,
+  0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b,
   0x2b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x2f, 0x2f, 0x20, 0x4e,
   0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x28,
   0x6c, 0x6f, 0x67, 0x20, 0x20, 0x7c, 0x5a, 0x28, 0x4e, 0x29, 0x7c, 0x29,
@@ -78,6 +78,6 @@ unsigned char double_cl[] = {
   0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20,
   0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29,
-  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x0a
+  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d
 };
-unsigned int double_cl_len = 958;
+unsigned int double_cl_len = 956;

+ 1 - 1
libmandel/src/opencl/doubledouble.cl

@@ -76,10 +76,10 @@ __kernel void iterate(__global float* A, const int width,
         double2 aa = mul(a, a);
         double2 bb = mul(b, b);
         double2 ab = mul(a, b);
-        if (aa.s0 + bb.s0 > 16) break;
         double2 minusbb = (double2)(-bb.s0, -bb.s1);
         a = add(add(aa, minusbb), ca);
         b = add(add(ab, ab), cb);
+        if (aa.s0 + bb.s0 > 16) break;
         n++;
     }
 

+ 231 - 223
libmandel/src/opencl/doubledouble.h

@@ -2,241 +2,249 @@ unsigned char doubledouble_cl[] = {
   0x23, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x20, 0x4f, 0x50, 0x45, 0x4e,
   0x43, 0x4c, 0x20, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x53, 0x49, 0x4f, 0x4e,
   0x20, 0x63, 0x6c, 0x5f, 0x6b, 0x68, 0x72, 0x5f, 0x66, 0x70, 0x36, 0x34,
-  0x20, 0x3a, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x0a, 0x0a, 0x69,
-  0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x32, 0x20, 0x74, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28, 0x64, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x20, 0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c,
-  0x65, 0x20, 0x62, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64,
-  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x73, 0x20, 0x3d, 0x20, 0x61, 0x20,
-  0x2b, 0x20, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x20, 0x62, 0x62, 0x20, 0x3d, 0x20, 0x73, 0x20, 0x2d,
-  0x20, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62,
-  0x6c, 0x65, 0x20, 0x65, 0x20, 0x3d, 0x20, 0x28, 0x61, 0x20, 0x2d, 0x20,
-  0x28, 0x73, 0x20, 0x2d, 0x20, 0x62, 0x62, 0x29, 0x29, 0x20, 0x2b, 0x20,
-  0x28, 0x62, 0x20, 0x2d, 0x20, 0x62, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x64, 0x6f,
-  0x75, 0x62, 0x6c, 0x65, 0x32, 0x29, 0x28, 0x73, 0x2c, 0x20, 0x65, 0x29,
-  0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x71, 0x75, 0x69, 0x63,
-  0x6b, 0x54, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28, 0x64, 0x6f, 0x75, 0x62,
-  0x6c, 0x65, 0x20, 0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x20, 0x62, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f,
-  0x75, 0x62, 0x6c, 0x65, 0x20, 0x73, 0x20, 0x3d, 0x20, 0x61, 0x20, 0x2b,
-  0x20, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62,
-  0x6c, 0x65, 0x20, 0x65, 0x20, 0x3d, 0x20, 0x62, 0x20, 0x2d, 0x20, 0x28,
-  0x73, 0x20, 0x2d, 0x20, 0x61, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x3a, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x0d, 0x0a, 0x0d,
+  0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x64, 0x6f, 0x75, 0x62,
+  0x6c, 0x65, 0x32, 0x20, 0x74, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28, 0x64,
+  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75,
+  0x62, 0x6c, 0x65, 0x20, 0x62, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x73, 0x20, 0x3d,
+  0x20, 0x61, 0x20, 0x2b, 0x20, 0x62, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x62, 0x62, 0x20, 0x3d,
+  0x20, 0x73, 0x20, 0x2d, 0x20, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x65, 0x20, 0x3d, 0x20,
+  0x28, 0x61, 0x20, 0x2d, 0x20, 0x28, 0x73, 0x20, 0x2d, 0x20, 0x62, 0x62,
+  0x29, 0x29, 0x20, 0x2b, 0x20, 0x28, 0x62, 0x20, 0x2d, 0x20, 0x62, 0x62,
+  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75,
+  0x72, 0x6e, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x29,
+  0x28, 0x73, 0x2c, 0x20, 0x65, 0x29, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a,
+  0x0d, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x64, 0x6f, 0x75,
+  0x62, 0x6c, 0x65, 0x32, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x54, 0x77,
+  0x6f, 0x53, 0x75, 0x6d, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20,
+  0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x62, 0x29,
+  0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62,
+  0x6c, 0x65, 0x20, 0x73, 0x20, 0x3d, 0x20, 0x61, 0x20, 0x2b, 0x20, 0x62,
+  0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c,
+  0x65, 0x20, 0x65, 0x20, 0x3d, 0x20, 0x62, 0x20, 0x2d, 0x20, 0x28, 0x73,
+  0x20, 0x2d, 0x20, 0x61, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
   0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62,
-  0x6c, 0x65, 0x32, 0x29, 0x28, 0x73, 0x2c, 0x20, 0x65, 0x29, 0x3b, 0x0a,
-  0x7d, 0x0a, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x64, 0x6f,
-  0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x74, 0x77, 0x6f, 0x50, 0x72, 0x6f,
-  0x64, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x61, 0x2c, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x62, 0x29, 0x20, 0x7b, 0x0a,
-  0x2f, 0x2f, 0x23, 0x69, 0x66, 0x64, 0x65, 0x66, 0x20, 0x51, 0x44, 0x5f,
-  0x46, 0x4d, 0x53, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62,
-  0x6c, 0x65, 0x20, 0x70, 0x20, 0x3d, 0x20, 0x61, 0x20, 0x2a, 0x20, 0x62,
-  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x20, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x6d, 0x61, 0x28, 0x61, 0x2c, 0x20,
-  0x62, 0x2c, 0x20, 0x2d, 0x70, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
-  0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62,
-  0x6c, 0x65, 0x32, 0x29, 0x28, 0x70, 0x2c, 0x20, 0x65, 0x29, 0x3b, 0x0a,
-  0x2f, 0x2f, 0x23, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x2f, 0x2f, 0x20, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x61, 0x5f, 0x68, 0x69, 0x2c,
-  0x20, 0x61, 0x5f, 0x6c, 0x6f, 0x2c, 0x20, 0x62, 0x5f, 0x68, 0x69, 0x2c,
-  0x20, 0x62, 0x5f, 0x6c, 0x6f, 0x3b, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x64,
-  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x20, 0x3d, 0x20, 0x61, 0x20,
-  0x2a, 0x20, 0x62, 0x3b, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x73, 0x70, 0x6c,
-  0x69, 0x74, 0x28, 0x61, 0x2c, 0x20, 0x61, 0x5f, 0x68, 0x69, 0x2c, 0x20,
-  0x61, 0x5f, 0x6c, 0x6f, 0x29, 0x3b, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x73,
+  0x6c, 0x65, 0x32, 0x29, 0x28, 0x73, 0x2c, 0x20, 0x65, 0x29, 0x3b, 0x0d,
+  0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65,
+  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x74, 0x77, 0x6f,
+  0x50, 0x72, 0x6f, 0x64, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20,
+  0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x62, 0x29,
+  0x20, 0x7b, 0x0d, 0x0a, 0x2f, 0x2f, 0x23, 0x69, 0x66, 0x64, 0x65, 0x66,
+  0x20, 0x51, 0x44, 0x5f, 0x46, 0x4d, 0x53, 0x0d, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x20, 0x3d, 0x20,
+  0x61, 0x20, 0x2a, 0x20, 0x62, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x65, 0x20, 0x3d, 0x20, 0x66,
+  0x6d, 0x61, 0x28, 0x61, 0x2c, 0x20, 0x62, 0x2c, 0x20, 0x2d, 0x70, 0x29,
+  0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72,
+  0x6e, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x29, 0x28,
+  0x70, 0x2c, 0x20, 0x65, 0x29, 0x3b, 0x0d, 0x0a, 0x2f, 0x2f, 0x23, 0x65,
+  0x6c, 0x73, 0x65, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x64, 0x6f, 0x75,
+  0x62, 0x6c, 0x65, 0x20, 0x61, 0x5f, 0x68, 0x69, 0x2c, 0x20, 0x61, 0x5f,
+  0x6c, 0x6f, 0x2c, 0x20, 0x62, 0x5f, 0x68, 0x69, 0x2c, 0x20, 0x62, 0x5f,
+  0x6c, 0x6f, 0x3b, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x64, 0x6f, 0x75,
+  0x62, 0x6c, 0x65, 0x20, 0x70, 0x20, 0x3d, 0x20, 0x61, 0x20, 0x2a, 0x20,
+  0x62, 0x3b, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x73, 0x70, 0x6c, 0x69,
+  0x74, 0x28, 0x61, 0x2c, 0x20, 0x61, 0x5f, 0x68, 0x69, 0x2c, 0x20, 0x61,
+  0x5f, 0x6c, 0x6f, 0x29, 0x3b, 0x0d, 0x0a, 0x2f, 0x2f, 0x20, 0x20, 0x73,
   0x70, 0x6c, 0x69, 0x74, 0x28, 0x62, 0x2c, 0x20, 0x62, 0x5f, 0x68, 0x69,
-  0x2c, 0x20, 0x62, 0x5f, 0x6c, 0x6f, 0x29, 0x3b, 0x0a, 0x2f, 0x2f, 0x20,
-  0x20, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x61, 0x5f, 0x68,
-  0x69, 0x20, 0x2a, 0x20, 0x62, 0x5f, 0x68, 0x69, 0x20, 0x2d, 0x20, 0x70,
-  0x29, 0x20, 0x2b, 0x20, 0x61, 0x5f, 0x68, 0x69, 0x20, 0x2a, 0x20, 0x62,
-  0x5f, 0x6c, 0x6f, 0x20, 0x2b, 0x20, 0x61, 0x5f, 0x6c, 0x6f, 0x20, 0x2a,
-  0x20, 0x62, 0x5f, 0x68, 0x69, 0x29, 0x20, 0x2b, 0x20, 0x61, 0x5f, 0x6c,
-  0x6f, 0x20, 0x2a, 0x20, 0x62, 0x5f, 0x6c, 0x6f, 0x3b, 0x0a, 0x2f, 0x2f,
-  0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x70, 0x3b, 0x0a,
-  0x2f, 0x2f, 0x23, 0x65, 0x6e, 0x64, 0x69, 0x66, 0x0a, 0x7d, 0x0a, 0x0a,
-  0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c,
-  0x65, 0x32, 0x20, 0x6d, 0x75, 0x6c, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c,
+  0x2c, 0x20, 0x62, 0x5f, 0x6c, 0x6f, 0x29, 0x3b, 0x0d, 0x0a, 0x2f, 0x2f,
+  0x20, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x61, 0x5f,
+  0x68, 0x69, 0x20, 0x2a, 0x20, 0x62, 0x5f, 0x68, 0x69, 0x20, 0x2d, 0x20,
+  0x70, 0x29, 0x20, 0x2b, 0x20, 0x61, 0x5f, 0x68, 0x69, 0x20, 0x2a, 0x20,
+  0x62, 0x5f, 0x6c, 0x6f, 0x20, 0x2b, 0x20, 0x61, 0x5f, 0x6c, 0x6f, 0x20,
+  0x2a, 0x20, 0x62, 0x5f, 0x68, 0x69, 0x29, 0x20, 0x2b, 0x20, 0x61, 0x5f,
+  0x6c, 0x6f, 0x20, 0x2a, 0x20, 0x62, 0x5f, 0x6c, 0x6f, 0x3b, 0x0d, 0x0a,
+  0x2f, 0x2f, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x70,
+  0x3b, 0x0d, 0x0a, 0x2f, 0x2f, 0x23, 0x65, 0x6e, 0x64, 0x69, 0x66, 0x0d,
+  0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65,
+  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x6d, 0x75, 0x6c,
+  0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x61, 0x2c, 0x20,
+  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x62, 0x29, 0x20, 0x7b,
+  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x32, 0x20, 0x70, 0x20, 0x3d, 0x20, 0x74, 0x77, 0x6f, 0x50, 0x72, 0x6f,
+  0x64, 0x28, 0x61, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x62, 0x2e, 0x73, 0x30,
+  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x2e, 0x73, 0x31,
+  0x20, 0x2b, 0x3d, 0x20, 0x28, 0x61, 0x2e, 0x73, 0x30, 0x20, 0x2a, 0x20,
+  0x62, 0x2e, 0x73, 0x31, 0x20, 0x2b, 0x20, 0x61, 0x2e, 0x73, 0x31, 0x20,
+  0x2a, 0x20, 0x62, 0x2e, 0x73, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x71, 0x75, 0x69,
+  0x63, 0x6b, 0x54, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28, 0x70, 0x2e, 0x73,
+  0x30, 0x2c, 0x20, 0x70, 0x2e, 0x73, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x7d,
+  0x0d, 0x0a, 0x0d, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x64,
+  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x73, 0x71, 0x28, 0x64, 0x6f,
+  0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x61, 0x29, 0x20, 0x7b, 0x0d, 0x0a,
+  0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20,
+  0x70, 0x20, 0x3d, 0x20, 0x74, 0x77, 0x6f, 0x50, 0x72, 0x6f, 0x64, 0x28,
+  0x61, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x61, 0x2e, 0x73, 0x30, 0x29, 0x3b,
+  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x20, 0x65, 0x20, 0x3d, 0x20, 0x61, 0x2e, 0x73, 0x30, 0x20, 0x2a, 0x20,
+  0x61, 0x2e, 0x73, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70,
+  0x2e, 0x73, 0x31, 0x20, 0x2b, 0x3d, 0x20, 0x65, 0x20, 0x2b, 0x20, 0x65,
+  0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72,
+  0x6e, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x54, 0x77, 0x6f, 0x53, 0x75,
+  0x6d, 0x28, 0x70, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x70, 0x2e, 0x73, 0x31,
+  0x29, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x69, 0x6e, 0x6c,
+  0x69, 0x6e, 0x65, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20,
+  0x61, 0x64, 0x64, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20,
+  0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x62,
+  0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75,
+  0x62, 0x6c, 0x65, 0x32, 0x20, 0x73, 0x65, 0x20, 0x3d, 0x20, 0x74, 0x77,
+  0x6f, 0x53, 0x75, 0x6d, 0x28, 0x61, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x62,
+  0x2e, 0x73, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73,
+  0x65, 0x2e, 0x73, 0x31, 0x20, 0x2b, 0x3d, 0x20, 0x61, 0x2e, 0x73, 0x31,
+  0x20, 0x2b, 0x20, 0x62, 0x2e, 0x73, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x71, 0x75, 0x69,
+  0x63, 0x6b, 0x54, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28, 0x73, 0x65, 0x2e,
+  0x73, 0x30, 0x2c, 0x20, 0x73, 0x65, 0x2e, 0x73, 0x31, 0x29, 0x3b, 0x0d,
+  0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65,
+  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x6d, 0x75, 0x6c,
+  0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c,
   0x65, 0x32, 0x20, 0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x32, 0x20, 0x62, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64,
+  0x20, 0x62, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64,
   0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x70, 0x20, 0x3d, 0x20, 0x74,
   0x77, 0x6f, 0x50, 0x72, 0x6f, 0x64, 0x28, 0x61, 0x2e, 0x73, 0x30, 0x2c,
-  0x20, 0x62, 0x2e, 0x73, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
-  0x70, 0x2e, 0x73, 0x31, 0x20, 0x2b, 0x3d, 0x20, 0x28, 0x61, 0x2e, 0x73,
-  0x30, 0x20, 0x2a, 0x20, 0x62, 0x2e, 0x73, 0x31, 0x20, 0x2b, 0x20, 0x61,
-  0x2e, 0x73, 0x31, 0x20, 0x2a, 0x20, 0x62, 0x2e, 0x73, 0x30, 0x29, 0x3b,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20,
-  0x71, 0x75, 0x69, 0x63, 0x6b, 0x54, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28,
-  0x70, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x70, 0x2e, 0x73, 0x31, 0x29, 0x3b,
-  0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x64,
-  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x73, 0x71, 0x28, 0x64, 0x6f,
-  0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x61, 0x29, 0x20, 0x7b, 0x0a, 0x20,
-  0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x70,
-  0x20, 0x3d, 0x20, 0x74, 0x77, 0x6f, 0x50, 0x72, 0x6f, 0x64, 0x28, 0x61,
-  0x2e, 0x73, 0x30, 0x2c, 0x20, 0x61, 0x2e, 0x73, 0x30, 0x29, 0x3b, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x65,
-  0x20, 0x3d, 0x20, 0x61, 0x2e, 0x73, 0x30, 0x20, 0x2a, 0x20, 0x61, 0x2e,
-  0x73, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x2e, 0x73, 0x31,
-  0x20, 0x2b, 0x3d, 0x20, 0x65, 0x20, 0x2b, 0x20, 0x65, 0x3b, 0x0a, 0x20,
-  0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x71, 0x75,
-  0x69, 0x63, 0x6b, 0x54, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28, 0x70, 0x2e,
-  0x73, 0x30, 0x2c, 0x20, 0x70, 0x2e, 0x73, 0x31, 0x29, 0x3b, 0x0a, 0x7d,
-  0x0a, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x64, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x32, 0x20, 0x61, 0x64, 0x64, 0x28, 0x64, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x32, 0x20, 0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62,
-  0x6c, 0x65, 0x32, 0x20, 0x62, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
-  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x73, 0x65, 0x20,
-  0x3d, 0x20, 0x74, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28, 0x61, 0x2e, 0x73,
-  0x30, 0x2c, 0x20, 0x62, 0x2e, 0x73, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x73, 0x65, 0x2e, 0x73, 0x31, 0x20, 0x2b, 0x3d, 0x20, 0x61,
-  0x2e, 0x73, 0x31, 0x20, 0x2b, 0x20, 0x62, 0x2e, 0x73, 0x31, 0x3b, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x71,
-  0x75, 0x69, 0x63, 0x6b, 0x54, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28, 0x73,
-  0x65, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x73, 0x65, 0x2e, 0x73, 0x31, 0x29,
-  0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x6d, 0x75, 0x6c, 0x44,
-  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x32, 0x20, 0x61, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20,
-  0x62, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x32, 0x20, 0x70, 0x20, 0x3d, 0x20, 0x74, 0x77, 0x6f,
-  0x50, 0x72, 0x6f, 0x64, 0x28, 0x61, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x62,
-  0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x2e, 0x73, 0x31, 0x20,
-  0x2b, 0x3d, 0x20, 0x61, 0x2e, 0x73, 0x31, 0x20, 0x2a, 0x20, 0x62, 0x3b,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20,
-  0x71, 0x75, 0x69, 0x63, 0x6b, 0x54, 0x77, 0x6f, 0x53, 0x75, 0x6d, 0x28,
-  0x70, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x70, 0x2e, 0x73, 0x31, 0x29, 0x3b,
-  0x0a, 0x7d, 0x0a, 0x0a, 0x5f, 0x5f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c,
-  0x20, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74,
-  0x65, 0x28, 0x5f, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x66,
-  0x6c, 0x6f, 0x61, 0x74, 0x2a, 0x20, 0x41, 0x2c, 0x20, 0x63, 0x6f, 0x6e,
-  0x73, 0x74, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
-  0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x78, 0x31, 0x2c, 0x20, 0x64,
-  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x78, 0x32, 0x2c, 0x20, 0x64, 0x6f,
-  0x75, 0x62, 0x6c, 0x65, 0x20, 0x79, 0x31, 0x2c, 0x20, 0x64, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x20, 0x79, 0x32, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x62, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x2e,
+  0x73, 0x31, 0x20, 0x2b, 0x3d, 0x20, 0x61, 0x2e, 0x73, 0x31, 0x20, 0x2a,
+  0x20, 0x62, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74,
+  0x75, 0x72, 0x6e, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x54, 0x77, 0x6f,
+  0x53, 0x75, 0x6d, 0x28, 0x70, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x70, 0x2e,
+  0x73, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x5f,
+  0x5f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x20, 0x76, 0x6f, 0x69, 0x64,
+  0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x65, 0x28, 0x5f, 0x5f, 0x67,
+  0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x2a,
+  0x20, 0x41, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x69, 0x6e,
+  0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x0d, 0x0a, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x20, 0x70, 0x77, 0x31, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x20, 0x70, 0x77, 0x32, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x20, 0x70, 0x68, 0x31, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x20, 0x70, 0x68, 0x32, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6d, 0x61,
-  0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x73, 0x6d, 0x6f, 0x6f, 0x74,
-  0x68, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62,
+  0x6c, 0x65, 0x20, 0x78, 0x31, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c,
+  0x65, 0x20, 0x78, 0x32, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x20, 0x79, 0x31, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20,
+  0x79, 0x32, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x69, 0x6e, 0x74, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x2c, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x6a, 0x78, 0x31, 0x2c, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x6a, 0x78, 0x32, 0x2c, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x6a, 0x79, 0x31, 0x2c, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x6a, 0x79, 0x32, 0x29, 0x20,
-  0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x6e,
-  0x64, 0x65, 0x78, 0x20, 0x3d, 0x20, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x6c,
-  0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x28, 0x30, 0x29, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x77,
+  0x31, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x77,
+  0x32, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x68,
+  0x31, 0x2c, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x68,
+  0x32, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x78, 0x2c, 0x20,
+  0x69, 0x6e, 0x74, 0x20, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x2c, 0x0d,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69,
+  0x6e, 0x74, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x2c, 0x20, 0x64, 0x6f,
+  0x75, 0x62, 0x6c, 0x65, 0x20, 0x6a, 0x78, 0x31, 0x2c, 0x20, 0x64, 0x6f,
+  0x75, 0x62, 0x6c, 0x65, 0x20, 0x6a, 0x78, 0x32, 0x2c, 0x20, 0x64, 0x6f,
+  0x75, 0x62, 0x6c, 0x65, 0x20, 0x6a, 0x79, 0x31, 0x2c, 0x20, 0x64, 0x6f,
+  0x75, 0x62, 0x6c, 0x65, 0x20, 0x6a, 0x79, 0x32, 0x29, 0x20, 0x7b, 0x0d,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x64,
+  0x65, 0x78, 0x20, 0x3d, 0x20, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x6c, 0x6f,
+  0x62, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x28, 0x30, 0x29, 0x3b, 0x0d, 0x0a,
   0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x70, 0x78, 0x20, 0x3d,
   0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x25, 0x20, 0x77, 0x69, 0x64,
-  0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20,
-  0x70, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x2f,
-  0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20,
-  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x78, 0x6c, 0x20,
-  0x3d, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x29, 0x28,
-  0x78, 0x31, 0x2c, 0x20, 0x78, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
-  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x79, 0x74, 0x20,
-  0x3d, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x29, 0x28,
-  0x79, 0x31, 0x2c, 0x20, 0x79, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
-  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x70, 0x69, 0x78,
-  0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x58, 0x20, 0x3d, 0x20, 0x28,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x29, 0x28, 0x70, 0x77, 0x31,
-  0x2c, 0x20, 0x70, 0x77, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x70, 0x69, 0x78, 0x65,
-  0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x59, 0x20, 0x3d, 0x20, 0x28, 0x64,
-  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x29, 0x28, 0x70, 0x68, 0x31, 0x2c,
-  0x20, 0x70, 0x68, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64,
-  0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61,
-  0x64, 0x64, 0x28, 0x6d, 0x75, 0x6c, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x28, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x58,
-  0x2c, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x29, 0x20, 0x70,
-  0x78, 0x29, 0x2c, 0x20, 0x78, 0x6c, 0x29, 0x3b, 0x20, 0x2f, 0x2f, 0x20,
-  0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x58, 0x20,
-  0x2a, 0x20, 0x70, 0x78, 0x20, 0x2b, 0x20, 0x78, 0x6c, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x62, 0x20,
-  0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x6d, 0x75, 0x6c, 0x44, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x28, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61,
-  0x6c, 0x65, 0x59, 0x2c, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-  0x29, 0x20, 0x70, 0x79, 0x29, 0x2c, 0x20, 0x79, 0x74, 0x29, 0x3b, 0x20,
-  0x2f, 0x2f, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c,
-  0x65, 0x59, 0x20, 0x2a, 0x20, 0x70, 0x79, 0x20, 0x2b, 0x20, 0x79, 0x74,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32,
-  0x20, 0x63, 0x61, 0x20, 0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20,
-  0x21, 0x3d, 0x20, 0x30, 0x20, 0x3f, 0x20, 0x28, 0x28, 0x64, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x32, 0x29, 0x20, 0x28, 0x6a, 0x78, 0x31, 0x2c, 0x20,
-  0x6a, 0x78, 0x32, 0x29, 0x29, 0x20, 0x3a, 0x20, 0x61, 0x3b, 0x0a, 0x20,
-  0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x63,
-  0x62, 0x20, 0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x21, 0x3d,
-  0x20, 0x30, 0x20, 0x3f, 0x20, 0x28, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c,
-  0x65, 0x32, 0x29, 0x20, 0x28, 0x6a, 0x79, 0x31, 0x2c, 0x20, 0x6a, 0x79,
-  0x32, 0x29, 0x29, 0x20, 0x3a, 0x20, 0x62, 0x3b, 0x0a, 0x0a, 0x0a, 0x20,
-  0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6e, 0x20, 0x3d, 0x20, 0x30,
-  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20,
-  0x28, 0x6e, 0x20, 0x3c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x2d, 0x20, 0x31,
-  0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x61, 0x61, 0x20, 0x3d,
-  0x20, 0x6d, 0x75, 0x6c, 0x28, 0x61, 0x2c, 0x20, 0x61, 0x29, 0x3b, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62,
-  0x6c, 0x65, 0x32, 0x20, 0x62, 0x62, 0x20, 0x3d, 0x20, 0x6d, 0x75, 0x6c,
-  0x28, 0x62, 0x2c, 0x20, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x74, 0x68, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74,
+  0x20, 0x70, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20,
+  0x2f, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a,
   0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20,
-  0x61, 0x62, 0x20, 0x3d, 0x20, 0x6d, 0x75, 0x6c, 0x28, 0x61, 0x2c, 0x20,
-  0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x2e, 0x73, 0x30, 0x20, 0x2b, 0x20,
-  0x62, 0x62, 0x2e, 0x73, 0x30, 0x20, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20,
-  0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x6d,
-  0x69, 0x6e, 0x75, 0x73, 0x62, 0x62, 0x20, 0x3d, 0x20, 0x28, 0x64, 0x6f,
-  0x75, 0x62, 0x6c, 0x65, 0x32, 0x29, 0x28, 0x2d, 0x62, 0x62, 0x2e, 0x73,
-  0x30, 0x2c, 0x20, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x31, 0x29, 0x3b, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20,
-  0x61, 0x64, 0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61, 0x61, 0x2c, 0x20,
-  0x6d, 0x69, 0x6e, 0x75, 0x73, 0x62, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x61,
-  0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62,
-  0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61,
-  0x62, 0x2c, 0x20, 0x61, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x62, 0x29, 0x3b,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b, 0x2b,
-  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
-  0x20, 0x2f, 0x2f, 0x20, 0x4e, 0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20,
-  0x6c, 0x6f, 0x67, 0x20, 0x28, 0x6c, 0x6f, 0x67, 0x20, 0x20, 0x7c, 0x5a,
-  0x28, 0x4e, 0x29, 0x7c, 0x29, 0x20, 0x2f, 0x20, 0x6c, 0x6f, 0x67, 0x20,
-  0x32, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x6e, 0x20,
-  0x3e, 0x3d, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e,
-  0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x78, 0x3b, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0a, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x73,
-  0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x20, 0x21, 0x3d, 0x20, 0x30, 0x29, 0x0a,
+  0x78, 0x6c, 0x20, 0x3d, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x32, 0x29, 0x28, 0x78, 0x31, 0x2c, 0x20, 0x78, 0x32, 0x29, 0x3b, 0x0d,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32,
+  0x20, 0x79, 0x74, 0x20, 0x3d, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c,
+  0x65, 0x32, 0x29, 0x28, 0x79, 0x31, 0x2c, 0x20, 0x79, 0x32, 0x29, 0x3b,
+  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x32, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65,
+  0x58, 0x20, 0x3d, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32,
+  0x29, 0x28, 0x70, 0x77, 0x31, 0x2c, 0x20, 0x70, 0x77, 0x32, 0x29, 0x3b,
+  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x32, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65,
+  0x59, 0x20, 0x3d, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32,
+  0x29, 0x28, 0x70, 0x68, 0x31, 0x2c, 0x20, 0x70, 0x68, 0x32, 0x29, 0x3b,
+  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x32, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x6d, 0x75,
+  0x6c, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x28, 0x70, 0x69, 0x78, 0x65,
+  0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x58, 0x2c, 0x20, 0x28, 0x64, 0x6f,
+  0x75, 0x62, 0x6c, 0x65, 0x29, 0x20, 0x70, 0x78, 0x29, 0x2c, 0x20, 0x78,
+  0x6c, 0x29, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c,
+  0x53, 0x63, 0x61, 0x6c, 0x65, 0x58, 0x20, 0x2a, 0x20, 0x70, 0x78, 0x20,
+  0x2b, 0x20, 0x78, 0x6c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f,
+  0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x64,
+  0x64, 0x28, 0x6d, 0x75, 0x6c, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x28,
+  0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x59, 0x2c,
+  0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x29, 0x20, 0x70, 0x79,
+  0x29, 0x2c, 0x20, 0x79, 0x74, 0x29, 0x3b, 0x20, 0x2f, 0x2f, 0x20, 0x70,
+  0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x59, 0x20, 0x2a,
+  0x20, 0x70, 0x79, 0x20, 0x2b, 0x20, 0x79, 0x74, 0x0d, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x63, 0x61,
+  0x20, 0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x21, 0x3d, 0x20,
+  0x30, 0x20, 0x3f, 0x20, 0x28, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x32, 0x29, 0x20, 0x28, 0x6a, 0x78, 0x31, 0x2c, 0x20, 0x6a, 0x78, 0x32,
+  0x29, 0x29, 0x20, 0x3a, 0x20, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x63, 0x62, 0x20,
+  0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x21, 0x3d, 0x20, 0x30,
+  0x20, 0x3f, 0x20, 0x28, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32,
+  0x29, 0x20, 0x28, 0x6a, 0x79, 0x31, 0x2c, 0x20, 0x6a, 0x79, 0x32, 0x29,
+  0x29, 0x20, 0x3a, 0x20, 0x62, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x0d, 0x0a,
+  0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6e, 0x20, 0x3d, 0x20,
+  0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69, 0x6c,
+  0x65, 0x20, 0x28, 0x6e, 0x20, 0x3c, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x2d,
+  0x20, 0x31, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x61,
+  0x61, 0x20, 0x3d, 0x20, 0x6d, 0x75, 0x6c, 0x28, 0x61, 0x2c, 0x20, 0x61,
+  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x20, 0x62, 0x62, 0x20, 0x3d,
+  0x20, 0x6d, 0x75, 0x6c, 0x28, 0x62, 0x2c, 0x20, 0x62, 0x29, 0x3b, 0x0d,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75,
+  0x62, 0x6c, 0x65, 0x32, 0x20, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x6d, 0x75,
+  0x6c, 0x28, 0x61, 0x2c, 0x20, 0x62, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x32, 0x20, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x62, 0x62, 0x20, 0x3d, 0x20,
+  0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x32, 0x29, 0x28, 0x2d, 0x62,
+  0x62, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x31,
+  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x61, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64, 0x64, 0x28,
+  0x61, 0x61, 0x2c, 0x20, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x62, 0x62, 0x29,
+  0x2c, 0x20, 0x63, 0x61, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28,
+  0x61, 0x64, 0x64, 0x28, 0x61, 0x62, 0x2c, 0x20, 0x61, 0x62, 0x29, 0x2c,
+  0x20, 0x63, 0x62, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x2e, 0x73, 0x30,
+  0x20, 0x2b, 0x20, 0x62, 0x62, 0x2e, 0x73, 0x30, 0x20, 0x3e, 0x20, 0x31,
+  0x36, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0d, 0x0a, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b, 0x2b, 0x3b, 0x0d,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x2f, 0x2f, 0x20, 0x4e, 0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d,
+  0x20, 0x6c, 0x6f, 0x67, 0x20, 0x28, 0x6c, 0x6f, 0x67, 0x20, 0x20, 0x7c,
+  0x5a, 0x28, 0x4e, 0x29, 0x7c, 0x29, 0x20, 0x2f, 0x20, 0x6c, 0x6f, 0x67,
+  0x20, 0x32, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28,
+  0x6e, 0x20, 0x3e, 0x3d, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x2d, 0x20, 0x31,
+  0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41,
+  0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20, 0x6d, 0x61,
+  0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65,
+  0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x69, 0x66, 0x20, 0x28, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x20, 0x21,
+  0x3d, 0x20, 0x30, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65,
+  0x78, 0x5d, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74,
+  0x29, 0x20, 0x6e, 0x29, 0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c,
+  0x6f, 0x67, 0x28, 0x6c, 0x6f, 0x67, 0x28, 0x61, 0x2e, 0x73, 0x30, 0x20,
+  0x2a, 0x20, 0x61, 0x2e, 0x73, 0x30, 0x20, 0x2b, 0x20, 0x62, 0x2e, 0x73,
+  0x30, 0x20, 0x2a, 0x20, 0x62, 0x2e, 0x73, 0x30, 0x29, 0x20, 0x2f, 0x20,
+  0x32, 0x29, 0x20, 0x2f, 0x20, 0x6c, 0x6f, 0x67, 0x28, 0x32, 0x2e, 0x30,
+  0x66, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x65, 0x6c, 0x73, 0x65, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64,
+  0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61,
+  0x74, 0x29, 0x20, 0x6e, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
   0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20, 0x28,
-  0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x20, 0x6e, 0x29, 0x20, 0x2b,
-  0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67, 0x28, 0x6c, 0x6f, 0x67,
-  0x28, 0x61, 0x2e, 0x73, 0x30, 0x20, 0x2a, 0x20, 0x61, 0x2e, 0x73, 0x30,
-  0x20, 0x2b, 0x20, 0x62, 0x2e, 0x73, 0x30, 0x20, 0x2a, 0x20, 0x62, 0x2e,
-  0x73, 0x30, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x29, 0x20, 0x2f, 0x20, 0x6c,
-  0x6f, 0x67, 0x28, 0x32, 0x2e, 0x30, 0x66, 0x29, 0x3b, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41,
-  0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20, 0x28, 0x28,
-  0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29, 0x3b, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20,
-  0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29, 0x20, 0x2b,
-  0x20, 0x31, 0x20, 0x2d, 0x20, 0x28, 0x61, 0x20, 0x2a, 0x20, 0x61, 0x20,
-  0x2b, 0x20, 0x62, 0x20, 0x2a, 0x20, 0x62, 0x20, 0x2d, 0x20, 0x31, 0x36,
-  0x29, 0x20, 0x2f, 0x20, 0x28, 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, 0x31,
-  0x36, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x20,
+  0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29, 0x20, 0x2b, 0x20,
+  0x31, 0x20, 0x2d, 0x20, 0x28, 0x61, 0x20, 0x2a, 0x20, 0x61, 0x20, 0x2b,
+  0x20, 0x62, 0x20, 0x2a, 0x20, 0x62, 0x20, 0x2d, 0x20, 0x31, 0x36, 0x29,
+  0x20, 0x2f, 0x20, 0x28, 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, 0x31, 0x36,
+  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x67,
   0x65, 0x74, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x69, 0x64,
-  0x28, 0x30, 0x29, 0x5d, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x7d
+  0x28, 0x30, 0x29, 0x5d, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0d, 0x0a, 0x7d
 };
-unsigned int doubledouble_cl_len = 2867;
+unsigned int doubledouble_cl_len = 2964;

+ 2 - 2
libmandel/src/opencl/doublefloat.cl

@@ -110,10 +110,10 @@ __kernel void iterate(__global float* A, const int width,
         float2 aa = sq(a);
         float2 bb = sq(b);
         float2 ab = mul(a, b);
-        if (aa.s0 + bb.s0 > 16) break;
         float2 minusbb = (float2)(-bb.s0, -bb.s1);
         a = add(add(aa, minusbb), ca);
         b = add(add(ab, ab), cb);
+        if (aa.s0 + bb.s0 > 16) break;
         n++;
     }
 
@@ -126,4 +126,4 @@ __kernel void iterate(__global float* A, const int width,
         else
             A[index] = ((float)n);
     }
-}
+}

+ 16 - 16
libmandel/src/opencl/doublefloat.h

@@ -259,20 +259,20 @@ unsigned char doublefloat_cl[] = {
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32,
   0x20, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x6d, 0x75, 0x6c, 0x28, 0x61, 0x2c,
   0x20, 0x62, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x2e, 0x73, 0x30, 0x20,
-  0x2b, 0x20, 0x62, 0x62, 0x2e, 0x73, 0x30, 0x20, 0x3e, 0x20, 0x31, 0x36,
-  0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32,
-  0x20, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x62, 0x62, 0x20, 0x3d, 0x20, 0x28,
-  0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x29, 0x28, 0x2d, 0x62, 0x62, 0x2e,
-  0x73, 0x30, 0x2c, 0x20, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x31, 0x29, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20,
-  0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61, 0x61,
-  0x2c, 0x20, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x62, 0x62, 0x29, 0x2c, 0x20,
-  0x63, 0x61, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64,
-  0x64, 0x28, 0x61, 0x62, 0x2c, 0x20, 0x61, 0x62, 0x29, 0x2c, 0x20, 0x63,
-  0x62, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x32, 0x20, 0x6d, 0x69, 0x6e,
+  0x75, 0x73, 0x62, 0x62, 0x20, 0x3d, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61,
+  0x74, 0x32, 0x29, 0x28, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x30, 0x2c, 0x20,
+  0x2d, 0x62, 0x62, 0x2e, 0x73, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x64,
+  0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61, 0x61, 0x2c, 0x20, 0x6d, 0x69,
+  0x6e, 0x75, 0x73, 0x62, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x61, 0x29, 0x3b,
+  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20,
+  0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61, 0x62,
+  0x2c, 0x20, 0x61, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x62, 0x29, 0x3b, 0x0d,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20,
+  0x28, 0x61, 0x61, 0x2e, 0x73, 0x30, 0x20, 0x2b, 0x20, 0x62, 0x62, 0x2e,
+  0x73, 0x30, 0x20, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x62, 0x72, 0x65,
+  0x61, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x6e, 0x2b, 0x2b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
   0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x4e,
   0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x28,
@@ -297,6 +297,6 @@ unsigned char doublefloat_cl[] = {
   0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20,
   0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29, 0x3b, 0x0d,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d
 };
-unsigned int doublefloat_cl_len = 3587;
+unsigned int doublefloat_cl_len = 3585;

+ 2 - 2
libmandel/src/opencl/fixed128.cl

@@ -73,9 +73,9 @@ __kernel void iterate(__global float* A, const int width,
         long2 aa = mul(a, a);
         long2 bb = mul(b, b);
         long2 ab = mul(a, b);
-        if (aa[0] + bb[0] > (16LL << 48)) break;
         a = add(sub(aa, bb), ca);
         b = add(add(ab, ab), cb);
+        if (aa[0] + bb[0] > (16LL << 48)) break;
         n++;
     }
 
@@ -91,4 +91,4 @@ __kernel void iterate(__global float* A, const int width,
         else
             A[index] = ((float)n);
     }
-}
+}

+ 12 - 12
libmandel/src/opencl/fixed128.h

@@ -170,16 +170,16 @@ unsigned char fixed128_cl[] = {
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x32, 0x20,
   0x61, 0x62, 0x20, 0x3d, 0x20, 0x6d, 0x75, 0x6c, 0x28, 0x61, 0x2c, 0x20,
   0x62, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x5b, 0x30, 0x5d, 0x20, 0x2b,
-  0x20, 0x62, 0x62, 0x5b, 0x30, 0x5d, 0x20, 0x3e, 0x20, 0x28, 0x31, 0x36,
-  0x4c, 0x4c, 0x20, 0x3c, 0x3c, 0x20, 0x34, 0x38, 0x29, 0x29, 0x20, 0x62,
-  0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x73,
-  0x75, 0x62, 0x28, 0x61, 0x61, 0x2c, 0x20, 0x62, 0x62, 0x29, 0x2c, 0x20,
-  0x63, 0x61, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64,
-  0x64, 0x28, 0x61, 0x62, 0x2c, 0x20, 0x61, 0x62, 0x29, 0x2c, 0x20, 0x63,
-  0x62, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x73, 0x75, 0x62,
+  0x28, 0x61, 0x61, 0x2c, 0x20, 0x62, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x61,
+  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x62, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64, 0x64, 0x28,
+  0x61, 0x62, 0x2c, 0x20, 0x61, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x62, 0x29,
+  0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69,
+  0x66, 0x20, 0x28, 0x61, 0x61, 0x5b, 0x30, 0x5d, 0x20, 0x2b, 0x20, 0x62,
+  0x62, 0x5b, 0x30, 0x5d, 0x20, 0x3e, 0x20, 0x28, 0x31, 0x36, 0x4c, 0x4c,
+  0x20, 0x3c, 0x3c, 0x20, 0x34, 0x38, 0x29, 0x29, 0x20, 0x62, 0x72, 0x65,
+  0x61, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x6e, 0x2b, 0x2b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
   0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x4e,
   0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x28,
@@ -221,6 +221,6 @@ unsigned char fixed128_cl[] = {
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e,
   0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f,
   0x61, 0x74, 0x29, 0x6e, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
-  0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a
+  0x7d, 0x0d, 0x0a, 0x7d
 };
-unsigned int fixed128_cl_len = 2670;
+unsigned int fixed128_cl_len = 2668;

+ 2 - 2
libmandel/src/opencl/fixed512.cl

@@ -98,9 +98,9 @@ __kernel void iterate(__global float* A, const int width,
         ulong2 aa = mul(a, a);
         ulong2 bb = mul(b, b);
         ulong2 ab = mul(a, b);
-        if (aa.s0 + aa.s1 + bb.s0 + bb.s1 > 16) break;
         a = add(sub(aa, bb), ca);
         b = add(add(ab, ab), cb);
+        if (aa.s0 + aa.s1 + bb.s0 + bb.s1 > 16) break;
         n++;
     }
 
@@ -118,4 +118,4 @@ __kernel void iterate(__global float* A, const int width,
     }
     //               A[index] = ((float)n) + 1 - (a * a + b * b - 16) / (256 - 16);
     //           A[get_global_id(0)] = 5;
-}
+}

+ 12 - 12
libmandel/src/opencl/fixed512.h

@@ -227,16 +227,16 @@ unsigned char fixed512_cl[] = {
   0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75,
   0x6c, 0x6f, 0x6e, 0x67, 0x32, 0x20, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x6d,
   0x75, 0x6c, 0x28, 0x61, 0x2c, 0x20, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61,
-  0x2e, 0x73, 0x30, 0x20, 0x2b, 0x20, 0x61, 0x61, 0x2e, 0x73, 0x31, 0x20,
-  0x2b, 0x20, 0x62, 0x62, 0x2e, 0x73, 0x30, 0x20, 0x2b, 0x20, 0x62, 0x62,
-  0x2e, 0x73, 0x31, 0x20, 0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x62, 0x72,
-  0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x73, 0x75, 0x62,
-  0x28, 0x61, 0x61, 0x2c, 0x20, 0x62, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x61,
-  0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62,
-  0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61,
-  0x62, 0x2c, 0x20, 0x61, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x62, 0x29, 0x3b,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x64,
+  0x64, 0x28, 0x73, 0x75, 0x62, 0x28, 0x61, 0x61, 0x2c, 0x20, 0x62, 0x62,
+  0x29, 0x2c, 0x20, 0x63, 0x61, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28,
+  0x61, 0x64, 0x64, 0x28, 0x61, 0x62, 0x2c, 0x20, 0x61, 0x62, 0x29, 0x2c,
+  0x20, 0x63, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x2e, 0x73, 0x30, 0x20,
+  0x2b, 0x20, 0x61, 0x61, 0x2e, 0x73, 0x31, 0x20, 0x2b, 0x20, 0x62, 0x62,
+  0x2e, 0x73, 0x30, 0x20, 0x2b, 0x20, 0x62, 0x62, 0x2e, 0x73, 0x31, 0x20,
+  0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b,
   0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b, 0x2b,
   0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
   0x20, 0x2f, 0x2f, 0x20, 0x4e, 0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20,
@@ -283,6 +283,6 @@ unsigned char fixed512_cl[] = {
   0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x67, 0x65, 0x74,
   0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x28, 0x30,
-  0x29, 0x5d, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x7d, 0x0a
+  0x29, 0x5d, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x7d
 };
-unsigned int fixed512_cl_len = 3418;
+unsigned int fixed512_cl_len = 3417;

+ 2 - 2
libmandel/src/opencl/fixed64.cl

@@ -26,9 +26,9 @@ __kernel void iterate(__global float* A, const int width,
         long aa = mul(a, a);
         long bb = mul(b, b);
         long ab = mul(a, b);
-        if (aa + bb > (16LL << 48)) break;
         a = aa - bb + ca;
         b = ab + ab + cb;
+        if (aa + bb > (16LL << 48)) break;
         n++;
     }
 
@@ -44,4 +44,4 @@ __kernel void iterate(__global float* A, const int width,
         else
             A[index] = ((float)n);
     }
-}
+}

+ 10 - 10
libmandel/src/opencl/fixed64.h

@@ -68,14 +68,14 @@ unsigned char fixed64_cl[] = {
   0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c,
   0x6f, 0x6e, 0x67, 0x20, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x6d, 0x75, 0x6c,
   0x28, 0x61, 0x2c, 0x20, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x20, 0x2b,
-  0x20, 0x62, 0x62, 0x20, 0x3e, 0x20, 0x28, 0x31, 0x36, 0x4c, 0x4c, 0x20,
-  0x3c, 0x3c, 0x20, 0x34, 0x38, 0x29, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61,
-  0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61,
-  0x20, 0x3d, 0x20, 0x61, 0x61, 0x20, 0x2d, 0x20, 0x62, 0x62, 0x20, 0x2b,
-  0x20, 0x63, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x62, 0x20, 0x2b, 0x20, 0x61, 0x62,
-  0x20, 0x2b, 0x20, 0x63, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x61, 0x20, 0x2d,
+  0x20, 0x62, 0x62, 0x20, 0x2b, 0x20, 0x63, 0x61, 0x3b, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x62,
+  0x20, 0x2b, 0x20, 0x61, 0x62, 0x20, 0x2b, 0x20, 0x63, 0x62, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28,
+  0x61, 0x61, 0x20, 0x2b, 0x20, 0x62, 0x62, 0x20, 0x3e, 0x20, 0x28, 0x31,
+  0x36, 0x4c, 0x4c, 0x20, 0x3c, 0x3c, 0x20, 0x34, 0x38, 0x29, 0x29, 0x20,
+  0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x6e, 0x2b, 0x2b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
   0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x4e, 0x20,
   0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67, 0x20, 0x28, 0x6c,
@@ -115,6 +115,6 @@ unsigned char fixed64_cl[] = {
   0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20,
   0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29,
-  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a
+  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d
 };
-unsigned int fixed64_cl_len = 1402;
+unsigned int fixed64_cl_len = 1401;

+ 18 - 16
libmandel/src/opencl/float.cl

@@ -13,9 +13,9 @@ __kernel void iterate(__global float* A, const int width, float xl, float yt, fl
        float aa = a * a;
        float bb = b * b;
        float ab = a * b;
-       if (aa + bb > 16) break;
        a = aa - bb + ca;
        b = ab + ab + cb;
+       if (aa + bb > 16) break;
        n++;
    }
    if (n >= max - 1) {
@@ -38,22 +38,23 @@ __kernel void iterate_vec4(__global float* A, const int width, float xl, float y
    float4 b = (float4) (y * pixelScaleY + yt);
    float4 ca = julia ? ((float4)(juliaX)) : a;
    float4 cb = julia ? ((float4)(juliaY)) : b;
-   float4 resa = a;
-   float4 resb = b;
-   int4 count = (int4)(0);
+   float4 resa = (float4)(0);
+   float4 resb = (float4)(0);
+   float4 count = (float4)(0);
 
    int n = 0;
    if (smooth) {
+       int4 cmp = isless((float4)(16.0f), (float4)(16.0f));
        while (n < max) {
            float4 ab = a * b;
            float4 cmpVal = fma(a, a, b * b);
-           int4 cmp = isless(cmpVal, (float4)(16.0f));
-           if (!any(cmp)) break;
            a = fma(a, a, -fma(b, b, -ca));
            b = fma(2, ab, cb);
            resa = as_float4((as_int4(a) & cmp) | (as_int4(resa) & ~cmp));
            resb = as_float4((as_int4(b) & cmp) | (as_int4(resb) & ~cmp));
-           count += cmp & (int4)(1);
+           cmp = isless(cmpVal, (float4)(16.0f));
+           if (!any(cmp)) break;
+           count += as_float4(cmp & as_int4((float4)(1)));
            n++;
        }
    }
@@ -61,19 +62,20 @@ __kernel void iterate_vec4(__global float* A, const int width, float xl, float y
        while (n < max) {
            float4 ab = a * b;
            float4 cmpVal = fma(a, a, b * b);
-           int4 cmp = isless(cmpVal, (float4)(16.0f));
-           if (!any(cmp)) break;
            a = fma(a, a, -fma(b, b, -ca));
            b = fma(2, ab, cb);
-           count += cmp & (int4)(1);
+           int4 cmp = isless(cmpVal, (float4)(16.0f));
+           if (!any(cmp)) break;
+           count += as_float4(cmp & as_int4((float4)(1)));
            n++;
        }
     }
-   for (int i = 0; i < 4 && i + x < width; i++) {
-       if (smooth != 0)
-           A[index + i] = ((float) count[i]) + 1 - log(log(fma(resa[i], resa[i], resb[i] * resb[i])) / 2) / log(2.0f);
-      else
-          A[index + i] = ((float) count[i]);
+    for (int i = 0; i < 4 && i + x < width; i++) {
+    if (smooth != 0) {
+        if (count[i] >= 0)
+            A[index + i] = ((float) count[i]) + 1 - log(log(fma(resa[i], resa[i], resb[i] * resb[i])) / 2) / log(2.0f);
+    }
+    else
+        A[index + i] = ((float) count[i]);
    }
 }
-

+ 208 - 202
libmandel/src/opencl/float.h

@@ -1,101 +1,98 @@
 unsigned char float_cl[] = {
-  0x0d, 0x0a, 0x5f, 0x5f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x20, 0x76,
-  0x6f, 0x69, 0x64, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x65, 0x28,
-  0x5f, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x66, 0x6c, 0x6f,
-  0x61, 0x74, 0x2a, 0x20, 0x41, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74,
-  0x20, 0x69, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20,
-  0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, 0x6c, 0x2c, 0x20, 0x66, 0x6c,
-  0x6f, 0x61, 0x74, 0x20, 0x79, 0x74, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61,
-  0x74, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65,
-  0x58, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x69, 0x78,
-  0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x59, 0x2c, 0x20, 0x69, 0x6e,
-  0x74, 0x20, 0x6d, 0x61, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x73,
-  0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6a,
-  0x75, 0x6c, 0x69, 0x61, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20,
-  0x6a, 0x75, 0x6c, 0x69, 0x61, 0x58, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61,
-  0x74, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x59, 0x29, 0x20, 0x7b, 0x0d,
-  0x0a, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x64, 0x65,
-  0x78, 0x20, 0x3d, 0x20, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x6c, 0x6f, 0x62,
-  0x61, 0x6c, 0x5f, 0x69, 0x64, 0x28, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
-  0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e,
-  0x64, 0x65, 0x78, 0x20, 0x25, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d,
-  0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x2f, 0x20, 0x77, 0x69, 0x64,
-  0x74, 0x68, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61,
-  0x74, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x78, 0x20, 0x2a, 0x20, 0x70, 0x69,
-  0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x58, 0x20, 0x2b, 0x20,
-  0x78, 0x6c, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61,
-  0x74, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x79, 0x20, 0x2a, 0x20, 0x70, 0x69,
-  0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x59, 0x20, 0x2b, 0x20,
-  0x79, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61,
-  0x74, 0x20, 0x63, 0x61, 0x20, 0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61,
-  0x20, 0x21, 0x3d, 0x20, 0x30, 0x20, 0x3f, 0x20, 0x6a, 0x75, 0x6c, 0x69,
-  0x61, 0x58, 0x20, 0x3a, 0x20, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
-  0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x63, 0x62, 0x20, 0x3d, 0x20, 0x6a,
-  0x75, 0x6c, 0x69, 0x61, 0x20, 0x21, 0x3d, 0x20, 0x30, 0x20, 0x3f, 0x20,
-  0x6a, 0x75, 0x6c, 0x69, 0x61, 0x59, 0x20, 0x3a, 0x20, 0x62, 0x3b, 0x0d,
-  0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6e, 0x20,
-  0x3d, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69,
-  0x6c, 0x65, 0x20, 0x28, 0x6e, 0x20, 0x3c, 0x20, 0x6d, 0x61, 0x78, 0x20,
-  0x2d, 0x20, 0x31, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x0a, 0x5f, 0x5f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x20, 0x76, 0x6f,
+  0x69, 0x64, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x65, 0x28, 0x5f,
+  0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x66, 0x6c, 0x6f, 0x61,
+  0x74, 0x2a, 0x20, 0x41, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20,
+  0x69, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x66,
+  0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78, 0x6c, 0x2c, 0x20, 0x66, 0x6c, 0x6f,
+  0x61, 0x74, 0x20, 0x79, 0x74, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
+  0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x58,
+  0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x69, 0x78, 0x65,
+  0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x59, 0x2c, 0x20, 0x69, 0x6e, 0x74,
+  0x20, 0x6d, 0x61, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x73, 0x6d,
+  0x6f, 0x6f, 0x74, 0x68, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6a, 0x75,
+  0x6c, 0x69, 0x61, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6a,
+  0x75, 0x6c, 0x69, 0x61, 0x58, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
+  0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x59, 0x29, 0x20, 0x7b, 0x0a, 0x20,
+  0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20,
+  0x3d, 0x20, 0x67, 0x65, 0x74, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
+  0x5f, 0x69, 0x64, 0x28, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x69,
+  0x6e, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78,
+  0x20, 0x25, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a, 0x20, 0x20,
+  0x20, 0x69, 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x64,
+  0x65, 0x78, 0x20, 0x2f, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x61, 0x20, 0x3d,
+  0x20, 0x78, 0x20, 0x2a, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63,
+  0x61, 0x6c, 0x65, 0x58, 0x20, 0x2b, 0x20, 0x78, 0x6c, 0x3b, 0x0a, 0x20,
+  0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x20, 0x3d, 0x20,
+  0x79, 0x20, 0x2a, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61,
+  0x6c, 0x65, 0x59, 0x20, 0x2b, 0x20, 0x79, 0x74, 0x3b, 0x0a, 0x20, 0x20,
+  0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x63, 0x61, 0x20, 0x3d, 0x20,
+  0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x21, 0x3d, 0x20, 0x30, 0x20, 0x3f,
+  0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x58, 0x20, 0x3a, 0x20, 0x61, 0x3b,
+  0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x63, 0x62,
+  0x20, 0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x21, 0x3d, 0x20,
+  0x30, 0x20, 0x3f, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x59, 0x20, 0x3a,
+  0x20, 0x62, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20,
+  0x6e, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x68,
+  0x69, 0x6c, 0x65, 0x20, 0x28, 0x6e, 0x20, 0x3c, 0x20, 0x6d, 0x61, 0x78,
+  0x20, 0x2d, 0x20, 0x31, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x61, 0x61, 0x20,
-  0x3d, 0x20, 0x61, 0x20, 0x2a, 0x20, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62,
-  0x62, 0x20, 0x3d, 0x20, 0x62, 0x20, 0x2a, 0x20, 0x62, 0x3b, 0x0d, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
-  0x20, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x20, 0x2a, 0x20, 0x62, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20,
-  0x28, 0x61, 0x61, 0x20, 0x2b, 0x20, 0x62, 0x62, 0x20, 0x3e, 0x20, 0x31,
-  0x36, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0d, 0x0a, 0x20,
+  0x3d, 0x20, 0x61, 0x20, 0x2a, 0x20, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x62, 0x62,
+  0x20, 0x3d, 0x20, 0x62, 0x20, 0x2a, 0x20, 0x62, 0x3b, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x61,
+  0x62, 0x20, 0x3d, 0x20, 0x61, 0x20, 0x2a, 0x20, 0x62, 0x3b, 0x0a, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x61,
-  0x20, 0x2d, 0x20, 0x62, 0x62, 0x20, 0x2b, 0x20, 0x63, 0x61, 0x3b, 0x0d,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d, 0x20,
-  0x61, 0x62, 0x20, 0x2b, 0x20, 0x61, 0x62, 0x20, 0x2b, 0x20, 0x63, 0x62,
-  0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b,
-  0x2b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20,
-  0x20, 0x69, 0x66, 0x20, 0x28, 0x6e, 0x20, 0x3e, 0x3d, 0x20, 0x6d, 0x61,
-  0x78, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78,
-  0x5d, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
-  0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20,
-  0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66,
+  0x20, 0x2d, 0x20, 0x62, 0x62, 0x20, 0x2b, 0x20, 0x63, 0x61, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x61,
+  0x62, 0x20, 0x2b, 0x20, 0x61, 0x62, 0x20, 0x2b, 0x20, 0x63, 0x62, 0x3b,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28,
+  0x61, 0x61, 0x20, 0x2b, 0x20, 0x62, 0x62, 0x20, 0x3e, 0x20, 0x31, 0x36,
+  0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b, 0x2b, 0x3b, 0x0a, 0x20, 0x20, 0x20,
+  0x7d, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x6e, 0x20, 0x3e,
+  0x3d, 0x20, 0x6d, 0x61, 0x78, 0x20, 0x2d, 0x20, 0x31, 0x29, 0x20, 0x7b,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e,
+  0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x78, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65,
+  0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66,
   0x20, 0x28, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x20, 0x21, 0x3d, 0x20,
-  0x30, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20,
-  0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29,
-  0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67, 0x28, 0x6c,
-  0x6f, 0x67, 0x28, 0x61, 0x20, 0x2a, 0x20, 0x61, 0x20, 0x2b, 0x20, 0x62,
-  0x20, 0x2a, 0x20, 0x62, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x29, 0x20, 0x2f,
-  0x20, 0x6c, 0x6f, 0x67, 0x28, 0x32, 0x2e, 0x30, 0x66, 0x29, 0x3b, 0x0d,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20,
-  0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29, 0x3b, 0x0d,
-  0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
-  0x0d, 0x0a, 0x5f, 0x5f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x20, 0x76,
-  0x6f, 0x69, 0x64, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x65, 0x5f,
-  0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61,
-  0x6c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x2a, 0x20, 0x41, 0x2c, 0x20,
-  0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x77, 0x69,
-  0x64, 0x74, 0x68, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x78,
-  0x6c, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x79, 0x74, 0x2c,
-  0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c,
-  0x53, 0x63, 0x61, 0x6c, 0x65, 0x58, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61,
-  0x74, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65,
-  0x59, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x78, 0x2c, 0x20,
-  0x69, 0x6e, 0x74, 0x20, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x2c, 0x20,
-  0x69, 0x6e, 0x74, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x2c, 0x20, 0x66,
-  0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x58, 0x2c,
-  0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61,
-  0x59, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74,
-  0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, 0x20, 0x67, 0x65, 0x74,
-  0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x28, 0x30,
-  0x29, 0x20, 0x2a, 0x20, 0x34, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x69,
-  0x6e, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78,
-  0x20, 0x25, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0d, 0x0a, 0x20,
-  0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x69, 0x6e,
-  0x64, 0x65, 0x78, 0x20, 0x2f, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20,
+  0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d,
+  0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29, 0x20,
+  0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67, 0x28, 0x6c, 0x6f,
+  0x67, 0x28, 0x61, 0x20, 0x2a, 0x20, 0x61, 0x20, 0x2b, 0x20, 0x62, 0x20,
+  0x2a, 0x20, 0x62, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x29, 0x20, 0x2f, 0x20,
+  0x6c, 0x6f, 0x67, 0x28, 0x32, 0x2e, 0x30, 0x66, 0x29, 0x3b, 0x0a, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b,
+  0x69, 0x6e, 0x64, 0x65, 0x78, 0x5d, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x66,
+  0x6c, 0x6f, 0x61, 0x74, 0x29, 0x6e, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
+  0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x0a, 0x5f, 0x5f, 0x6b, 0x65, 0x72, 0x6e,
+  0x65, 0x6c, 0x20, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x69, 0x74, 0x65, 0x72,
+  0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x63, 0x34, 0x28, 0x5f, 0x5f, 0x67,
+  0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x2a,
+  0x20, 0x41, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x69, 0x6e,
+  0x74, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2c, 0x20, 0x66, 0x6c, 0x6f,
+  0x61, 0x74, 0x20, 0x78, 0x6c, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
+  0x20, 0x79, 0x74, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70,
+  0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x58, 0x2c, 0x20,
+  0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53,
+  0x63, 0x61, 0x6c, 0x65, 0x59, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6d,
+  0x61, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x73, 0x6d, 0x6f, 0x6f,
+  0x74, 0x68, 0x2c, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6a, 0x75, 0x6c, 0x69,
+  0x61, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6a, 0x75, 0x6c,
+  0x69, 0x61, 0x58, 0x2c, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x20, 0x6a,
+  0x75, 0x6c, 0x69, 0x61, 0x59, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
+  0x69, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, 0x20,
+  0x67, 0x65, 0x74, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x69,
+  0x64, 0x28, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x20,
+  0x20, 0x69, 0x6e, 0x74, 0x20, 0x78, 0x20, 0x3d, 0x20, 0x69, 0x6e, 0x64,
+  0x65, 0x78, 0x20, 0x25, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x79, 0x20, 0x3d, 0x20, 0x69,
+  0x6e, 0x64, 0x65, 0x78, 0x20, 0x2f, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
+  0x3b, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20,
   0x61, 0x20, 0x3d, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29,
   0x20, 0x28, 0x78, 0x20, 0x2a, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53,
   0x63, 0x61, 0x6c, 0x65, 0x58, 0x20, 0x2b, 0x20, 0x78, 0x6c, 0x2c, 0x20,
@@ -106,119 +103,128 @@ unsigned char float_cl[] = {
   0x58, 0x20, 0x2b, 0x20, 0x78, 0x6c, 0x2c, 0x20, 0x28, 0x78, 0x20, 0x2b,
   0x20, 0x33, 0x29, 0x20, 0x2a, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53,
   0x63, 0x61, 0x6c, 0x65, 0x58, 0x20, 0x2b, 0x20, 0x78, 0x6c, 0x29, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20,
-  0x62, 0x20, 0x3d, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29,
-  0x20, 0x28, 0x79, 0x20, 0x2a, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53,
-  0x63, 0x61, 0x6c, 0x65, 0x59, 0x20, 0x2b, 0x20, 0x79, 0x74, 0x29, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20,
-  0x63, 0x61, 0x20, 0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x3f,
-  0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29, 0x28, 0x6a,
-  0x75, 0x6c, 0x69, 0x61, 0x58, 0x29, 0x29, 0x20, 0x3a, 0x20, 0x61, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20,
-  0x63, 0x62, 0x20, 0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x3f,
-  0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29, 0x28, 0x6a,
-  0x75, 0x6c, 0x69, 0x61, 0x59, 0x29, 0x29, 0x20, 0x3a, 0x20, 0x62, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20,
-  0x72, 0x65, 0x73, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x3b, 0x0d, 0x0a, 0x20,
-  0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x72, 0x65, 0x73,
-  0x62, 0x20, 0x3d, 0x20, 0x62, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x69,
-  0x6e, 0x74, 0x34, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x3d, 0x20,
-  0x28, 0x69, 0x6e, 0x74, 0x34, 0x29, 0x28, 0x30, 0x29, 0x3b, 0x0d, 0x0a,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x20, 0x6e, 0x20, 0x3d,
-  0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28,
-  0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20,
-  0x28, 0x6e, 0x20, 0x3c, 0x20, 0x6d, 0x61, 0x78, 0x29, 0x20, 0x7b, 0x0d,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x61, 0x62, 0x20, 0x3d, 0x20,
-  0x61, 0x20, 0x2a, 0x20, 0x62, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x62,
+  0x20, 0x3d, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29, 0x20,
+  0x28, 0x79, 0x20, 0x2a, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63,
+  0x61, 0x6c, 0x65, 0x59, 0x20, 0x2b, 0x20, 0x79, 0x74, 0x29, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x63, 0x61,
+  0x20, 0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x3f, 0x20, 0x28,
+  0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29, 0x28, 0x6a, 0x75, 0x6c,
+  0x69, 0x61, 0x58, 0x29, 0x29, 0x20, 0x3a, 0x20, 0x61, 0x3b, 0x0a, 0x20,
+  0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x63, 0x62, 0x20,
+  0x3d, 0x20, 0x6a, 0x75, 0x6c, 0x69, 0x61, 0x20, 0x3f, 0x20, 0x28, 0x28,
+  0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29, 0x28, 0x6a, 0x75, 0x6c, 0x69,
+  0x61, 0x59, 0x29, 0x29, 0x20, 0x3a, 0x20, 0x62, 0x3b, 0x0a, 0x20, 0x20,
+  0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x72, 0x65, 0x73, 0x61,
+  0x20, 0x3d, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29, 0x28,
+  0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
+  0x34, 0x20, 0x72, 0x65, 0x73, 0x62, 0x20, 0x3d, 0x20, 0x28, 0x66, 0x6c,
+  0x6f, 0x61, 0x74, 0x34, 0x29, 0x28, 0x30, 0x29, 0x3b, 0x0a, 0x20, 0x20,
+  0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x63, 0x6f, 0x75, 0x6e,
+  0x74, 0x20, 0x3d, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29,
+  0x28, 0x30, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74,
+  0x20, 0x6e, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x69,
+  0x66, 0x20, 0x28, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x29, 0x20, 0x7b,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x34,
+  0x20, 0x63, 0x6d, 0x70, 0x20, 0x3d, 0x20, 0x69, 0x73, 0x6c, 0x65, 0x73,
+  0x73, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x29, 0x28, 0x31,
+  0x36, 0x2e, 0x30, 0x66, 0x29, 0x2c, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61,
+  0x74, 0x34, 0x29, 0x28, 0x31, 0x36, 0x2e, 0x30, 0x66, 0x29, 0x29, 0x3b,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69, 0x6c,
+  0x65, 0x20, 0x28, 0x6e, 0x20, 0x3c, 0x20, 0x6d, 0x61, 0x78, 0x29, 0x20,
+  0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x61, 0x62, 0x20, 0x3d,
+  0x20, 0x61, 0x20, 0x2a, 0x20, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74,
   0x34, 0x20, 0x63, 0x6d, 0x70, 0x56, 0x61, 0x6c, 0x20, 0x3d, 0x20, 0x66,
   0x6d, 0x61, 0x28, 0x61, 0x2c, 0x20, 0x61, 0x2c, 0x20, 0x62, 0x20, 0x2a,
-  0x20, 0x62, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x34, 0x20, 0x63, 0x6d,
+  0x20, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x66, 0x6d, 0x61, 0x28,
+  0x61, 0x2c, 0x20, 0x61, 0x2c, 0x20, 0x2d, 0x66, 0x6d, 0x61, 0x28, 0x62,
+  0x2c, 0x20, 0x62, 0x2c, 0x20, 0x2d, 0x63, 0x61, 0x29, 0x29, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62,
+  0x20, 0x3d, 0x20, 0x66, 0x6d, 0x61, 0x28, 0x32, 0x2c, 0x20, 0x61, 0x62,
+  0x2c, 0x20, 0x63, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x61, 0x20, 0x3d,
+  0x20, 0x61, 0x73, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x28,
+  0x61, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x34, 0x28, 0x61, 0x29, 0x20, 0x26,
+  0x20, 0x63, 0x6d, 0x70, 0x29, 0x20, 0x7c, 0x20, 0x28, 0x61, 0x73, 0x5f,
+  0x69, 0x6e, 0x74, 0x34, 0x28, 0x72, 0x65, 0x73, 0x61, 0x29, 0x20, 0x26,
+  0x20, 0x7e, 0x63, 0x6d, 0x70, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x62,
+  0x20, 0x3d, 0x20, 0x61, 0x73, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34,
+  0x28, 0x28, 0x61, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x34, 0x28, 0x62, 0x29,
+  0x20, 0x26, 0x20, 0x63, 0x6d, 0x70, 0x29, 0x20, 0x7c, 0x20, 0x28, 0x61,
+  0x73, 0x5f, 0x69, 0x6e, 0x74, 0x34, 0x28, 0x72, 0x65, 0x73, 0x62, 0x29,
+  0x20, 0x26, 0x20, 0x7e, 0x63, 0x6d, 0x70, 0x29, 0x29, 0x3b, 0x0a, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6d,
   0x70, 0x20, 0x3d, 0x20, 0x69, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x28, 0x63,
   0x6d, 0x70, 0x56, 0x61, 0x6c, 0x2c, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61,
   0x74, 0x34, 0x29, 0x28, 0x31, 0x36, 0x2e, 0x30, 0x66, 0x29, 0x29, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x69, 0x66, 0x20, 0x28, 0x21, 0x61, 0x6e, 0x79, 0x28, 0x63, 0x6d,
-  0x70, 0x29, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0d, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61,
-  0x20, 0x3d, 0x20, 0x66, 0x6d, 0x61, 0x28, 0x61, 0x2c, 0x20, 0x61, 0x2c,
-  0x20, 0x2d, 0x66, 0x6d, 0x61, 0x28, 0x62, 0x2c, 0x20, 0x62, 0x2c, 0x20,
-  0x2d, 0x63, 0x61, 0x29, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x66,
-  0x6d, 0x61, 0x28, 0x32, 0x2c, 0x20, 0x61, 0x62, 0x2c, 0x20, 0x63, 0x62,
-  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x73,
-  0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x28, 0x61, 0x73, 0x5f,
-  0x69, 0x6e, 0x74, 0x34, 0x28, 0x61, 0x29, 0x20, 0x26, 0x20, 0x63, 0x6d,
-  0x70, 0x29, 0x20, 0x7c, 0x20, 0x28, 0x61, 0x73, 0x5f, 0x69, 0x6e, 0x74,
-  0x34, 0x28, 0x72, 0x65, 0x73, 0x61, 0x29, 0x20, 0x26, 0x20, 0x7e, 0x63,
-  0x6d, 0x70, 0x29, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x62, 0x20, 0x3d,
-  0x20, 0x61, 0x73, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x28,
-  0x61, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x34, 0x28, 0x62, 0x29, 0x20, 0x26,
-  0x20, 0x63, 0x6d, 0x70, 0x29, 0x20, 0x7c, 0x20, 0x28, 0x61, 0x73, 0x5f,
-  0x69, 0x6e, 0x74, 0x34, 0x28, 0x72, 0x65, 0x73, 0x62, 0x29, 0x20, 0x26,
-  0x20, 0x7e, 0x63, 0x6d, 0x70, 0x29, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x69, 0x66, 0x20, 0x28, 0x21, 0x61, 0x6e, 0x79, 0x28, 0x63, 0x6d, 0x70,
+  0x29, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x75,
-  0x6e, 0x74, 0x20, 0x2b, 0x3d, 0x20, 0x63, 0x6d, 0x70, 0x20, 0x26, 0x20,
-  0x28, 0x69, 0x6e, 0x74, 0x34, 0x29, 0x28, 0x31, 0x29, 0x3b, 0x0d, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e,
-  0x2b, 0x2b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20,
-  0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x28, 0x6e,
-  0x20, 0x3c, 0x20, 0x6d, 0x61, 0x78, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c,
-  0x6f, 0x61, 0x74, 0x34, 0x20, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x20,
-  0x2a, 0x20, 0x62, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20,
-  0x63, 0x6d, 0x70, 0x56, 0x61, 0x6c, 0x20, 0x3d, 0x20, 0x66, 0x6d, 0x61,
-  0x28, 0x61, 0x2c, 0x20, 0x61, 0x2c, 0x20, 0x62, 0x20, 0x2a, 0x20, 0x62,
-  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x34, 0x20, 0x63, 0x6d, 0x70, 0x20,
-  0x3d, 0x20, 0x69, 0x73, 0x6c, 0x65, 0x73, 0x73, 0x28, 0x63, 0x6d, 0x70,
-  0x56, 0x61, 0x6c, 0x2c, 0x20, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34,
-  0x29, 0x28, 0x31, 0x36, 0x2e, 0x30, 0x66, 0x29, 0x29, 0x3b, 0x0d, 0x0a,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69,
-  0x66, 0x20, 0x28, 0x21, 0x61, 0x6e, 0x79, 0x28, 0x63, 0x6d, 0x70, 0x29,
-  0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d,
-  0x20, 0x66, 0x6d, 0x61, 0x28, 0x61, 0x2c, 0x20, 0x61, 0x2c, 0x20, 0x2d,
-  0x66, 0x6d, 0x61, 0x28, 0x62, 0x2c, 0x20, 0x62, 0x2c, 0x20, 0x2d, 0x63,
-  0x61, 0x29, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x66, 0x6d, 0x61,
-  0x28, 0x32, 0x2c, 0x20, 0x61, 0x62, 0x2c, 0x20, 0x63, 0x62, 0x29, 0x3b,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x2b, 0x3d, 0x20, 0x63, 0x6d,
-  0x70, 0x20, 0x26, 0x20, 0x28, 0x69, 0x6e, 0x74, 0x34, 0x29, 0x28, 0x31,
-  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x6e, 0x2b, 0x2b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
-  0x0d, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x69, 0x6e,
-  0x74, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x20, 0x69, 0x20, 0x3c,
-  0x20, 0x34, 0x20, 0x26, 0x26, 0x20, 0x69, 0x20, 0x2b, 0x20, 0x78, 0x20,
-  0x3c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3b, 0x20, 0x69, 0x2b, 0x2b,
-  0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x69, 0x66, 0x20, 0x28, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x20, 0x21,
-  0x3d, 0x20, 0x30, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78,
-  0x20, 0x2b, 0x20, 0x69, 0x5d, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c,
-  0x6f, 0x61, 0x74, 0x29, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5b, 0x69,
-  0x5d, 0x29, 0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67,
-  0x28, 0x6c, 0x6f, 0x67, 0x28, 0x66, 0x6d, 0x61, 0x28, 0x72, 0x65, 0x73,
-  0x61, 0x5b, 0x69, 0x5d, 0x2c, 0x20, 0x72, 0x65, 0x73, 0x61, 0x5b, 0x69,
-  0x5d, 0x2c, 0x20, 0x72, 0x65, 0x73, 0x62, 0x5b, 0x69, 0x5d, 0x20, 0x2a,
-  0x20, 0x72, 0x65, 0x73, 0x62, 0x5b, 0x69, 0x5d, 0x29, 0x29, 0x20, 0x2f,
-  0x20, 0x32, 0x29, 0x20, 0x2f, 0x20, 0x6c, 0x6f, 0x67, 0x28, 0x32, 0x2e,
-  0x30, 0x66, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x65, 0x6c, 0x73, 0x65, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x41, 0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20,
-  0x2b, 0x20, 0x69, 0x5d, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f,
-  0x61, 0x74, 0x29, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5b, 0x69, 0x5d,
-  0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d,
-  0x0a, 0x0d, 0x0a
+  0x6e, 0x74, 0x20, 0x2b, 0x3d, 0x20, 0x61, 0x73, 0x5f, 0x66, 0x6c, 0x6f,
+  0x61, 0x74, 0x34, 0x28, 0x63, 0x6d, 0x70, 0x20, 0x26, 0x20, 0x61, 0x73,
+  0x5f, 0x69, 0x6e, 0x74, 0x34, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74,
+  0x34, 0x29, 0x28, 0x31, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b, 0x2b, 0x3b,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20,
+  0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20,
+  0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69,
+  0x6c, 0x65, 0x20, 0x28, 0x6e, 0x20, 0x3c, 0x20, 0x6d, 0x61, 0x78, 0x29,
+  0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x34, 0x20, 0x61, 0x62, 0x20,
+  0x3d, 0x20, 0x61, 0x20, 0x2a, 0x20, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61,
+  0x74, 0x34, 0x20, 0x63, 0x6d, 0x70, 0x56, 0x61, 0x6c, 0x20, 0x3d, 0x20,
+  0x66, 0x6d, 0x61, 0x28, 0x61, 0x2c, 0x20, 0x61, 0x2c, 0x20, 0x62, 0x20,
+  0x2a, 0x20, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x66, 0x6d, 0x61,
+  0x28, 0x61, 0x2c, 0x20, 0x61, 0x2c, 0x20, 0x2d, 0x66, 0x6d, 0x61, 0x28,
+  0x62, 0x2c, 0x20, 0x62, 0x2c, 0x20, 0x2d, 0x63, 0x61, 0x29, 0x29, 0x3b,
+  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x62, 0x20, 0x3d, 0x20, 0x66, 0x6d, 0x61, 0x28, 0x32, 0x2c, 0x20, 0x61,
+  0x62, 0x2c, 0x20, 0x63, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x74, 0x34, 0x20,
+  0x63, 0x6d, 0x70, 0x20, 0x3d, 0x20, 0x69, 0x73, 0x6c, 0x65, 0x73, 0x73,
+  0x28, 0x63, 0x6d, 0x70, 0x56, 0x61, 0x6c, 0x2c, 0x20, 0x28, 0x66, 0x6c,
+  0x6f, 0x61, 0x74, 0x34, 0x29, 0x28, 0x31, 0x36, 0x2e, 0x30, 0x66, 0x29,
+  0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x21, 0x61, 0x6e, 0x79, 0x28, 0x63,
+  0x6d, 0x70, 0x29, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63,
+  0x6f, 0x75, 0x6e, 0x74, 0x20, 0x2b, 0x3d, 0x20, 0x61, 0x73, 0x5f, 0x66,
+  0x6c, 0x6f, 0x61, 0x74, 0x34, 0x28, 0x63, 0x6d, 0x70, 0x20, 0x26, 0x20,
+  0x61, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x34, 0x28, 0x28, 0x66, 0x6c, 0x6f,
+  0x61, 0x74, 0x34, 0x29, 0x28, 0x31, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b,
+  0x2b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a,
+  0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
+  0x72, 0x20, 0x28, 0x69, 0x6e, 0x74, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x30,
+  0x3b, 0x20, 0x69, 0x20, 0x3c, 0x20, 0x34, 0x20, 0x26, 0x26, 0x20, 0x69,
+  0x20, 0x2b, 0x20, 0x78, 0x20, 0x3c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
+  0x3b, 0x20, 0x69, 0x2b, 0x2b, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
+  0x20, 0x69, 0x66, 0x20, 0x28, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x20,
+  0x21, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x63, 0x6f, 0x75, 0x6e,
+  0x74, 0x5b, 0x69, 0x5d, 0x20, 0x3e, 0x3d, 0x20, 0x30, 0x29, 0x0a, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41,
+  0x5b, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x2b, 0x20, 0x69, 0x5d, 0x20,
+  0x3d, 0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x20, 0x63,
+  0x6f, 0x75, 0x6e, 0x74, 0x5b, 0x69, 0x5d, 0x29, 0x20, 0x2b, 0x20, 0x31,
+  0x20, 0x2d, 0x20, 0x6c, 0x6f, 0x67, 0x28, 0x6c, 0x6f, 0x67, 0x28, 0x66,
+  0x6d, 0x61, 0x28, 0x72, 0x65, 0x73, 0x61, 0x5b, 0x69, 0x5d, 0x2c, 0x20,
+  0x72, 0x65, 0x73, 0x61, 0x5b, 0x69, 0x5d, 0x2c, 0x20, 0x72, 0x65, 0x73,
+  0x62, 0x5b, 0x69, 0x5d, 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x62, 0x5b,
+  0x69, 0x5d, 0x29, 0x29, 0x20, 0x2f, 0x20, 0x32, 0x29, 0x20, 0x2f, 0x20,
+  0x6c, 0x6f, 0x67, 0x28, 0x32, 0x2e, 0x30, 0x66, 0x29, 0x3b, 0x0a, 0x20,
+  0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73,
+  0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x5b,
+  0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x2b, 0x20, 0x69, 0x5d, 0x20, 0x3d,
+  0x20, 0x28, 0x28, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x29, 0x20, 0x63, 0x6f,
+  0x75, 0x6e, 0x74, 0x5b, 0x69, 0x5d, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
+  0x7d, 0x0a, 0x7d, 0x0a
 };
-unsigned int float_cl_len = 2643;
+unsigned int float_cl_len = 2716;

+ 1 - 1
libmandel/src/opencl/quaddouble.cl

@@ -231,10 +231,10 @@ __kernel void iterate(__global float* A, const int width,
         double4 aa = sq(a);
         double4 bb = sq(b);
         double4 ab = mul(a, b);
-        if (aa.s0 + bb.s0 > 16) break;
         double4 minusbb = (double4)(-bb.s0, -bb.s1, -bb.s2, -bb.s3);
         a = add(add(aa, minusbb), ca);
         b = add(add(ab, ab), cb);
+        if (aa.s0 + bb.s0 > 16) break;
         n++;
     }
 

+ 15 - 15
libmandel/src/opencl/quaddouble.h

@@ -490,21 +490,21 @@ unsigned char quaddouble_cl[] = {
   0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f,
   0x75, 0x62, 0x6c, 0x65, 0x34, 0x20, 0x61, 0x62, 0x20, 0x3d, 0x20, 0x6d,
   0x75, 0x6c, 0x28, 0x61, 0x2c, 0x20, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61,
-  0x2e, 0x73, 0x30, 0x20, 0x2b, 0x20, 0x62, 0x62, 0x2e, 0x73, 0x30, 0x20,
-  0x3e, 0x20, 0x31, 0x36, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75,
-  0x62, 0x6c, 0x65, 0x34, 0x20, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x62, 0x62,
-  0x20, 0x3d, 0x20, 0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x34, 0x29,
-  0x28, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x2d, 0x62, 0x62,
-  0x2e, 0x73, 0x31, 0x2c, 0x20, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x32, 0x2c,
-  0x20, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20,
-  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x64,
-  0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61, 0x61, 0x2c, 0x20, 0x6d, 0x69,
-  0x6e, 0x75, 0x73, 0x62, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x61, 0x29, 0x3b,
-  0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d,
-  0x20, 0x61, 0x64, 0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61, 0x62, 0x2c,
-  0x20, 0x61, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x62, 0x29, 0x3b, 0x0a, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+  0x34, 0x20, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x62, 0x62, 0x20, 0x3d, 0x20,
+  0x28, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x34, 0x29, 0x28, 0x2d, 0x62,
+  0x62, 0x2e, 0x73, 0x30, 0x2c, 0x20, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x31,
+  0x2c, 0x20, 0x2d, 0x62, 0x62, 0x2e, 0x73, 0x32, 0x2c, 0x20, 0x2d, 0x62,
+  0x62, 0x2e, 0x73, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x61, 0x20, 0x3d, 0x20, 0x61, 0x64, 0x64, 0x28, 0x61,
+  0x64, 0x64, 0x28, 0x61, 0x61, 0x2c, 0x20, 0x6d, 0x69, 0x6e, 0x75, 0x73,
+  0x62, 0x62, 0x29, 0x2c, 0x20, 0x63, 0x61, 0x29, 0x3b, 0x0a, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x20, 0x3d, 0x20, 0x61, 0x64,
+  0x64, 0x28, 0x61, 0x64, 0x64, 0x28, 0x61, 0x62, 0x2c, 0x20, 0x61, 0x62,
+  0x29, 0x2c, 0x20, 0x63, 0x62, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
+  0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x61, 0x2e, 0x73,
+  0x30, 0x20, 0x2b, 0x20, 0x62, 0x62, 0x2e, 0x73, 0x30, 0x20, 0x3e, 0x20,
+  0x31, 0x36, 0x29, 0x20, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3b, 0x0a, 0x20,
   0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x2b, 0x2b, 0x3b, 0x0a,
   0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f,
   0x2f, 0x20, 0x4e, 0x20, 0x2b, 0x20, 0x31, 0x20, 0x2d, 0x20, 0x6c, 0x6f,

+ 15 - 11
mandelvid/src/main.cpp

@@ -19,7 +19,7 @@ int main() {
         mnd::Real("1.2246019034401093377903721086780361028058704962292211685926779200766324399350798858672587301860274703389823933260119617558370004128301410779021141722617e-10")
     };*/
     evi.end = mnd::MandelViewport {
-        mnd::Real("-2.0"),
+        mnd::Real("-1.0"),
         mnd::Real("-1.0"),
         mnd::Real("1.0e-3"),
         mnd::Real("1.0e-3")
@@ -27,7 +27,7 @@ int main() {
     //evi.end.zoomCenter(1.0e+27);
     evi.gradient = Gradient::defaultGradient();
 
-    evi.mi.bWidth = 1280;
+    /*evi.mi.bWidth = 1280;
     evi.mi.bHeight = 720;
     evi.mi.maxIter = 1000;
     evi.fps = 60;
@@ -40,24 +40,28 @@ int main() {
 
     MandelVideoGenerator mvg(evi);
 
-    mvg.generate(mndCtxt.getDefaultGenerator());
-    //
+    mvg.generate(mndCtxt.getDefaultGenerator());*/
     
 
-/*
     mnd::MandelContext mc = mnd::initializeContext();
     mnd::MandelInfo mi;
     mi.view = evi.start;
-    mi.bWidth = 8000;
-    mi.bHeight = 8000;
-    mi.maxIter = 100;
+    mi.bWidth = 12000;
+    mi.bHeight = 12000;
+    mi.maxIter = 800;
     mi.smooth = true;
     alm::ImageExportInfo iei;
     iei.drawInfo = mi;
-    iei.gradient = &evi.gradient;
+    iei.gradient = evi.gradient;
     iei.generator = &mc.getDefaultGenerator();
-    alm::exportPng("file.png", iei);
-*/
+    iei.options.jpegQuality = 100;
+    iei.path = "file.jpg";
+    try {
+        alm::exportImage(iei);
+    } catch (alm::ImageExportException& iee) {
+        printf("%s\n", iee.what());
+        return 1;
+    }
 
     return 0;
 }