Jelajahi Sumber

massive, epic improvements to gradient editor

Nicolas Winkler 4 tahun lalu
induk
melakukan
47030b6606

+ 7 - 0
include/GradientMenu.h

@@ -4,6 +4,7 @@
 #include <QWidget>
 #include <QVector>
 #include <QPair>
+#include <QFile>
 
 #include "Gradient.h"
 
@@ -27,11 +28,17 @@ public:
     const Gradient& getGradientBefore(void) const;
     void setGradient(Gradient grad);
 
+    void loadGradient(QFile& file);
+
 signals:
     void gradientChanged(void);
 private slots:
+    void onGradientChanged(void);
     void on_removeBtn_clicked();
     void on_presetCmb_currentIndexChanged(int index);
+    void on_saveBtn_clicked();
+    void on_loadBtn_clicked();
+    void on_maxValSpb_valueChanged(double maxVal);
 };
 
 #endif // GRADIENTMENU_H

+ 2 - 0
libalmond/include/Gradient.h

@@ -22,10 +22,12 @@ public:
     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;
+    inline bool isRepeat(void) const { return repeat; }
 
     static Gradient defaultGradient(void);
 
     static Gradient fromXml(const std::string& xml);
+    std::string toXml(void) const;
 
     ///
     /// \brief get the maximum value this gradient accepts

+ 36 - 4
libalmond/src/Gradient.cpp

@@ -6,6 +6,7 @@
 #include "XmlException.h"
 
 #include <cmath>
+#include <sstream>
 #include <algorithm>
 #include <functional>
 
@@ -49,7 +50,10 @@ Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat,
     CubicSpline bsp(bs, false, true);
 
     if(precalcSteps <= 0) {
-        precalcSteps = int(max * 15) + 10;
+        precalcSteps = int(max * 7) + 10;
+    }
+    if (precalcSteps > 12000) {
+        precalcSteps = 12000;
     }
 
     for (int i = 0; i < precalcSteps; i++) {
@@ -99,6 +103,9 @@ Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, float maxVal,
     if(precalcSteps <= 0) {
         precalcSteps = int(max * 7) + 10;
     }
+    if (precalcSteps > 12000) {
+        precalcSteps = 12000;
+    }
 
     for (int i = 0; i < precalcSteps; i++) {
         float position = i * max / precalcSteps;
@@ -153,8 +160,14 @@ Gradient Gradient::fromXml(const std::string& xml)
         throw alm::XmlException{ "invalid root node" };
 
     bool repeat = false;
-    if (auto* re = dynamic_cast<XMLElement*>(rootNode))
-        repeat = re->BoolAttribute("repeat");
+    bool hasMax = false;
+    float maxVal = 0.0f;
+    if (auto* re = dynamic_cast<XMLElement*>(rootNode)) {
+        repeat = re->BoolAttribute("repeat", false);
+        XMLError e = re->QueryFloatAttribute("max", &maxVal);
+        if (e == XML_SUCCESS)
+            hasMax = true;
+    }
 
     std::vector<std::pair<RGBColor, float>> points;
 
@@ -172,8 +185,27 @@ Gradient Gradient::fromXml(const std::string& xml)
 
         colorNode = colorNode->NextSiblingElement("color");
     }
+    if (hasMax)
+        return Gradient{ std::move(points), maxVal, repeat };
+    else
+        return Gradient{ std::move(points), repeat };
+}
+
 
-    return Gradient{ std::move(points), repeat };
+std::string Gradient::toXml(void) const
+{
+    std::stringstream buf;
+
+    buf << "<gradient max=\"" << max << "\" repeat=\"" << (repeat ? "true" : "false") << "\">" << std::endl;
+    for (const auto&[color, val] : points) {
+        buf << "    <color " <<
+               "r=\"" << int(color.r) <<
+               "\" g=\"" << int(color.g) <<
+               "\" b=\"" << int(color.b) <<
+               "\" p=\"" << val << "\" />" << std::endl;
+    }
+    buf << "</gradient>" << std::endl;
+    return buf.str();
 }
 
 

+ 1 - 1
resources/Almond.qrc

@@ -6,7 +6,7 @@
         <file alias="zoom_out">icons/zoom_out.svg</file>
     </qresource>
     <qresource prefix="/gradients">
-        <file alias="default">gradients/default.xml</file>
+        <file alias="oldschool">gradients/oldschool.xml</file>
         <file alias="clouds">gradients/clouds.xml</file>
         <file alias="rainbow">gradients/rainbow.xml</file>
         <file alias="grayscale">gradients/grayscale.xml</file>

+ 0 - 0
resources/gradients/default.xml → resources/gradients/oldschool.xml


+ 68 - 5
src/GradientMenu.cpp

@@ -1,17 +1,22 @@
 #include "GradientMenu.h"
 #include "ui_GradientMenu.h"
 
+#include "XmlException.h"
+
 #include <QFile>
+#include <QMessageBox>
+#include <QFileDialog>
 
 const QString GradientMenu::presetNames[] = {
     "blue gold",
     "clouds",
-    "default",
+    "oldschool",
     "grayscale",
     "peach",
     "rainbow"
 };
 
+
 GradientMenu::GradientMenu(QWidget *parent) :
     QWidget(parent),
     ui(new Ui::GradientMenu)
@@ -29,6 +34,7 @@ GradientMenu::GradientMenu(QWidget *parent) :
         ui->presetCmb->addItem(presetName);
     }
     connect(ui->gradientWidget, &GradientWidget::gradientChanged, this, &GradientMenu::gradientChanged);
+    connect(ui->gradientWidget, &GradientWidget::gradientChanged, this, &GradientMenu::onGradientChanged);
 }
 
 
@@ -56,6 +62,28 @@ void GradientMenu::setGradient(Gradient grad)
     ui->gradientWidget->setGradient(std::move(grad));
 }
 
+
+void GradientMenu::loadGradient(QFile& file)
+{
+    if (file.isOpen() || file.open(QFile::ReadOnly)) {
+        QString xml = QString::fromUtf8(file.readAll());
+        try {
+            ui->gradientWidget->setGradient(Gradient::fromXml(xml.toStdString()));
+        } catch (alm::XmlException& xmlex) {
+            QMessageBox::critical(this, tr("Error Loading Gradient"), tr("Error loading gradient: ") + xmlex.what());
+        } catch (...) {
+            QMessageBox::critical(this, tr("Error Loading Gradient"), tr("Unknown error loading gradient."));
+        }
+    }
+}
+
+
+void GradientMenu::onGradientChanged(void)
+{
+    ui->maxValSpb->setValue(ui->gradientWidget->getGradient().getMax());
+}
+
+
 void GradientMenu::on_removeBtn_clicked()
 {
     ui->gradientWidget->removeSelectedHandle();
@@ -65,9 +93,44 @@ void GradientMenu::on_presetCmb_currentIndexChanged(int index)
 {
     QString presetName = presetNames[index];
     QFile gradXml{ ":/gradients/" + presetName };
-    if (gradXml.open(QFile::ReadOnly)) {
-        QString xml = QString::fromUtf8(gradXml.readAll());
-        ui->gradientWidget->setGradient(Gradient::fromXml(xml.toStdString()));
-        gradXml.close();
+    loadGradient(gradXml);
+}
+
+void GradientMenu::on_saveBtn_clicked()
+{
+    std::string xml = ui->gradientWidget->getGradient().toXml();
+    QString filename =
+            QFileDialog::getSaveFileName(this, tr("Save Gradient"), "", "Gradient XML Files (*.xml)");
+    if (!filename.isNull()) {
+        QFile saveFile{ filename };
+        bool opened = saveFile.open(QFile::WriteOnly);
+        if (!opened) {
+            QMessageBox::critical(this, tr("Error saving file"), tr("Error saving gradient: Could not open file."));
+        }
+        saveFile.write(QString::fromStdString(xml).toUtf8());
+        saveFile.close();
+    }
+}
+
+void GradientMenu::on_loadBtn_clicked()
+{
+    QFileDialog openDialog{ this, tr("Load Gradient"), "", "" };
+    connect(&openDialog, &QFileDialog::fileSelected, [this] (const QString& name) {
+        QFile file{ name };
+        loadGradient(file);
+    });
+    openDialog.exec();
+}
+
+
+void GradientMenu::on_maxValSpb_valueChanged(double maxVal)
+{
+    const Gradient& old = ui->gradientWidget->getGradient();
+    float minVal = old.getPoints().at(old.getPoints().size() - 1).second;
+    if (maxVal < minVal) {
+        ui->maxValSpb->setValue(minVal);
+        maxVal = minVal;
     }
+    Gradient g = Gradient{ old.getPoints(), float(maxVal), old.isRepeat() };
+    ui->gradientWidget->setGradient(std::move(g));
 }

+ 1 - 79
src/GradientWidget.cpp

@@ -45,6 +45,7 @@ void GradientWidget::updateGradient(void)
 {
     gradient = Gradient{ points, maxValue };
     update();
+    emit gradientChanged();
 }
 
 
@@ -97,13 +98,6 @@ void GradientWidget::paintEvent(QPaintEvent* e)
     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 });
     }
@@ -111,60 +105,12 @@ void GradientWidget::paintEvent(QPaintEvent* e)
     // 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{ linGrad };
     painter.fillRect(gradientRect, brush);
 
     int index = 0;
     for (auto& [color, point] : 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);*/
         int hs = HandleState::HANDLE_NORMAL;
         if (dragging && selectedHandle == index)
             hs |= HANDLE_DOWN;
@@ -175,30 +121,6 @@ void GradientWidget::paintEvent(QPaintEvent* e)
         paintHandle(painter, r, fromRGB(color), hs);
         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());
-    */
 }
 
 

+ 54 - 2
ui/GradientMenu.ui

@@ -13,7 +13,7 @@
   <property name="windowTitle">
    <string>Select Gradient</string>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1,0">
+  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1,0,0">
    <property name="leftMargin">
     <number>0</number>
    </property>
@@ -41,12 +41,64 @@
     </widget>
    </item>
    <item>
-    <widget class="QComboBox" name="presetCmb"/>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="2" column="0">
+      <widget class="QPushButton" name="loadBtn">
+       <property name="text">
+        <string>Load Gradient</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QComboBox" name="presetCmb"/>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="presetLbl">
+       <property name="text">
+        <string>Load Preset:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QPushButton" name="saveBtn">
+       <property name="text">
+        <string>Save Gradient</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
    </item>
    <item>
     <widget class="GradientWidget" name="gradientWidget" native="true"/>
    </item>
    <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QLabel" name="maxValLbl">
+       <property name="text">
+        <string>Max. Value</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDoubleSpinBox" name="maxValSpb">
+       <property name="decimals">
+        <number>0</number>
+       </property>
+       <property name="maximum">
+        <double>100000.000000000000000</double>
+       </property>
+       <property name="singleStep">
+        <double>10.000000000000000</double>
+       </property>
+       <property name="stepType">
+        <enum>QAbstractSpinBox::AdaptiveDecimalStepType</enum>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <widget class="QPushButton" name="removeBtn">