--- /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 <QRandomGenerator>
+#include <QSvgGenerator>
+
+Tracer::Tracer(QObject* parent)
+ : QObject{parent},
+ palette_{{Qt::black,
+ Qt::white,
+ {"#f29ce7"},
+ {"#9f086e"},
+ {"#54a2fa"},
+ {"#2c78d2"}}} {}
+
+QList<QColor> Tracer::palette() const { return palette_; }
+
+void Tracer::setPalette(const QList<QColor>& 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<QColor>& 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<QColor>& 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(); }