Ver código fonte

better gradienting

Nicolas Winkler 4 anos atrás
pai
commit
ac7bb5997a

+ 33 - 84
Almond.cpp

@@ -17,18 +17,16 @@ Almond::Almond(QWidget* parent) :
     mandelContext{ mnd::initializeContext() }
 {
     ui.setupUi(this);
-    //mw = std::make_unique<MandelWidget>(mandelContext,
-    //                                    &mandelContext.getDefaultGenerator(),
-    //                                    ui.centralWidget);
     fractalWidget = new FractalWidget(this);
     fractalWidget->setGenerator(&mandelContext.getDefaultGenerator());
     fractalWidget->setGradient(Gradient::defaultGradient());
+    fractalWidget->setSmoothColoring(ui.smooth->isChecked());
+
     customGeneratorDialog = std::make_unique<CustomGenerator>(mandelContext);
     customGenerator = nullptr;
     customViewSave = mnd::MandelViewport::centerView();
 
     on_maxIterations_editingFinished();
-    fractalWidget->setSmoothColoring(ui.smooth->isChecked());
 
     currentView = MANDELBROT;
     mandelGenerator = &mandelContext.getDefaultGenerator();
@@ -57,34 +55,11 @@ Almond::Almond(QWidget* parent) :
     connect(imageSm, &AlmondSubMenu::accepted, this, &Almond::imageExportOk);
     connect(videoSm, &AlmondSubMenu::accepted, this, &Almond::videoExportOk);
     connect(gradientSm, &AlmondSubMenu::accepted, this, &Almond::gradientEditOk);
-
-
+    connect(gradientSm, &AlmondSubMenu::cancelled, [this] () {
+        fractalWidget->setGradient(gradientMenu->getGradientBefore());
+    });
     connect(gradientMenu, &GradientMenu::gradientChanged, [this] () {
-
-        std::vector<std::pair<RGBColor, float>> np;
-        const auto& points = gradientMenu->getGradient();
-        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 grad{ np, true };
-        fractalWidget->setGradient(grad);
+        fractalWidget->setGradient(gradientMenu->getGradient());
     });
 
 
@@ -131,13 +106,14 @@ void Almond::submitBackgroundTask(BackgroundTask* task)
 {
     QObject::connect(task, &BackgroundTask::finished, this, &Almond::backgroundTaskFinished);
     QObject::connect(task, &BackgroundTask::progress, this, &Almond::backgroundTaskProgress);
+    int running = backgroundTasks.activeThreadCount();
     backgroundTasks.start(task);
-    //if (taken) {
+    if (running == 0) {
         ui.backgroundProgress->setRange(0, 0);
         ui.backgroundProgress->setFormat("");
         ui.backgroundProgress->setEnabled(true);
         ui.cancelProgress->setEnabled(true);
-    //}
+    }
 }
 
 
@@ -174,24 +150,25 @@ void Almond::imageExportOk(void)
 {
     mnd::MandelInfo mi;
     mi.maxIter = eim->getMaxIterations();
-    mi.view = mnd::MandelViewport{};// mw->getViewport(); // TODO update
+    mi.view = fractalWidget->getViewport();
     mi.bWidth = eim->getWidth();
     mi.bHeight = eim->getHeight();
     mi.view.adjustAspectRatio(mi.bWidth, mi.bHeight);
-    mi.smooth = true; // TODO update
-    /*if (currentView == JULIA) {
-        mi.julia = mw->getMandelInfo().julia;
-        mi.juliaX = mw->getJuliaX();
-        mi.juliaY = mw->getJuliaY();
-    }*/
+    mi.smooth = true;
+    if (currentView == JULIA) {
+        mi.julia = fractalWidget->getMandelInfo().julia;
+        mi.juliaX = fractalWidget->getMandelInfo().juliaX;
+        mi.juliaY = fractalWidget->getMandelInfo().juliaY;
+    }
 
-    mnd::MandelGenerator* currentGenerator = nullptr;// mw->getGenerator();// TODO update
-    mnd::MandelGenerator& g = currentGenerator ? *currentGenerator : mandelContext.getDefaultGenerator();
+    mnd::MandelGenerator* currentGenerator = fractalWidget->getGenerator();
+    if (currentGenerator == nullptr)
+        currentGenerator = &mandelContext.getDefaultGenerator();
 
     alm::ImageExportInfo iei;
     iei.drawInfo = mi;
-    iei.generator = &g;
-    iei.gradient = Gradient::defaultGradient(); // mw->getGradient();// TODO update
+    iei.generator = currentGenerator;
+    iei.gradient = fractalWidget->getGradient();
     iei.path = eim->getPath().toStdString();
     iei.options.jpegQuality = 95;
     submitBackgroundTask(new ImageExportTask(iei, [this] () { return stoppingBackgroundTasks; }));
@@ -202,14 +179,13 @@ void Almond::imageExportOk(void)
 
 void Almond::videoExportOk(void)
 {
-    /*
     ExportVideoInfo evi = evm->getInfo();
-    evi.gradient = mw->getGradient();
-    evi.mi.smooth = mw->getSmoothColoring();
+    evi.gradient = fractalWidget->getGradient();
+    evi.mi.smooth = fractalWidget->getMandelInfo().smooth;
     if (currentView == JULIA) {
-        evi.mi.julia = mw->getMandelInfo().julia;
-        evi.mi.juliaX = mw->getMandelInfo().juliaX;
-        evi.mi.juliaY = mw->getMandelInfo().juliaY;
+        evi.mi.julia = fractalWidget->getMandelInfo().julia;
+        evi.mi.juliaX = fractalWidget->getMandelInfo().juliaX;
+        evi.mi.juliaY = fractalWidget->getMandelInfo().juliaY;
     }
     if (evi.path == "") {
         QMessageBox errMsg = QMessageBox(QMessageBox::Icon::Critical, "Error", "No path specified.");
@@ -217,45 +193,18 @@ void Almond::videoExportOk(void)
     }
     else {
         MandelVideoGenerator mvg(evi);
-        mnd::MandelGenerator& g = *mw->getGenerator();
-        printf("wii: %ld\n", evi.mi.bWidth);
+        mnd::MandelGenerator& g = *fractalWidget->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 };
-    fractalWidget->setGradient(std::move(g));
+    fractalWidget->setGradient(gradientMenu->getGradient());
     amw->showMainMenu();
 }
 
@@ -342,7 +291,7 @@ void Almond::on_maxIterations_editingFinished()
 
 void Almond::on_chooseGradient_clicked()
 {
-    const auto& gradient = fractalWidget->getGradient(); // TODO update
+    /*const auto& gradient = fractalWidget->getGradient(); // TODO update
     auto points = gradient.getPoints();
     std::for_each(points.begin(), points.end(), [](auto& x) { x.second /= 300; });
 
@@ -351,8 +300,8 @@ void Almond::on_chooseGradient_clicked()
         [](auto& qp) -> QPair<float, QColor> {
         auto& [col, pos] = qp;
         return { pos, QColor{ (col.r), (col.g), (col.b) } };
-    });
-    this->gradientMenu->setGradient(std::move(np));
+    });*/
+    this->gradientMenu->setGradient(fractalWidget->getGradient());
     emit this->amw->showSubMenu(2);
     //gcd.exec();
     //auto gradient = gcd.getGradient();
@@ -413,7 +362,7 @@ void Almond::on_resetZoom_clicked()
 
 void Almond::on_displayInfo_stateChanged(int checked)
 {
-    // TODO update this->mw->setDisplayInfo(checked != Qt::Unchecked);
+    this->fractalWidget->setDisplayInfo(checked != Qt::Unchecked);
 }
 
 

+ 61 - 1
FractalWidget.cpp

@@ -2,6 +2,9 @@
 #include <QMouseEvent>
 
 
+#include <QPainter>
+
+
 
 Q_DECLARE_METATYPE(mnd::MandelViewport)
 
@@ -172,10 +175,19 @@ const mnd::MandelViewport& FractalWidget::getViewport(void) const
 }
 
 
+void FractalWidget::setDisplayInfo(bool displayInfo)
+{
+    if (displayInfo != this->displayInfo) {
+        this->displayInfo = displayInfo;
+        update();
+    }
+}
+
+
 void FractalWidget::resizeGL(int w, int h)
 {
     FractalZoomWidget::resizeGL(w, h);
-    targetViewport.adjustAspectRatio(w, h);
+    targetViewport.height = targetViewport.width * h / w;
 }
 
 
@@ -183,6 +195,54 @@ void FractalWidget::paintGL(void)
 {
     FractalZoomWidget::paintGL();
     updateAnimations();
+
+    if (displayInfo)
+        drawDisplayInfo();
+}
+
+
+void FractalWidget::drawDisplayInfo(void)
+{
+    QPainter infoPainter{ this };
+
+    const float DIST_FROM_BORDER = 15;
+    float maxWidth = this->width() - 2 * DIST_FROM_BORDER;
+    mnd::Real distPerPixel = getViewport().width / this->width();
+    float log10 = (mnd::convert<float>(mnd::log(distPerPixel)) + ::logf(maxWidth)) / ::logf(10);
+    mnd::Real displayDist = mnd::pow(mnd::Real(10), ::floor(log10));
+    float pixels = mnd::convert<float>(displayDist / distPerPixel);
+    int factor = 1;
+    for (int i = 9; i > 1; i--) {
+        if (pixels * i < maxWidth) {
+            factor *= i;
+            pixels *= i;
+            displayDist *= i;
+            break;
+        }
+    }
+
+    std::stringstream dis;
+    if (::abs(log10) < 3) {
+        dis << mnd::convert<float>(displayDist);
+    }
+    else {
+        dis << factor << "e" << int(::floor(log10));
+    }
+
+    if (maxWidth > 400) {
+        dis << "; per pixel: " << distPerPixel;
+    }
+
+    float lineY = this->height() - DIST_FROM_BORDER;
+    float lineXEnd = DIST_FROM_BORDER + pixels;
+
+    infoPainter.setPen(Qt::white);
+    infoPainter.setFont(QFont("Arial", 12));
+    infoPainter.drawLine(QPointF{ DIST_FROM_BORDER, lineY }, QPointF{ lineXEnd, lineY });
+    infoPainter.drawLine(QPointF{ DIST_FROM_BORDER, lineY }, QPointF{ DIST_FROM_BORDER, lineY - 5 });
+    infoPainter.drawLine(QPointF{ lineXEnd, lineY }, QPointF{ lineXEnd, lineY - 5 });
+    infoPainter.drawText(int(DIST_FROM_BORDER), int(lineY - 20), int(lineXEnd - DIST_FROM_BORDER), 20,
+                         Qt::AlignCenter, QString::fromStdString(dis.str()));
 }
 
 

+ 6 - 0
FractalWidget.h

@@ -27,6 +27,8 @@ class FractalWidget :
     bool selectingPoint = false;
     float pointX, pointY;
 
+    bool displayInfo = false;
+
     /// the target of an ongoing animation
     mnd::MandelViewport targetViewport;
     std::chrono::time_point<std::chrono::high_resolution_clock> lastAnimUpdate;
@@ -44,9 +46,13 @@ public:
     virtual void setViewport(const mnd::MandelViewport& viewport) override;
     virtual const mnd::MandelViewport& getViewport(void) const override;
 
+    void setDisplayInfo(bool displayInfo);
+
     virtual void resizeGL(int w, int h) override;
     virtual void paintGL(void) override;
 
+    void drawDisplayInfo(void);
+
 private:
     void newAnimation(void);
     void updateAnimations(void);

+ 7 - 9
FractalZoomWidget.cpp

@@ -383,7 +383,7 @@ void FractalZoomWidget::initializeGL(void)
 void FractalZoomWidget::resizeGL(int w, int h)
 {
     EscapeTimeVisualWidget::resizeGL(w, h);
-    mandelInfo.view.adjustAspectRatio(w, h);
+    mandelInfo.view.height = mandelInfo.view.width * h / w;
 }
 
 
@@ -441,17 +441,15 @@ void FractalZoomWidget::paintGL(void)
             }
 
             if (t != nullptr) {
-                t->img->drawRect(float(x), float(y), float(w), float(w));
-                /*glBegin(GL_LINE_LOOP);
-                glVertex2f(float(x), float(y));
-                glVertex2f(float(x) + float(w), float(y));
-                glVertex2f(float(x) + float(w), float(y) + float(w));
-                glVertex2f(float(x), float(y) + float(w));
-                glEnd();*/
-
                 if (!t->enoughResolution) {
+                    auto under = searchUnder(level, i, j, 1);
+                    if (under) {
+                        t = under;
+                    }
                     calcer.calc(grid, level, i, j, t->img->getRecalcPriority());
                 }
+
+                t->img->drawRect(float(x), float(y), float(w), float(w));
             }
             else {
                 calcer.calc(grid, level, i, j, 1000);

+ 13 - 5
GradientMenu.cpp

@@ -7,9 +7,9 @@ GradientMenu::GradientMenu(QWidget *parent) :
 {
     ui->setupUi(this);
     ui->gradientWidget->setGradient(
-        {
-            { 0.1, QColor{ 10, 200, 20 } },
-            { 0.7, QColor{ 100, 20, 120 } }
+        std::vector<std::pair<RGBColor, float>> {
+            { RGBColor{ 10, 200, 20 }, 0.1 },
+            { RGBColor{ 100, 20, 120 }, 0.7 }
         }
     );
     connect(ui->gradientWidget, &GradientWidget::gradientChanged, this, &GradientMenu::gradientChanged);
@@ -22,12 +22,20 @@ GradientMenu::~GradientMenu()
 }
 
 
-const QVector<QPair<float, QColor>>& GradientMenu::getGradient(void)
+const Gradient& GradientMenu::getGradient(void)
 {
     return ui->gradientWidget->getGradient();
 }
 
-void GradientMenu::setGradient(QVector<QPair<float, QColor>> grad)
+
+const Gradient& GradientMenu::getGradientBefore(void) const
+{
+    return before;
+}
+
+
+void GradientMenu::setGradient(Gradient grad)
 {
+    before = grad;
     ui->gradientWidget->setGradient(std::move(grad));
 }

+ 6 - 2
GradientMenu.h

@@ -5,6 +5,8 @@
 #include <QVector>
 #include <QPair>
 
+#include "Gradient.h"
+
 namespace Ui {
 class GradientMenu;
 }
@@ -14,12 +16,14 @@ class GradientMenu : public QWidget
     Q_OBJECT
 
     Ui::GradientMenu *ui;
+    Gradient before;
 public:
     explicit GradientMenu(QWidget *parent = nullptr);
     ~GradientMenu(void);
 
-    const QVector<QPair<float, QColor>>& getGradient(void);
-    void setGradient(QVector<QPair<float, QColor>> grad);
+    const Gradient& getGradient(void);
+    const Gradient& getGradientBefore(void) const;
+    void setGradient(Gradient grad);
 
 signals:
     void gradientChanged(void);

+ 94 - 32
GradientWidget.cpp

@@ -16,19 +16,32 @@ GradientWidget::GradientWidget(QWidget* parent) :
     selectedHandle = -1;
     mouseOver = -1;
 
+    maxValue = 1.0f;
+
+    colorPicker = new QColorDialog(this);
+    colorPicker->setOption(QColorDialog::NoButtons);
+    connect(colorPicker, &QColorDialog::currentColorChanged, this, &GradientWidget::selectedColorChanged);
+
     setMouseTracking(true);
 }
 
 
-const QVector<QPair<float, QColor>>& GradientWidget::getGradient(void) const
+/*const QVector<QPair<float, QColor>>& GradientWidget::getGradient(void) const
 {
     return points;
+}*/
+
+const Gradient& GradientWidget::getGradient(void) const
+{
+    return gradient;
 }
 
 
-void GradientWidget::setGradient(QVector<QPair<float, QColor>> vec)
+void GradientWidget::setGradient(Gradient gr)
 {
-    points = std::move(vec);
+    gradient = std::move(gr);
+    points = gradient.getPoints();
+    maxValue = gradient.getMax();
 }
 
 
@@ -64,6 +77,12 @@ QColor lerp(const QColor& a, const QColor& b, float v)
 }
 
 
+void GradientWidget::updateGradient(void)
+{
+    gradient = Gradient{ points, maxValue };
+}
+
+
 QColor GradientWidget::colorAtY(float y)
 {
     float v = handleYToGradVal(y);
@@ -71,14 +90,14 @@ QColor GradientWidget::colorAtY(float y)
     QColor down = QColor(QColor::Invalid);
     float upv = 0;
     float downv = 1;
-    for (const auto& [val, color] : points) {
+    for (const auto& [color, val] : points) {
         if (val >= upv && val < v) {
             upv = val;
-            up = color;
+            up = QColor(color.r, color.g, color.b);
         }
         if (val <= downv && val > v) {
             downv = val;
-            down = color;
+            down = QColor(color.r, color.g, color.b);
         }
     }
 
@@ -89,11 +108,11 @@ QColor GradientWidget::colorAtY(float y)
     return lerp(up, down, (v - upv) / (downv - upv));
 }
 
+
 void GradientWidget::paintEvent(QPaintEvent* e)
 {
     QPainter painter{ this };
 
-
     QRect gradientRect = getGradientRect();
     QStyleOption frameOptions;
     frameOptions.init(this);
@@ -108,9 +127,19 @@ void GradientWidget::paintEvent(QPaintEvent* e)
         fhmargins = fvmargins = 3;
     }
 
-    QLinearGradient gradient;
-    gradient.setStart(0, gradientRect.top());
-    gradient.setFinalStop(0, gradientRect.bottom());
+    QLinearGradient linGrad;
+    linGrad.setStart(0, gradientRect.top());
+    linGrad.setFinalStop(0, gradientRect.bottom());
+
+    const int stops = this->height() / 5;
+    /*for (int i = 0; i < stops; i++) {
+        auto col = gradient.get(float(i) / stops * gradient.getMax());
+        linGrad.setColorAt(float(i) / stops, QColor{ col.r, col.g, col.b });
+    }*/
+
+    for (const auto& [col, at] : points) {
+        linGrad.setColorAt(at / maxValue, QColor{ col.r, col.g, col.b });
+    }
 
     // adjust rect to have small margins, so the frame
     // around the gradient is visible
@@ -120,7 +149,7 @@ void GradientWidget::paintEvent(QPaintEvent* e)
 
 
 
-    std::vector<int> orderedIndices(points.size());
+    /*std::vector<int> orderedIndices(points.size());
     for (int i = 0; i < points.size(); i++)
         orderedIndices.push_back(i);
 
@@ -145,13 +174,13 @@ void GradientWidget::paintEvent(QPaintEvent* e)
         gradient.setColorAt(point, color);
         lastPoint = point;
         lastColor = color;
-    }
+    }*/
 
-    QBrush brush{ gradient };
+    QBrush brush{ linGrad };
     painter.fillRect(gradientRect, brush);
 
     int index = 0;
-    for (auto& [point, color] : points) {
+    for (auto& [color, point] : points) {
         QRect r = getHandleRect(index);
         /*QStyleOptionButton so;
         so.init(this);
@@ -176,7 +205,7 @@ void GradientWidget::paintEvent(QPaintEvent* e)
             hs |= HANDLE_MOUSEOVER;
         if (selectedHandle == index)
             hs |= HANDLE_SELECTED;
-        paintHandle(painter, r, color, hs);
+        paintHandle(painter, r, fromRGB(color), hs);
         index++;
     }
     /*for (auto&[point, color] : points) {
@@ -247,7 +276,7 @@ void GradientWidget::mousePressEvent(QMouseEvent* e)
         selectedHandle = handle;
         dragging = true;
         selectOffsetY = e->y() - gradValToHandleY(
-                    points[handle].first);
+                    points[handle].second);
         update();
         e->accept();
     }
@@ -274,8 +303,9 @@ 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;
+        newVal = std::clamp(newVal, 0.0f, maxValue);
+        points[selectedHandle].second = newVal;
+        updateGradient();
         update();
         emit gradientChanged();
         e->accept();
@@ -295,24 +325,33 @@ void GradientWidget::mouseMoveEvent(QMouseEvent* e)
 
 void GradientWidget::mouseDoubleClickEvent(QMouseEvent* e)
 {
+    auto torgb = [](const QColor& c) {
+        return RGBColor{
+            uint8_t(c.red()), uint8_t(c.green()), uint8_t(c.blue())
+        };
+    };
     QRect handleArea = getHandleArea();
     int handle = handleAtPos(e->pos());
     if (handle != -1) {
-        QColor current = points.at(handle).second;
-        QColor newColor = QColorDialog::getColor(current,
+        RGBColor current = points.at(handle).first;
+        /*QColor newColor = QColorDialog::getColor(current,
                                                  this,
-                                                 tr("Pick Color"));
-        if (newColor.isValid()) {
-            points[handle].second = newColor;
+                                                 tr("Pick Color"));*/
+        selectedHandle = handle;
+        colorPicker->setCurrentColor(fromRGB(current));
+        colorPicker->exec();
+        /*if (newColor.isValid()) {
+            points[handle].first = torgb(newColor);
             update();
             emit gradientChanged();
-        }
+        }*/
         e->accept();
     }
     else if (handleArea.contains(e->pos())) {
         float v = handleYToGradVal(e->pos().y());
-        points.append({ v, colorAtY(e->pos().y()) });
+        points.emplace_back(torgb(colorAtY(e->pos().y())), v);
         e->accept();
+        updateGradient();
         update();
     }
     else {
@@ -349,10 +388,29 @@ QSize GradientWidget::sizeHint(void) const
 }
 
 
+void GradientWidget::selectedColorChanged(const QColor& newColor)
+{
+    if (points.size() > selectedHandle) {
+        points.at(selectedHandle).first = RGBColor {
+            uint8_t(newColor.red()),
+            uint8_t(newColor.green()),
+            uint8_t(newColor.blue())
+        };
+        updateGradient();
+        update();
+        emit gradientChanged();
+    }
+}
+
+
 QRect GradientWidget::getGradientRect(void) const
 {
-    int left, top, right, bottom;
-    getContentsMargins(&left, &top, &right, &bottom);
+    QMargins cm = contentsMargins();
+    int top = cm.top();
+    int bottom = cm.bottom();
+    int left = cm.left();
+    int right = cm.right();
+
     int spacing = this->style()->pixelMetric(
                 QStyle::PM_LayoutHorizontalSpacing);
     if (spacing == -1) {
@@ -372,7 +430,7 @@ QRect GradientWidget::getGradientRect(void) const
 QRect GradientWidget::getHandleRect(int index) const
 {
     QRect handleArea = getHandleArea();
-    float y = handleArea.top() + points.at(index).first * handleArea.height();
+    float y = handleArea.top() + points.at(index).second / maxValue * handleArea.height();
     return QRect {
         handleArea.x(), int(y - handleHeight / 2),
         handleWidth, handleHeight
@@ -382,8 +440,12 @@ QRect GradientWidget::getHandleRect(int index) const
 
 QRect GradientWidget::getHandleArea(void) const
 {
-    int left, top, right, bottom;
-    getContentsMargins(&left, &top, &right, &bottom);
+    QMargins cm = contentsMargins();
+    int top = cm.top();
+    int bottom = cm.bottom();
+    int left = cm.left();
+    int right = cm.right();
+
     top += handleHeight / 2;
     bottom += handleHeight / 2;
     float y = top;
@@ -408,14 +470,14 @@ int GradientWidget::handleAtPos(QPoint pos) const
 float GradientWidget::handleYToGradVal(float y) const
 {
     QRect area = getHandleArea();
-    return (y - area.top()) / area.height();
+    return maxValue * (y - area.top()) / area.height();
 }
 
 
 float GradientWidget::gradValToHandleY(float v) const
 {
     QRect area = getHandleArea();
-    return area.top() + v * area.height();
+    return area.top() + v / maxValue * area.height();
 }
 
 

+ 22 - 4
GradientWidget.h

@@ -2,16 +2,23 @@
 #define GRADIENTWIDGET_H
 
 #include <QWidget>
-#include <QLinearGradient>
 #include <QPainterPath>
+#include <QColorDialog>
 #include <QVector>
 #include <QPair>
 
+#include "Gradient.h"
+
 class GradientWidget :
     public QWidget
 {
     Q_OBJECT
-    QVector<QPair<float, QColor>> points;
+
+    std::vector<std::pair<RGBColor, float>> points;
+    Gradient gradient;
+    float maxValue;
+
+    QColorDialog* colorPicker;
 
     bool dragging;
     int selectedHandle;
@@ -34,8 +41,16 @@ public:
 
     explicit GradientWidget(QWidget *parent = nullptr);
 
-    const QVector<QPair<float, QColor>>& getGradient(void) const;
-    void setGradient(QVector<QPair<float, QColor>>);
+    const Gradient& getGradient(void) const;
+    void setGradient(Gradient gradient);
+
+private:
+    void updateGradient(void);
+    inline QColor fromRGB(const RGBColor& rgb)
+    {
+        return QColor{ rgb.r, rgb.g, rgb.b };
+    }
+public:
 
     QColor colorAtY(float y);
 
@@ -50,6 +65,9 @@ public:
     QSize minimumSizeHint(void) const override;
     QSize sizeHint(void) const override;
 
+public slots:
+    void selectedColorChanged(const QColor& newColor);
+
 protected:
     /// \brief the area in which the gradient is displayed
     QRect getGradientRect(void) const;

+ 1 - 0
libalmond/include/Gradient.h

@@ -19,6 +19,7 @@ class Gradient
 public:
     Gradient(void);
     Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat = false, int precalcSteps = -1);
+    Gradient(std::vector<std::pair<RGBColor, float>> colors, float maxValue, bool repeat = false, int precalcSteps = -1);
 
     const std::vector<std::pair<RGBColor, float>>& getPoints(void) const;
 

+ 9 - 0
libalmond/src/CubicSpline.cpp

@@ -47,6 +47,15 @@ float CubicSpline::interpolateAt(float x)
     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); };
+
+    if (points.empty()) {
+        return 0.0f;
+    }
+
+    if(std::get<0>(points[0]) > x) {
+        return std::get<1>(points[0]);
+    }
+
     for (auto it = points.begin(); it != points.end() && (it + 1) != points.end(); ++it) {
         auto& left = *it;
         auto& right = *(it + 1);

+ 48 - 0
libalmond/src/Gradient.cpp

@@ -61,6 +61,54 @@ Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat,
 }
 
 
+Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, float maxVal, 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;
+        });
+
+    points = colors;
+    max = maxVal;
+
+    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, true);
+    CubicSpline gsp(gs, false, true);
+    CubicSpline bsp(bs, false, true);
+
+    if(precalcSteps <= 0) {
+        precalcSteps = int(max * 7) + 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);
+    }
+}
+
+
 const std::vector<std::pair<RGBColor, float>>& Gradient::getPoints(void) const
 {
     return points;