Browse Source

implemented load/save view

Nicolas Winkler 4 years ago
parent
commit
b372edb7dd

+ 0 - 2
Almond.pro

@@ -31,7 +31,6 @@ SOURCES += \
         src/Almond.cpp \
         src/AlmondMenuWidget.cpp \
         src/BackgroundTask.cpp \
-        src/Color.cpp \
         src/EscapeTimeVisualWidget.cpp \
         src/ExportImageMenu.cpp \
         src/ExportVideoMenu.cpp \
@@ -50,7 +49,6 @@ HEADERS += \
         include/Almond.h \
         include/AlmondMenuWidget.h \
         include/BackgroundTask.h \
-        include/Color.h \
         include/EscapeTimeVisualWidget.h \
         include/ExportImageMenu.h \
         include/ExportVideoMenu.h \

+ 3 - 0
CMakeLists.txt

@@ -21,6 +21,7 @@ set(CMAKE_CXX_STANDARD 17)
 
 FILE(GLOB AlmondSources src/*.cpp)
 FILE(GLOB AlmondHeaders include/*.h)
+FILE(GLOB AlmondUIs ui/*.ui)
 
 
 IF (WIN32)
@@ -28,11 +29,13 @@ IF (WIN32)
                    resources/Almond.qrc
                    resources/splash.qrc
                    resources/icon.rc
+                   ${AlmondUIs}
                    ${AlmondHeaders})
 ELSE()
     add_executable(Almond ${AlmondSources}
                    resources/Almond.qrc
                    resources/splash.qrc
+                   ${AlmondUIs}
                    ${AlmondHeaders})
     set_target_properties(Almond PROPERTIES OUTPUT_NAME "almond")
 ENDIF()

+ 4 - 0
include/Almond.h

@@ -119,6 +119,10 @@ private slots:
 
     void on_aboutBtn_clicked();
 
+    void on_loadBtn_clicked();
+
+    void on_saveBtn_clicked();
+
 private:
     Ui::AlmondClass ui;
 };

+ 1 - 1
include/FractalZoomWidget.h

@@ -62,7 +62,7 @@ public slots:
     void setCurrentLevel(int level);
     void notFinished(int level, GridIndex i, GridIndex j);
     void jobFailed(int level, GridIndex i, GridIndex j);
-    void redirect(int level, GridIndex i, GridIndex j, int calcState, Bitmap<float>* bmp);
+    void redirect(int level, GridIndex i, GridIndex j, unsigned int calcState, Bitmap<float>* bmp);
 signals:
     void done(int level, GridIndex i, GridIndex j, Bitmap<float>* bmp);
 };

+ 0 - 21
libalmond/CMakeLists.txt

@@ -38,27 +38,6 @@ target_link_libraries(libalmond PUBLIC ${FFMPEG_LIBRARIES})
 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)
-#    foreach(header ${ZLIB_PUBLIC_HDRS})
-#        get_filename_component(the_incluude ${header} DIRECTORY)
-#        list(APPEND ZLIB_PUB_INCLUDE ${the_incluude})
-#    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)

+ 2 - 2
libalmond/include/Gradient.h

@@ -30,8 +30,8 @@ class alm::Gradient
     bool repeat;
 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);
+    Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat = true);
+    Gradient(std::vector<std::pair<RGBColor, float>> colors, float maxValue, bool repeat = true);
 
     const std::vector<std::pair<RGBColor, float>>& getPoints(void) const;
     static Gradient defaultGradient(void);

+ 24 - 7
libalmond/include/Serialize.h

@@ -20,7 +20,7 @@ namespace alm
 
 
     template<typename T>
-    tinyxml2::XMLElement* serialize(tinyxml2::XMLDocument& doc, const T&);
+    void serialize(tinyxml2::XMLElement* elem, const T&);
 
     template<typename T>
     std::string toXml(const T&);
@@ -34,13 +34,33 @@ namespace alm
 
     // specializations
     template<>
-    tinyxml2::XMLElement* serialize<Gradient>(tinyxml2::XMLDocument& doc, const Gradient&);
-    template<>
-    std::string toXml<Gradient>(const Gradient&);
+    void serialize<Gradient>(tinyxml2::XMLElement* elem, const Gradient&);
     template<>
     Gradient deserialize<Gradient>(tinyxml2::XMLElement* xml);
     template<>
+    std::string toXml<Gradient>(const Gradient&);
+    template<>
     Gradient fromXml<Gradient>(const std::string& xml);
+
+
+    template<>
+    void serialize<mnd::MandelViewport>(tinyxml2::XMLElement* elem, const mnd::MandelViewport&);
+    template<>
+    mnd::MandelViewport deserialize<mnd::MandelViewport>(tinyxml2::XMLElement* xml);
+
+    template<>
+    void serialize<mnd::MandelInfo>(tinyxml2::XMLElement* elem, const mnd::MandelInfo&);
+    template<>
+    mnd::MandelInfo deserialize<mnd::MandelInfo>(tinyxml2::XMLElement* xml);
+
+    template<>
+    void serialize<ImageView>(tinyxml2::XMLElement* elem, const ImageView&);
+    template<>
+    ImageView deserialize<ImageView>(tinyxml2::XMLElement* xml);
+    template<>
+    std::string toXml<ImageView>(const ImageView&);
+    template<>
+    ImageView fromXml<ImageView>(const std::string& xml);
 }
 
 
@@ -48,9 +68,6 @@ struct alm::ImageView
 {
     mnd::MandelInfo view;
     Gradient gradient;
-
-    std::string toXml(void) const;
-    static ImageView fromXml(const std::string& xml);
 };
 
 #endif // LIBALMOND_SERIALIZE_H

+ 3 - 73
libalmond/src/Gradient.cpp

@@ -17,7 +17,7 @@ Gradient::Gradient(void) :
 }
 
 
-Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat, int precalcSteps) :
+Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat) :
     repeat{ repeat }
 {
     if(colors.empty() || colors.size() < 2)
@@ -41,45 +41,10 @@ Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, bool repeat,
     colorSpline = ColorSpline{ fs, false, false };
     
     return;
-    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;
-    }
-    if (precalcSteps > 12000) {
-        precalcSteps = 12000;
-    }
-
-    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(std::vector<std::pair<RGBColor, float>> colors, float maxVal, bool repeat, int precalcSteps) :
+Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, float maxVal, bool repeat) :
     repeat{ repeat }
 {
     if(colors.empty() || colors.size() < 2)
@@ -103,41 +68,6 @@ Gradient::Gradient(std::vector<std::pair<RGBColor, float>> colors, float maxVal,
     }
     colorSpline = ColorSpline{ fs, false, false };
     return;
-    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;
-    }
-    if (precalcSteps > 12000) {
-        precalcSteps = 12000;
-    }
-
-    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);
-    }
 }
 
 
@@ -217,7 +147,6 @@ RGBColor Gradient::get(float x) const
 
 RGBColor Gradient::interpolate(float x) const
 {
-    return colorSpline.interpolateAt(x);
     
     if (pointMap.empty()) {
         return RGBColor{ 0, 0, 0 };
@@ -231,6 +160,7 @@ RGBColor Gradient::interpolate(float x) const
         else
             return (pointMap.rbegin())->second;
     }
+    return colorSpline.interpolateAt(x);
 
     auto firstLess = pointMap.lower_bound(x);
 

+ 192 - 4
libalmond/src/Serialize.cpp

@@ -12,6 +12,7 @@ using tinyxml2::XMLElement;
 using tinyxml2::XMLDocument;
 using tinyxml2::XMLError;
 using tinyxml2::XMLNode;
+using tinyxml2::XMLPrinter;
 using tinyxml2::XML_SUCCESS;
 
 
@@ -24,7 +25,7 @@ alm::Gradient alm::deserialize<alm::Gradient>(XMLElement* elem)
     bool repeat = false;
     bool hasMax = false;
     float maxVal = 0.0f;
-    repeat = elem->BoolAttribute("repeat", false);
+    repeat = elem->BoolAttribute("repeat", true);
     XMLError e = elem->QueryFloatAttribute("max", &maxVal);
     if (e == XML_SUCCESS)
         hasMax = true;
@@ -52,11 +53,18 @@ alm::Gradient alm::deserialize<alm::Gradient>(XMLElement* elem)
 }
 
 
-
 template<>
-tinyxml2::XMLElement* alm::serialize<alm::Gradient>(tinyxml2::XMLDocument& doc, const alm::Gradient&)
+void alm::serialize<alm::Gradient>(tinyxml2::XMLElement* gradient, const alm::Gradient& grad)
 {
-    return nullptr;
+    gradient->SetAttribute("repeat", grad.isRepeat());
+    gradient->SetAttribute("version", "1.0.0");
+    for (const auto& [col, point] : grad.getPoints()) {
+        XMLElement* cElem = gradient->InsertNewChildElement("color");
+        cElem->SetAttribute("r", int(col.r));
+        cElem->SetAttribute("g", int(col.g));
+        cElem->SetAttribute("b", int(col.b));
+        cElem->SetAttribute("p", float(point));
+    }
 }
 
 
@@ -90,3 +98,183 @@ std::string alm::toXml<alm::Gradient>(const alm::Gradient& g)
     return buf.str();
 }
 
+
+template<>
+void alm::serialize<mnd::MandelViewport>(XMLElement* elem, const mnd::MandelViewport& v)
+{
+    XMLElement* x = elem->InsertNewChildElement("x");
+    XMLElement* y = elem->InsertNewChildElement("y");
+    XMLElement* w = elem->InsertNewChildElement("width");
+    XMLElement* h = elem->InsertNewChildElement("height");
+    x->SetText(mnd::toString(v.x).c_str());
+    y->SetText(mnd::toString(v.y).c_str());
+    w->SetText(mnd::toString(v.width).c_str());
+    h->SetText(mnd::toString(v.height).c_str());
+}
+
+
+template<>
+mnd::MandelViewport alm::deserialize<mnd::MandelViewport>(XMLElement* elem)
+{
+    if (elem == nullptr)
+        throw alm::XmlException{ "invalid root node" };
+    XMLElement* x = elem->FirstChildElement("x");
+    XMLElement* y = elem->FirstChildElement("y");
+    XMLElement* width = elem->FirstChildElement("width");
+    XMLElement* height = elem->FirstChildElement("height");
+
+    const char* xStr = x->GetText();
+    const char* yStr = y->GetText();
+    const char* wStr = width->GetText();
+    const char* hStr = height->GetText();
+
+    mnd::MandelViewport viewport;
+    try {
+        viewport.x = mnd::Real{ xStr };
+        viewport.y = mnd::Real{ yStr };
+        viewport.width = mnd::Real{ wStr };
+        viewport.height = mnd::Real{ hStr };
+    }
+    catch(std::runtime_error& re) {
+        throw XmlException{ "could not parse viewport" };
+    }
+    return viewport;
+}
+
+
+template<>
+void alm::serialize<mnd::MandelInfo>(XMLElement* elem, const mnd::MandelInfo& v)
+{
+    XMLElement* viewport = elem->InsertNewChildElement("viewport");
+    serialize(viewport, v.view);
+    if (v.julia) {
+        XMLElement* julia = elem->InsertNewChildElement("julia");
+        XMLElement* juliaX = julia->InsertNewChildElement("x");
+        XMLElement* juliaY = julia->InsertNewChildElement("y");
+        juliaX->SetText(mnd::toString(v.juliaX).c_str());
+        juliaY->SetText(mnd::toString(v.juliaY).c_str());
+    }
+    XMLElement* smooth = elem->InsertNewChildElement("smooth");
+    XMLElement* maxIter = elem->InsertNewChildElement("maxIterations");
+    XMLElement* width = elem->InsertNewChildElement("width");
+    XMLElement* height = elem->InsertNewChildElement("height");
+    smooth->SetText(v.smooth);
+    maxIter->SetText(v.maxIter);
+    width->SetText(v.bWidth);
+    height->SetText(v.bHeight);
+}
+
+
+template<>
+mnd::MandelInfo alm::deserialize<mnd::MandelInfo>(XMLElement* elem)
+{
+    if (elem == nullptr)
+        throw alm::XmlException{ "invalid root node" };
+
+    mnd::MandelInfo mi;
+    XMLElement* viewport = elem->FirstChildElement("viewport");
+    mi.view = deserialize<mnd::MandelViewport>(viewport);
+
+    XMLElement* julia = elem->FirstChildElement("julia");
+    if (julia != nullptr) {
+        XMLElement* juliaX = julia->FirstChildElement("x");
+        XMLElement* juliaY = julia->FirstChildElement("y");
+        try {
+            if (juliaX != nullptr) {
+                mi.juliaX = mnd::Real{ juliaX->GetText() };
+            }
+            if (juliaY != nullptr) {
+                mi.juliaY = mnd::Real{ juliaY->GetText() };
+            }
+        } catch (std::runtime_error& re) {
+            throw XmlException{ "invalid julia parameters" };
+        }
+    }
+
+    XMLElement* smooth = elem->FirstChildElement("smooth");
+    if (smooth != nullptr) {
+        mi.smooth = smooth->BoolText(true);
+    }
+    else {
+        mi.smooth = false;
+    }
+
+    XMLElement* bWidth = elem->FirstChildElement("width");
+    XMLElement* bHeight = elem->FirstChildElement("height");
+    if (bWidth != nullptr && bHeight != nullptr) {
+        mi.bWidth = bWidth->Int64Text(1920);
+        mi.bHeight = bHeight->Int64Text(1080);
+    }
+    else {
+        mi.bWidth = 1920;
+        mi.bHeight = 1080;
+    }
+
+    XMLElement* maxIter = elem->FirstChildElement("maxIterations");
+    if (maxIter != nullptr) {
+        mi.maxIter = maxIter->Int64Text(500);
+    }
+    else {
+        mi.maxIter = 500;
+    }
+    return mi;
+}
+
+
+template<>
+void alm::serialize<alm::ImageView>(XMLElement* elem, const alm::ImageView& v)
+{
+    XMLElement* view = elem->InsertNewChildElement("view");
+    XMLElement* gradient = elem->InsertNewChildElement("gradient");
+    serialize(view, v.view);
+    serialize(gradient, v.gradient);
+}
+
+
+template<>
+alm::ImageView alm::deserialize<alm::ImageView>(XMLElement* elem)
+{
+    if (elem == nullptr)
+        throw alm::XmlException{ "invalid root node" };
+    XMLElement* view = elem->FirstChildElement("view");
+    XMLElement* gradient = elem->FirstChildElement("gradient");
+
+    ImageView iv;
+    iv.view = deserialize<mnd::MandelInfo>(view);
+
+    if (gradient != nullptr) {
+        iv.gradient = deserialize<alm::Gradient>(gradient);
+    }
+    else {
+        iv.gradient = alm::Gradient::defaultGradient();
+    }
+    return iv;
+}
+
+
+template<>
+std::string alm::toXml<alm::ImageView>(const ImageView& iv)
+{
+    XMLDocument doc;
+    XMLElement* imageView = doc.NewElement("imageView");
+    serialize(imageView, iv);
+    doc.InsertFirstChild(imageView);
+    XMLPrinter printer;
+    doc.Accept(&printer);
+    return printer.CStr();
+}
+
+
+template<>
+alm::ImageView alm::fromXml<ImageView>(const std::string& xml)
+{
+    XMLDocument xmlDoc;
+    XMLError err = xmlDoc.Parse(xml.c_str());
+    if (err != XML_SUCCESS)
+        throw alm::XmlException{ "error parsing gradient xml" };
+    return deserialize<ImageView>(xmlDoc.RootElement());
+}
+
+
+
+

+ 1 - 0
resources/Almond.qrc

@@ -15,6 +15,7 @@
         <file alias="peach">gradients/peach.xml</file>
         <file alias="blue gold">gradients/blue_gold.xml</file>
         <file alias="element">gradients/element.xml</file>
+        <file alias="ikea">gradients/ikea.xml</file>
     </qresource>
     <qresource prefix="/about">
         <file alias="about_en">about_en.html</file>

+ 1 - 1
resources/gradients/element.xml

@@ -1,4 +1,4 @@
-<gradient max="350" repeat="false" version="1.0.0" >
+<gradient max="350" repeat="true" version="1.0.0" >
     <color r="49" g="49" b="49" p="0" />
     <color r="128" g="132" b="95" p="11.054" />
     <color r="243" g="255" b="0" p="21.4653" />

+ 1 - 1
resources/gradients/grayscale.xml

@@ -1,4 +1,4 @@
-<gradient repeat="true">
+<gradient repeat="false">
     <color r="255" g="255" b="255" p="0" />
     <color r="40" g="40" b="40" p="20" />
     <color r="255" g="255" b="255" p="40" />

+ 12 - 0
resources/gradients/ikea.xml

@@ -0,0 +1,12 @@
+<gradient max="240" repeat="true" version="1.0.0" >
+    <color r="30" g="50" b="210" p="0" />
+    <color r="254" g="235" b="0" p="10.6824" />
+    <color r="0" g="2" b="52" p="23.0776" />
+    <color r="45" g="54" b="205" p="35.84" />
+    <color r="255" g="255" b="255" p="53.9106" />
+    <color r="0" g="0" b="0" p="87.5294" />
+    <color r="255" g="244" b="0" p="100.8" />
+    <color r="22" g="20" b="59" p="135.459" />
+    <color r="255" g="255" b="255" p="198.212" />
+    <color r="30" g="50" b="210" p="240" />
+</gradient>

+ 34 - 1
src/Almond.cpp

@@ -8,7 +8,7 @@
 #include <QWindow>
 #include <QKeyEvent>
 
-#include "GridFlowLayout.h"
+#include "Serialize.h"
 
 #include <cmath>
 
@@ -526,3 +526,36 @@ void Almond::on_aboutBtn_clicked()
 {
     amw->showSubMenu(3);
 }
+
+
+void Almond::on_loadBtn_clicked()
+{
+    QString filename =
+        QFileDialog::getOpenFileName(this, tr("Load View"), "", "Almond XML Files (*.xml)");
+    QFile file{ filename };
+    if (file.open(QFile::ReadOnly)) {
+        alm::ImageView iv = alm::fromXml<alm::ImageView>(file.readAll().toStdString());
+        this->ui.smooth->setCheckState(iv.view.smooth ? Qt::Checked : Qt::Unchecked);
+        this->ui.maxIterations->setText(QString::number(iv.view.maxIter));
+        this->fractalWidget->setMaxIterations(iv.view.maxIter);
+        this->fractalWidget->setSmoothColoring(iv.view.smooth);
+        this->fractalWidget->setGradient(iv.gradient);
+        this->fractalWidget->setViewport(iv.view.view);
+    }
+}
+
+void Almond::on_saveBtn_clicked()
+{
+    QString filename =
+        QFileDialog::getSaveFileName(this, tr("Save View"), "", "Almond XML Files (*.xml)");
+    QFile file{ filename };
+    if (file.open(QFile::WriteOnly)) {
+        alm::ImageView iv;
+        iv.view = fractalWidget->getMandelInfo();
+        iv.view.bWidth = fractalWidget->getResolutionX();
+        iv.view.bHeight = fractalWidget->getResolutionY();
+        iv.gradient = fractalWidget->getGradient();
+        const std::string& xml = alm::toXml(iv);
+        file.write(xml.c_str());
+    }
+}

+ 5 - 2
src/EscapeTimeVisualWidget.cpp

@@ -529,8 +529,11 @@ void EscapeTimeVisualWidget::updateGradient(void)
     gl.glBindTexture(GL_TEXTURE_2D, gradientTextureId);
 
     gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, len, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, reinterpret_cast<unsigned char*> (pixels.get()));
-    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+    gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    if (gradient.isRepeat())
+        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+    else
+        gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
     gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     gl.glBindTexture(GL_TEXTURE_2D, 0);

+ 3 - 3
src/FractalWidget.cpp

@@ -242,8 +242,8 @@ 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 maxWidth = getResolutionX() - 2 * DIST_FROM_BORDER;
+    mnd::Real distPerPixel = getViewport().width / getResolutionX();
     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);
@@ -270,7 +270,7 @@ void FractalWidget::drawDisplayInfo(void)
     }
 
     float lineY = this->height() - DIST_FROM_BORDER;
-    float lineXEnd = DIST_FROM_BORDER + pixels;
+    float lineXEnd = DIST_FROM_BORDER + pixels / devicePixelRatioF();
 
     infoPainter.setPen(Qt::white);
     infoPainter.setFont(QFont("Arial", 12));

+ 1 - 1
src/FractalZoomWidget.cpp

@@ -130,7 +130,7 @@ void Calcer::jobFailed(int level, GridIndex i, GridIndex j)
 }
 
 
-void Calcer::redirect(int level, GridIndex i, GridIndex j, int calcState, Bitmap<float>* bmp)
+void Calcer::redirect(int level, GridIndex i, GridIndex j, unsigned int calcState, Bitmap<float>* bmp)
 {
     jobsMutex.lock();
     jobs.erase({ level, i, j });

+ 1 - 0
src/GradientMenu.cpp

@@ -16,6 +16,7 @@ const QString GradientMenu::presetNames[] = {
     "clouds",
     "oldschool",
     "element",
+    "ikea",
     "grayscale",
     "peach",
     "rainbow"

+ 33 - 15
ui/Almond.ui

@@ -65,7 +65,7 @@
           <x>0</x>
           <y>0</y>
           <width>593</width>
-          <height>1063</height>
+          <height>1124</height>
          </rect>
         </property>
         <layout class="QHBoxLayout" name="horizontalLayout_3">
@@ -98,20 +98,6 @@
               <string>Zoom</string>
              </property>
              <layout class="QGridLayout" name="gridLayout_2">
-              <item row="1" column="3">
-               <widget class="QPushButton" name="zoom_out">
-                <property name="icon">
-                 <iconset resource="../resources/Almond.qrc">
-                  <normaloff>:/icons/zoom_out</normaloff>:/icons/zoom_out</iconset>
-                </property>
-                <property name="iconSize">
-                 <size>
-                  <width>32</width>
-                  <height>32</height>
-                 </size>
-                </property>
-               </widget>
-              </item>
               <item row="1" column="1">
                <widget class="QPushButton" name="resetZoom">
                 <property name="sizePolicy">
@@ -146,6 +132,38 @@
                 </property>
                </widget>
               </item>
+              <item row="1" column="2">
+               <widget class="QPushButton" name="zoom_out">
+                <property name="icon">
+                 <iconset resource="../resources/Almond.qrc">
+                  <normaloff>:/icons/zoom_out</normaloff>:/icons/zoom_out</iconset>
+                </property>
+                <property name="iconSize">
+                 <size>
+                  <width>32</width>
+                  <height>32</height>
+                 </size>
+                </property>
+               </widget>
+              </item>
+              <item row="2" column="0" colspan="3">
+               <layout class="QHBoxLayout" name="horizontalLayout_6">
+                <item>
+                 <widget class="QPushButton" name="loadBtn">
+                  <property name="text">
+                   <string>Load View</string>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <widget class="QPushButton" name="saveBtn">
+                  <property name="text">
+                   <string>Save View</string>
+                  </property>
+                 </widget>
+                </item>
+               </layout>
+              </item>
              </layout>
             </widget>
            </item>