# HG changeset patch # User unC0Rr # Date 1736527054 -3600 # Node ID 2d65bd46c92fb1d8fd70df8e5c13273b2a2c3e15 # Parent 629d5123a979aa221c4c7ece231e07308d8dc6d3 Start work on hedgehog tracer diff -r 629d5123a979 -r 2d65bd46c92f tools/hhtracer/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/hhtracer/CMakeLists.txt Fri Jan 10 17:37:34 2025 +0100 @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.16) + +project(hhtracer VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Quick Svg) + +qt_standard_project_setup(REQUIRES 6.5) + +qt_add_executable(apphhtracer + main.cpp +) + +qt_add_qml_module(apphhtracer + URI hhtracer + VERSION 1.0 + QML_FILES + Main.qml + SOURCES tracer.h tracer.cpp +) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +set_target_properties(apphhtracer PROPERTIES +# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.apphhtracer + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_link_libraries(apphhtracer + PRIVATE Qt6::Quick + Qt6::Svg +) + +include(GNUInstallDirs) +install(TARGETS apphhtracer + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff -r 629d5123a979 -r 2d65bd46c92f tools/hhtracer/Main.qml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/hhtracer/Main.qml Fri Jan 10 17:37:34 2025 +0100 @@ -0,0 +1,101 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Layouts + +ApplicationWindow { + height: 900 + title: qsTr("Tracer") + visible: true + width: 1200 + + header: ToolBar { + RowLayout { + Button { + text: qsTr("Choose Image...") + + onClicked: fileDialog.open() + } + + Button { + text: qsTr("Start") + + onClicked: { + stepTimer.start(); + } + } + + Button { + text: qsTr("Stop") + + onClicked: { + stepTimer.stop(); + } + } + } + } + + FileDialog { + id: fileDialog + + nameFilters: ["Hedgehog images (*.png)"] + + onAccepted: { + console.log("Hello") + baseImage.source = selectedFile; + tracer.start(fileDialog.selectedFile); + } + } + + Tracer { + id: tracer + } + + + Timer { + id: stepTimer + + interval: 1500 + repeat: true + running: false + triggeredOnStart: true + + onTriggered: tracer.step() + } + + ColumnLayout { + anchors.fill: parent + + Image { + id: baseImage + + Layout.fillWidth: true + Layout.preferredHeight: 128 + fillMode: Image.PreserveAspectFit + } + + GridLayout { + Layout.fillWidth: true + Layout.fillHeight: true + columns: 10 + + Repeater { + model: tracer.solutions + + Image { + width: 32 + height: 32 + source: "file://" + modelData + fillMode: Image.PreserveAspectFit + + Rectangle { + border.width: 1 + border.color: "black" + color: "transparent" + anchors.fill: parent + } + } + } + } + } +} diff -r 629d5123a979 -r 2d65bd46c92f tools/hhtracer/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/hhtracer/main.cpp Fri Jan 10 17:37:34 2025 +0100 @@ -0,0 +1,22 @@ +#include +#include +#include + +#include "tracer.h" + +int main(int argc, char *argv[]) { + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + // Tracer tracer; + // engine.rootContext()->setContextProperty(QStringLiteral("tracer"), + // &tracer); + + QObject::connect( + &engine, &QQmlApplicationEngine::objectCreationFailed, &app, + []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); + engine.loadFromModule("hhtracer", "Main"); + + return app.exec(); +} diff -r 629d5123a979 -r 2d65bd46c92f tools/hhtracer/tracer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/hhtracer/tracer.cpp Fri Jan 10 17:37:34 2025 +0100 @@ -0,0 +1,149 @@ +#include "tracer.h" + +#include +#include + +Tracer::Tracer(QObject* parent) + : QObject{parent}, + palette_{{Qt::black, + Qt::white, + {"#f29ce7"}, + {"#9f086e"}, + {"#54a2fa"}, + {"#2c78d2"}}} {} + +QList Tracer::palette() const { return palette_; } + +void Tracer::setPalette(const QList& newPalette) { + if (palette_ == newPalette) return; + palette_ = newPalette; + emit paletteChanged(); +} + +double Tracer::bestSolution() const { return bestSolution_; } + +void Tracer::start(const QString& fileName) { + qDebug() << "Starting using" << fileName; + + bestSolution_ = 0; + solutions_.clear(); + generation_.clear(); + image_ = QImage{}; + + if (palette_.isEmpty()) { + qDebug("Empty palette"); + return; + } + + image_.load(QUrl(fileName).toLocalFile()); + + if (image_.isNull()) { + qDebug("Failed to load image"); + return; + } + + for (int i = 0; i < 100; ++i) { + generation_.append(Solution{{32, 32}, palette_}); + } +} + +void Tracer::step() { + solutions_.clear(); + + for (auto& solution : generation_) { + const auto fileName = newFileName(); + solutions_.append(fileName); + + solution.render(fileName); + } + + qDebug() << solutions_; + + emit solutionsChanged(); +} + +QStringList Tracer::solutions() const { return solutions_; } + +QString Tracer::newFileName() { + static qlonglong counter{0}; + counter += 1; + return tempDir_.filePath( + QStringLiteral("hedgehog_%1.svg").arg(counter, 3, 32, QChar(u'_'))); +} + +Solution::Solution(QSizeF size, const QList& palette) : size{size} { + fitness = 0; + primitives = {Primitive(size, palette)}; +} + +void Solution::render(const QString& fileName) const { + const auto imageSize = size.toSize(); + + QSvgGenerator generator; + generator.setFileName(fileName); + generator.setSize(imageSize); + generator.setViewBox(QRect(0, 0, imageSize.width(), imageSize.height())); + generator.setTitle("Hedgehog"); + generator.setDescription("Approximation of a target image using primitives"); + + QPainter painter; + painter.begin(&generator); + painter.setRenderHint(QPainter::Antialiasing, true); + + for (const auto& primitive : primitives) { + painter.setPen(primitive.pen); + painter.setBrush(primitive.brush); + painter.resetTransform(); + painter.translate(primitive.origin); + painter.rotate(primitive.rotation); + + switch (primitive.type) { + case Polygon: { + QPolygonF polygon; + polygon.append({0, 0}); + polygon.append(primitive.points); + + painter.drawPolygon(polygon); + break; + } + case Circle: + painter.drawEllipse({0, 0}, primitive.radius1, primitive.radius2); + break; + } + } + + painter.end(); +} + +double Solution::cost() const { + return std::accumulate(primitives.constBegin(), primitives.constEnd(), 0, + [](auto a, auto p) { return a + p.cost(); }); +} + +Primitive::Primitive(QSizeF size, const QList& palette) { + auto rg = QRandomGenerator::global(); + auto randomPoint = [&]() -> QPointF { + return {rg->bounded(size.width()), rg->bounded(size.height())}; + }; + + if (rg->bounded(2) == 0) { + type = Polygon; + + points.append(randomPoint()); + points.append(randomPoint()); + } else { + type = Circle; + + radius1 = rg->bounded(size.width()); + radius2 = rg->bounded(size.width()); + rotation = rg->bounded(90); + } + + pen = QPen(palette[rg->bounded(palette.length())]); + pen.setWidthF(rg->bounded(size.width() * 0.1)); + brush = QBrush(palette[rg->bounded(palette.length())]); + + origin = randomPoint(); +} + +double Primitive::cost() const { return 1.0 + 0.1 * points.length(); } diff -r 629d5123a979 -r 2d65bd46c92f tools/hhtracer/tracer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/hhtracer/tracer.h Fri Jan 10 17:37:34 2025 +0100 @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + +enum PrimitiveType { Polygon, Circle }; + +struct Primitive { + PrimitiveType type; + QPen pen; + QBrush brush; + QPointF origin; + QList points; // polygon + double radius1{}, radius2{}, rotation{}; // ellipse + + explicit Primitive(QSizeF size, const QList& palette); + double cost() const; +}; + +struct Solution { + QList primitives; + double fitness; + QSizeF size; + + explicit Solution(QSizeF size, const QList& palette); + void calculateFitness(const QImage& image); + void render(const QString& fileName) const; + double cost() const; +}; + +class Tracer : public QObject { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QList palette READ palette WRITE setPalette NOTIFY + paletteChanged FINAL) + Q_PROPERTY( + double bestSolution READ bestSolution NOTIFY bestSolutionChanged FINAL) + Q_PROPERTY(QStringList solutions READ solutions NOTIFY solutionsChanged FINAL) + + public: + explicit Tracer(QObject *parent = nullptr); + + QList palette() const; + void setPalette(const QList& newPalette); + + double bestSolution() const; + + Q_INVOKABLE void start(const QString& fileName); + Q_INVOKABLE void step(); + + QStringList solutions() const; + + Q_SIGNALS: + void paletteChanged(); + void bestSolutionChanged(); + void solutionsChanged(); + + private: + QList palette_; + double bestSolution_; + QStringList solutions_; + QList generation_; + QTemporaryDir tempDir_; + QImage image_; + + QString newFileName(); +};