tools/hhtracer/tracer.cpp
changeset 16085 9ad74696ddec
parent 16084 2d65bd46c92f
equal deleted inserted replaced
16084:2d65bd46c92f 16085:9ad74696ddec
     1 #include "tracer.h"
     1 #include "tracer.h"
     2 
     2 
     3 #include <QRandomGenerator>
     3 #include <QRandomGenerator>
     4 #include <QSvgGenerator>
     4 #include <QSvgGenerator>
     5 
     5 
     6 Tracer::Tracer(QObject* parent)
     6 Tracer::Tracer(QObject *parent)
     7     : QObject{parent},
     7     : QObject{parent},
     8       palette_{{Qt::black,
     8       palette_{{Qt::black,
     9                 Qt::white,
     9                 Qt::white,
       
    10                 {"#9f086e"},
    10                 {"#f29ce7"},
    11                 {"#f29ce7"},
    11                 {"#9f086e"},
       
    12                 {"#54a2fa"},
    12                 {"#54a2fa"},
    13                 {"#2c78d2"}}} {}
    13                 {"#2c78d2"}}} {}
    14 
    14 
    15 QList<QColor> Tracer::palette() const { return palette_; }
    15 QList<QColor> Tracer::palette() const { return palette_; }
    16 
    16 
    17 void Tracer::setPalette(const QList<QColor>& newPalette) {
    17 void Tracer::setPalette(const QList<QColor> &newPalette) {
    18   if (palette_ == newPalette) return;
    18   if (palette_ == newPalette) return;
    19   palette_ = newPalette;
    19   palette_ = newPalette;
    20   emit paletteChanged();
    20   emit paletteChanged();
    21 }
    21 }
    22 
    22 
    23 double Tracer::bestSolution() const { return bestSolution_; }
    23 double Tracer::bestSolution() const { return bestSolution_; }
    24 
    24 
    25 void Tracer::start(const QString& fileName) {
    25 void Tracer::start(const QString &fileName) {
    26   qDebug() << "Starting using" << fileName;
    26   qDebug() << "Starting using" << fileName;
    27 
    27 
    28   bestSolution_ = 0;
    28   bestSolution_ = 0;
    29   solutions_.clear();
    29   solutions_.clear();
    30   generation_.clear();
    30   generation_.clear();
    31   image_ = QImage{};
    31   referenceImage_ = QImage{};
    32 
    32 
    33   if (palette_.isEmpty()) {
    33   if (palette_.isEmpty()) {
    34     qDebug("Empty palette");
    34     qDebug("Empty palette");
    35     return;
    35     return;
    36   }
    36   }
    37 
    37 
    38   image_.load(QUrl(fileName).toLocalFile());
    38   referenceImage_.load(QUrl(fileName).toLocalFile());
    39 
    39 
    40   if (image_.isNull()) {
    40   if (referenceImage_.isNull()) {
    41     qDebug("Failed to load image");
    41     qDebug("Failed to load image");
    42     return;
    42     return;
    43   }
    43   }
    44 
    44 
    45   for (int i = 0; i < 100; ++i) {
    45   for (int i = 0; i < 600; ++i) {
    46     generation_.append(Solution{{32, 32}, palette_});
    46     generation_.append(Solution{{32, 32}, palette_});
    47   }
    47   }
    48 }
    48 }
    49 
    49 
    50 void Tracer::step() {
    50 void Tracer::step() {
       
    51   const auto size = generation_.size();
       
    52   const auto keepSize = 10;
       
    53   const auto replaceSize = 50;
       
    54   const auto kept = generation_.mid(0, keepSize);
       
    55   generation_ = generation_.mid(0, size - replaceSize);
       
    56 
       
    57   for (int i = 0; i < replaceSize; ++i) {
       
    58     generation_.append(Solution{{32, 32}, palette_});
       
    59   }
       
    60 
       
    61   auto rg = QRandomGenerator::global();
       
    62 
       
    63   for (qsizetype i = 0; i < size; i += 4) {
       
    64     const auto first = rg->bounded(size);
       
    65     const auto second = rg->bounded(size);
       
    66 
       
    67     if (first != second) {
       
    68       generation_[first].crossover(generation_[second]);
       
    69     }
       
    70   }
       
    71 
       
    72   std::for_each(std::begin(generation_), std::end(generation_),
       
    73                 [this](auto &s) { s.mutate(palette_); });
       
    74 
       
    75   std::for_each(std::begin(solutions_), std::end(solutions_),
       
    76                 [this](const auto &fn) { QFile::remove(fn); });
    51   solutions_.clear();
    77   solutions_.clear();
    52 
    78 
    53   for (auto& solution : generation_) {
    79   generation_.append(kept);
    54     const auto fileName = newFileName();
    80 
    55     solutions_.append(fileName);
    81   for (auto &solution : generation_) {
    56 
    82     solution.render(newFileName());
    57     solution.render(fileName);
    83 
    58   }
    84     solution.calculateFitness(referenceImage_);
    59 
    85 
    60   qDebug() << solutions_;
    86     solution.fitness += solution.cost() * 100;
    61 
    87   }
       
    88 
       
    89   std::sort(std::begin(generation_), std::end(generation_),
       
    90             [](const auto &a, const auto &b) { return a.fitness < b.fitness; });
       
    91 
       
    92   std::for_each(std::begin(generation_) + size, std::end(generation_),
       
    93                 [](const auto &s) { QFile::remove(s.fileName); });
       
    94   generation_.remove(size, kept.size());
       
    95 
       
    96   bestSolution_ = generation_[0].fitness;
       
    97 
       
    98   std::transform(std::begin(generation_), std::end(generation_),
       
    99                  std::back_inserter(solutions_),
       
   100                  [](const auto &a) { return a.fileName; });
       
   101 
       
   102   emit bestSolutionChanged();
    62   emit solutionsChanged();
   103   emit solutionsChanged();
    63 }
   104 }
    64 
   105 
    65 QStringList Tracer::solutions() const { return solutions_; }
   106 QStringList Tracer::solutions() const { return solutions_; }
    66 
   107 
    69   counter += 1;
   110   counter += 1;
    70   return tempDir_.filePath(
   111   return tempDir_.filePath(
    71       QStringLiteral("hedgehog_%1.svg").arg(counter, 3, 32, QChar(u'_')));
   112       QStringLiteral("hedgehog_%1.svg").arg(counter, 3, 32, QChar(u'_')));
    72 }
   113 }
    73 
   114 
    74 Solution::Solution(QSizeF size, const QList<QColor>& palette) : size{size} {
   115 Solution::Solution(QSizeF size, const QList<QColor> &palette) : size{size} {
    75   fitness = 0;
   116   fitness = 0;
    76   primitives = {Primitive(size, palette)};
   117   primitives = {Primitive(size, palette), Primitive(size, palette)};
    77 }
   118 }
    78 
   119 
    79 void Solution::render(const QString& fileName) const {
   120 void Solution::calculateFitness(const QImage &target) {
       
   121   QImage candidate{fileName};
       
   122 
       
   123   if (candidate.isNull()) {
       
   124     fitness = 1e32;
       
   125     return;
       
   126   }
       
   127 
       
   128   // Both images assumed same size, same format
       
   129   double diffSum = 0;
       
   130   int width = target.width();
       
   131   int height = target.height();
       
   132 
       
   133   for (int y = 0; y < height; ++y) {
       
   134     auto candScan = reinterpret_cast<const QRgb *>(candidate.scanLine(y));
       
   135     auto targScan = reinterpret_cast<const QRgb *>(target.scanLine(y));
       
   136     for (int x = 0; x < width; ++x) {
       
   137       // Compare RGBA channels
       
   138       const QRgb cPix = candScan[x];
       
   139       const QRgb tPix = targScan[x];
       
   140       // const auto ca = qAlpha(cPix) / 255.0;
       
   141       const auto ta = qAlpha(tPix) / 255.0;
       
   142       const auto dr = qRed(cPix) - qRed(tPix);
       
   143       const auto dg = qGreen(cPix) - qGreen(tPix);
       
   144       const auto db = qBlue(cPix) - qBlue(tPix);
       
   145       const auto da = qAlpha(cPix) - qAlpha(tPix);
       
   146       diffSum +=
       
   147           qMax(qMax(qMax(dr * dr, dg * dg), db * db) * ta, da * da * 1.0);
       
   148     }
       
   149   }
       
   150 
       
   151   fitness = diffSum;
       
   152 }
       
   153 
       
   154 void Solution::render(const QString &fileName) {
       
   155   this->fileName = fileName;
       
   156 
    80   const auto imageSize = size.toSize();
   157   const auto imageSize = size.toSize();
    81 
   158 
    82   QSvgGenerator generator;
   159   QSvgGenerator generator;
    83   generator.setFileName(fileName);
   160   generator.setFileName(fileName);
    84   generator.setSize(imageSize);
   161   generator.setSize(imageSize);
    88 
   165 
    89   QPainter painter;
   166   QPainter painter;
    90   painter.begin(&generator);
   167   painter.begin(&generator);
    91   painter.setRenderHint(QPainter::Antialiasing, true);
   168   painter.setRenderHint(QPainter::Antialiasing, true);
    92 
   169 
    93   for (const auto& primitive : primitives) {
   170   for (const auto &primitive : primitives) {
    94     painter.setPen(primitive.pen);
   171     painter.setPen(primitive.pen);
    95     painter.setBrush(primitive.brush);
   172     painter.setBrush(primitive.brush);
    96     painter.resetTransform();
   173     painter.resetTransform();
    97     painter.translate(primitive.origin);
   174     painter.translate(primitive.origin);
    98     painter.rotate(primitive.rotation);
   175     painter.rotate(primitive.rotation);
   118 double Solution::cost() const {
   195 double Solution::cost() const {
   119   return std::accumulate(primitives.constBegin(), primitives.constEnd(), 0,
   196   return std::accumulate(primitives.constBegin(), primitives.constEnd(), 0,
   120                          [](auto a, auto p) { return a + p.cost(); });
   197                          [](auto a, auto p) { return a + p.cost(); });
   121 }
   198 }
   122 
   199 
   123 Primitive::Primitive(QSizeF size, const QList<QColor>& palette) {
   200 void Solution::mutate(const QList<QColor> &palette) {
       
   201   if (primitives.isEmpty()) {
       
   202     return;
       
   203   }
       
   204 
       
   205   auto rg = QRandomGenerator::global();
       
   206   double mutationRate = 0.05;
       
   207 
       
   208   if (rg->bounded(1.0) > mutationRate) {
       
   209     return;
       
   210   }
       
   211 
       
   212   for (auto &prim : primitives) {
       
   213     // Pen width
       
   214     if (rg->bounded(1.0) < mutationRate) {
       
   215       prim.pen.setWidthF(prim.pen.widthF() * (rg->bounded(1.5) + 0.5) + 0.05);
       
   216     }
       
   217 
       
   218     // Origin
       
   219     if (rg->bounded(1.0) < mutationRate) {
       
   220       prim.origin += QPointF(rg->bounded(10.0) - 5.0, rg->bounded(10.0) - 5.0);
       
   221     }
       
   222 
       
   223     if (prim.type == Polygon) {
       
   224       // Points
       
   225       for (auto &pt : prim.points) {
       
   226         if (rg->bounded(1.0) < mutationRate) {
       
   227           prim.origin +=
       
   228               QPointF(rg->bounded(10.0) - 5.0, rg->bounded(10.0) - 5.0);
       
   229         }
       
   230       }
       
   231     } else {  // Circle/ellipse
       
   232       if (rg->bounded(1.0) < mutationRate) {
       
   233         prim.radius1 *= rg->bounded(0.4) + 0.8;
       
   234       }
       
   235       if (rg->bounded(1.0) < mutationRate) {
       
   236         prim.radius2 *= rg->bounded(0.4) + 0.8;
       
   237       }
       
   238       if (rg->bounded(1.0) < mutationRate) {
       
   239         prim.rotation = rg->bounded(90.0);
       
   240       }
       
   241     }
       
   242   }
       
   243 
       
   244   if (rg->bounded(1.0) < mutationRate) {
       
   245     auto i = rg->bounded(primitives.size());
       
   246 
       
   247     Primitive p{size, palette};
       
   248     primitives.insert(i, p);
       
   249   }
       
   250 
       
   251   if (rg->bounded(1.0) < mutationRate) {
       
   252     auto i = rg->bounded(primitives.size());
       
   253 
       
   254     primitives.remove(i);
       
   255   }
       
   256 }
       
   257 
       
   258 void Solution::crossover(Solution &other) {
       
   259   const auto n = qMin(primitives.size(), other.primitives.size());
       
   260 
       
   261   auto rg = QRandomGenerator::global();
       
   262 
       
   263   if (rg->bounded(1.0) < 0.02) {
       
   264     if (n <= 1) {
       
   265       return;
       
   266     }
       
   267     // swap tails
       
   268     const auto cp = rg->bounded(1, primitives.size());
       
   269     const auto ocp = rg->bounded(1, other.primitives.size());
       
   270 
       
   271     const auto tail = primitives.mid(cp);
       
   272     const auto otherTail = other.primitives.mid(ocp);
       
   273 
       
   274     primitives.remove(cp, primitives.size() - cp);
       
   275     other.primitives.remove(ocp, other.primitives.size() - ocp);
       
   276 
       
   277     primitives.append(otherTail);
       
   278     other.primitives.append(tail);
       
   279   } else {
       
   280     if (n < 1) {
       
   281       return;
       
   282     }
       
   283     // swap one element
       
   284     const auto cp = rg->bounded(primitives.size());
       
   285     const auto ocp = rg->bounded(other.primitives.size());
       
   286 
       
   287     qSwap(primitives[cp], other.primitives[ocp]);
       
   288   }
       
   289 }
       
   290 
       
   291 Primitive::Primitive(QSizeF size, const QList<QColor> &palette) {
   124   auto rg = QRandomGenerator::global();
   292   auto rg = QRandomGenerator::global();
   125   auto randomPoint = [&]() -> QPointF {
   293   auto randomPoint = [&]() -> QPointF {
   126     return {rg->bounded(size.width()), rg->bounded(size.height())};
   294     return {rg->bounded(size.width()), rg->bounded(size.height())};
   127   };
   295   };
   128 
   296 
   132     points.append(randomPoint());
   300     points.append(randomPoint());
   133     points.append(randomPoint());
   301     points.append(randomPoint());
   134   } else {
   302   } else {
   135     type = Circle;
   303     type = Circle;
   136 
   304 
   137     radius1 = rg->bounded(size.width());
   305     radius1 = rg->bounded(size.width() * 0.2) + 2;
   138     radius2 = rg->bounded(size.width());
   306     radius2 = rg->bounded(size.width() * 0.2) + 2;
   139     rotation = rg->bounded(90);
   307     rotation = rg->bounded(90);
   140   }
   308   }
   141 
   309 
   142   pen = QPen(palette[rg->bounded(palette.length())]);
   310   pen = QPen(palette[rg->bounded(palette.length())]);
   143   pen.setWidthF(rg->bounded(size.width() * 0.1));
   311   pen.setWidthF(rg->bounded(size.width() * 0.1));