File indexing completed on 2024-12-15 04:01:21

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "quantize.hpp"
0008 
0009 #include <QHash>
0010 
0011 #include <unordered_map>
0012 #include <memory>
0013 
0014 using namespace glaxnimate;
0015 
0016 namespace glaxnimate::utils::quantize::detail {
0017 
0018 static bool freq_sort_cmp(const ColorFrequency& a, const ColorFrequency& b) noexcept
0019 {
0020     return a.second > b.second;
0021 }
0022 
0023 std::vector<QRgb> color_frequencies_to_palette(std::vector<utils::quantize::ColorFrequency>& freq, int max)
0024 {
0025     std::sort(freq.begin(), freq.end(), detail::freq_sort_cmp);
0026 
0027     int count = qMin<int>(max, freq.size());
0028     std::vector<QRgb> out;
0029     out.reserve(count);
0030 
0031     for ( int i = 0; i < count; i++ )
0032         out.push_back(freq[i].first);
0033 
0034     return out;
0035 }
0036 
0037 using Distance = quint32;
0038 
0039 struct Color
0040 {
0041     qint32 r;
0042     qint32 g;
0043     qint32 b;
0044 
0045     constexpr Color(QRgb rgb) noexcept
0046     : r(qRed(rgb)), g(qGreen(rgb)), b(qBlue(rgb))
0047     {}
0048 
0049     constexpr Color() noexcept
0050     : r(0), g(0), b(0)
0051     {}
0052 
0053     constexpr Color(qint32 r, qint32 g, qint32 b) noexcept
0054     : r(r), g(g), b(b)
0055     {}
0056 
0057     constexpr Distance distance(const Color& oth) const noexcept
0058     {
0059         quint32 dr = r - oth.r;
0060         quint32 dg = g - oth.g;
0061         quint32 db = b - oth.b;
0062 
0063         return dr * dr + dg * dg + db * db;
0064     }
0065 
0066     constexpr QRgb rgb() const noexcept
0067     {
0068         return qRgb(r, g, b);
0069     }
0070 
0071     constexpr void weighted_add(const Color& oth, int weight) noexcept
0072     {
0073         r += oth.r * weight;
0074         g += oth.g * weight;
0075         b += oth.b * weight;
0076     }
0077 
0078     constexpr Color& operator+=(const Color& oth) noexcept
0079     {
0080         weighted_add(oth, 1);
0081         return *this;
0082     }
0083 
0084     Color mean(qreal total_weight) const noexcept
0085     {
0086         return {
0087             qRound(r / total_weight),
0088             qRound(g / total_weight),
0089             qRound(b / total_weight)
0090         };
0091     }
0092 };
0093 
0094 using HistogramMap = std::unordered_map<ColorFrequency::first_type, ColorFrequency::second_type>;
0095 
0096 HistogramMap color_frequency_map(QImage image, int alpha_threshold)
0097 {
0098     if ( image.format() != QImage::Format_RGBA8888 )
0099         image = image.convertToFormat(QImage::Format_RGBA8888);
0100 
0101     HistogramMap count;
0102     const uchar* data = image.constBits();
0103 
0104     int n_pixels = image.width() * image.height();
0105     for ( int i = 0; i < n_pixels; i++ )
0106         if ( data[i*4+3] >= alpha_threshold )
0107             ++count[qRgb(data[i*4], data[i*4+1], data[i*4+2])];
0108 
0109     return count;
0110 }
0111 
0112 } // utils::quantize::detail
0113 
0114 std::vector<utils::quantize::ColorFrequency> utils::quantize::color_frequencies(const QImage& image, int alpha_threshold)
0115 {
0116     auto count = detail::color_frequency_map(image, alpha_threshold);
0117     return std::vector<ColorFrequency>(count.begin(), count.end());
0118 }
0119 
0120 
0121 std::vector<QRgb> utils::quantize::k_modes(const QImage& image, int k)
0122 {
0123     auto freq = color_frequencies(image);
0124     return detail::color_frequencies_to_palette(freq, k);
0125 }
0126 
0127 
0128 
0129 namespace glaxnimate::utils::quantize::detail::k_means {
0130 
0131 struct Point
0132 {
0133     Color color;
0134 
0135     quint32 weight;
0136 
0137     Distance min_distance = std::numeric_limits<Distance>::max();
0138 
0139     int cluster = -1;
0140 
0141 
0142     constexpr Point(const ColorFrequency& p) noexcept
0143         : color(p.first), weight(p.second)
0144     {}
0145 };
0146 
0147 
0148 struct Cluster
0149 {
0150     Color centroid;
0151     quint32 total_weight = 0;
0152     Color sum = {};
0153 
0154     constexpr Cluster(const Color& color) noexcept
0155         : centroid(color)
0156     {}
0157 
0158     bool update()
0159     {
0160         if ( total_weight == 0 )
0161             return false;
0162 
0163         auto old = centroid;
0164 
0165         centroid.r = qRound(double(sum.r) / total_weight);
0166         centroid.g = qRound(double(sum.g) / total_weight);
0167         centroid.b = qRound(double(sum.b) / total_weight);
0168 
0169         total_weight = 0;
0170         sum = {};
0171         return old.rgb() != centroid.rgb();
0172     }
0173 };
0174 
0175 } // utils::quantize::detail
0176 
0177 std::vector<QRgb> utils::quantize::k_means(const QImage& image, int k, int iterations, KMeansMatch match)
0178 {
0179     auto freq = color_frequencies(image);
0180 
0181     // Avoid processing if we don't need to
0182     if ( int(freq.size()) <= k )
0183         return detail::color_frequencies_to_palette(freq, k);
0184 
0185     // Initialize points
0186     std::vector<detail::k_means::Point> points(freq.begin(), freq.end());
0187     freq.clear();
0188 
0189     // Keep track of the clusters we already used
0190     std::vector<detail::k_means::Point> cluster_init;
0191     cluster_init.reserve(k);
0192 
0193     std::vector<detail::k_means::Cluster> clusters;
0194     clusters.reserve(k);
0195 
0196     // Get the most common color as initial cluster centroid
0197     quint32 max_freq = 0;
0198     std::vector<detail::k_means::Point>::iterator best_iter;
0199     for ( auto it = points.begin(); it != points.end(); ++it )
0200     {
0201         if ( it->weight > max_freq )
0202         {
0203             max_freq = it->weight;
0204             best_iter = it;
0205         }
0206     }
0207     cluster_init.push_back(*best_iter);
0208     clusters.emplace_back(best_iter->color);
0209     // remove centroid from points
0210     std::swap(*best_iter, points.back());
0211     points.pop_back();
0212 
0213     // k-means++-like processing from now on (but deterministic)
0214     // ie: always select the centroid the farthest away from the other centroids
0215     while ( int(clusters.size()) < k )
0216     {
0217         detail::Distance max_dist = 0;
0218 
0219         for ( auto it = points.begin(); it != points.end(); ++it )
0220         {
0221             detail::Distance p_max = 0;
0222             for ( const auto& cluster : clusters )
0223             {
0224                 auto dist = it->color.distance(cluster.centroid);
0225                 if ( dist < p_max )
0226                     p_max = dist;
0227             }
0228 
0229             if ( p_max > max_dist )
0230             {
0231                 max_dist = p_max;
0232                 best_iter = it;
0233             }
0234         }
0235 
0236         cluster_init.push_back(*best_iter);
0237         clusters.emplace_back(best_iter->color);
0238         // remove centroid from points
0239         std::swap(*best_iter, points.back());
0240         points.pop_back();
0241     }
0242 
0243     // add back the removed centroids
0244     points.insert(points.end(), cluster_init.begin(), cluster_init.end());
0245     cluster_init.clear();
0246 
0247 
0248     // K-medoids
0249     bool loop = true;
0250     for ( int epoch = 0; epoch < iterations && loop; epoch++ )
0251     {
0252         // Assign points to clusters
0253         for ( int i = 0; i < k; i++ )
0254         {
0255             const auto& cluster = clusters[i];
0256             for ( auto& p : points )
0257             {
0258                 auto dist = p.color.distance(cluster.centroid);
0259                 if (dist < p.min_distance )
0260                 {
0261                     p.min_distance = dist;
0262                     p.cluster = i;
0263                 }
0264             }
0265         }
0266 
0267         // Move centroids
0268         for ( auto& p : points )
0269         {
0270             clusters[p.cluster].total_weight += p.weight;
0271             clusters[p.cluster].sum.weighted_add(p.color, p.weight);
0272             p.min_distance = std::numeric_limits<detail::Distance>::max();
0273         }
0274 
0275         // Quit if nothing has changed
0276         loop = false;
0277         for ( auto& cluster : clusters )
0278             loop = cluster.update() || loop;
0279     }
0280 
0281     // Post-process to find the closest color, reusing total_weight/sum for this
0282     if ( match )
0283     {
0284         for ( auto& cluster : clusters )
0285         {
0286             cluster.total_weight = 0;
0287             cluster.sum = {};
0288         }
0289 
0290 
0291         for ( auto& p : points )
0292         {
0293             auto& cluster = clusters[p.cluster];
0294             auto score = match == MostFrequent ? p.weight :
0295                 std::numeric_limits<quint32>::max() - p.color.distance(cluster.centroid);
0296 
0297             if ( score > cluster.total_weight )
0298             {
0299                 cluster.total_weight = score;
0300                 cluster.sum = p.color;
0301             }
0302         }
0303 
0304         for ( auto& cluster : clusters )
0305         {
0306             cluster.centroid = cluster.sum;
0307         }
0308     }
0309 
0310     std::vector<QRgb> result;
0311     result.reserve(k);
0312     for ( auto& cluster : clusters )
0313         result.push_back(cluster.centroid.rgb());
0314 
0315     return result;
0316 }
0317 
0318 /**
0319  * \note Most of the code here is taken from Inkscape (with several changes)
0320  * \see https://gitlab.com/inkscape/inkscape/-/blob/master/src/trace/quantize.cpp for the original code
0321  */
0322 namespace glaxnimate::utils::quantize::detail::octree {
0323 
0324 
0325 inline Color operator>>(Color rgb, int s)
0326 {
0327   Color res;
0328   res.r = rgb.r >> s; res.g = rgb.g >> s; res.b = rgb.b >> s;
0329   return res;
0330 }
0331 inline bool operator==(Color rgb1, Color rgb2)
0332 {
0333   return (rgb1.r == rgb2.r && rgb1.g == rgb2.g && rgb1.b == rgb2.b);
0334 }
0335 
0336 inline int childIndex(Color rgb)
0337 {
0338     return (((rgb.r)&1)<<2) | (((rgb.g)&1)<<1) | (((rgb.b)&1));
0339 }
0340 
0341 
0342 struct Node
0343 {
0344     Node *parent = nullptr;
0345     std::unique_ptr<Node> children[8];
0346     // number of children
0347     int nchild = 0;
0348     // width level of this node
0349     int width = 0;
0350     // rgb's prefix of that node
0351     Color rgb;
0352     // number of pixels this node accounts for
0353     quint32 weight = 0;
0354     // sum of pixels colors this node accounts for
0355     Color sum;
0356     // number of leaves under this node
0357     int nleaf = 0;
0358     // minimum impact
0359     unsigned long mi = 0;
0360 
0361 
0362     /**
0363      * compute the color palette associated to an octree.
0364      */
0365     void get_colors(std::vector<QRgb> &colors)
0366     {
0367         if (nchild == 0)
0368         {
0369             colors.push_back(sum.mean(weight).rgb());
0370         }
0371         else
0372         {
0373             for (auto & i : children)
0374                 if (i)
0375                     i->get_colors(colors);
0376         }
0377     }
0378 
0379     void update_mi()
0380     {
0381         mi = parent ? weight << (2 * parent->width) : 0;
0382     }
0383 };
0384 
0385 
0386 
0387 /**
0388  * builds a single <rgb> color leaf
0389  */
0390 static std::unique_ptr<Node> ocnodeLeaf(Color rgb, quint32 weight)
0391 {
0392     auto node = std::make_unique<Node>();
0393     node->width = 0;
0394     node->rgb = rgb;
0395     node->sum = Color(rgb.r * weight, rgb.g * weight, rgb.b * weight);
0396     node->weight = weight;
0397     node->nleaf = 1;
0398     node->mi = 0;
0399     return node;
0400 }
0401 
0402 
0403 /**
0404  *  merge nodes <node1> and <node2> at location <ref> with parent <parent>
0405  */
0406 static std::unique_ptr<Node> octreeMerge(Node *parent, Node *ref, std::unique_ptr<Node> node1, std::unique_ptr<Node> node2)
0407 {
0408     if (parent && !ref)
0409         parent->nchild++;
0410 
0411     if ( !node1 )
0412     {
0413         node2->parent = parent;
0414         return node2;
0415     }
0416 
0417     if ( !node2 )
0418     {
0419         node1->parent = parent;
0420         return node1;
0421     }
0422 
0423     int dwitdth = node1->width - node2->width;
0424     if (dwitdth > 0 && node1->rgb == node2->rgb >> dwitdth)
0425     {
0426         //place node2 below node1
0427         node1->parent = parent;
0428 
0429         int i = childIndex(node2->rgb >> (dwitdth - 1));
0430         node1->sum += node2->sum;
0431         node1->weight += node2->weight;
0432         node1->mi = 0;
0433         if (node1->children[i])
0434             node1->nleaf -= node1->children[i]->nleaf;
0435 
0436         node1->children[i] = octreeMerge(node1.get(), node1->children[i].get(), std::move(node1->children[i]), std::move(node2));
0437         node1->nleaf += node1->children[i]->nleaf;
0438         return node1;
0439     }
0440     else if (dwitdth < 0 && node2->rgb == node1->rgb >> (-dwitdth))
0441     {
0442         //place node1 below node2
0443         node2->parent = parent;
0444 
0445         int i = childIndex(node1->rgb >> (-dwitdth - 1));
0446         node2->sum += node1->sum;
0447         node2->weight += node1->weight;
0448         node2->mi = 0;
0449         if (node2->children[i])
0450             node2->nleaf -= node2->children[i]->nleaf;
0451         node2->children[i] = octreeMerge(node2.get(), node2->children[i].get(), std::move(node2->children[i]), std::move(node1));
0452         node2->nleaf += node2->children[i]->nleaf;
0453         return node2;
0454     }
0455     else
0456     {
0457         //nodes have either no intersection or the same root
0458         auto newnode = std::make_unique<Node>();
0459         newnode->sum = node1->sum;
0460         newnode->sum += node2->sum;
0461         newnode->weight = node1->weight + node2->weight;
0462 
0463         newnode->parent = parent;
0464 
0465         if (dwitdth == 0 && node1->rgb == node2->rgb)
0466         {
0467             //merge the nodes in <newnode>
0468             newnode->width = node1->width; // == node2->width
0469             newnode->rgb = node1->rgb;     // == node2->rgb
0470             newnode->nchild = 0;
0471             newnode->nleaf = 0;
0472             if (node1->nchild == 0 && node2->nchild == 0)
0473             {
0474                 newnode->nleaf = 1;
0475             }
0476             else
0477             {
0478                 for (int i = 0; i < 8; i++)
0479                 {
0480                     if (node1->children[i] || node2->children[i])
0481                     {
0482                         newnode->children[i] = octreeMerge(newnode.get(), newnode->children[i].get(), std::move(node1->children[i]), std::move(node2->children[i]));
0483                         newnode->nleaf += newnode->children[i]->nleaf;
0484                     }
0485                 }
0486             }
0487 
0488             return newnode;
0489         }
0490         else
0491         {
0492             //use <newnode> as a fork node with children <node1> and <node2>
0493             int newwidth =
0494               node1->width > node2->width ? node1->width : node2->width;
0495             Color rgb1 = node1->rgb >> (newwidth - node1->width);
0496             Color rgb2 = node2->rgb >> (newwidth - node2->width);
0497             //according to the previous tests <rgb1> != <rgb2> before the loop
0498             while ( !(rgb1 == rgb2) )
0499             {
0500                 rgb1 = rgb1 >> 1;
0501                 rgb2 = rgb2 >> 1;
0502                 newwidth++;
0503             }
0504             newnode->width = newwidth;
0505             newnode->rgb = rgb1;  // == rgb2
0506             newnode->nchild = 2;
0507             newnode->nleaf = node1->nleaf + node2->nleaf;
0508             int i1 = childIndex(node1->rgb >> (newwidth - node1->width - 1));
0509             int i2 = childIndex(node2->rgb >> (newwidth - node2->width - 1));
0510             node1->parent = newnode.get();
0511             newnode->children[i1] = std::move(node1);
0512             node2->parent = newnode.get();
0513             newnode->children[i2] = std::move(node2);
0514             return newnode;
0515         }
0516     }
0517 }
0518 
0519 
0520 /**
0521  * remove leaves whose prune impact value is lower than <lvl>. at most
0522  * <count> leaves are removed, and <count> is decreased on each removal.
0523  * all parameters including minimal impact values are regenerated.
0524  */
0525 static std::unique_ptr<Node> ocnodeStrip(std::unique_ptr<Node> node, int *count, unsigned long lvl)
0526 {
0527     if ( !count || !node )
0528         return {};
0529 
0530     if (node->nchild == 0) // leaf node
0531     {
0532         if (!node->mi)
0533             node->update_mi(); //mi generation may be required
0534         if (node->mi > lvl)
0535             return node; //leaf is above strip level
0536 
0537         (*count)--;
0538         return {};
0539     }
0540     else
0541     {
0542         if (node->mi && node->mi > lvl) //node is above strip level
0543             return node;
0544         node->nchild = 0;
0545         node->nleaf = 0;
0546         node->mi = 0;
0547         std::unique_ptr<Node> *lonelychild = nullptr;
0548         for (auto & child : node->children)
0549         {
0550             if ( child )
0551             {
0552                 child = ocnodeStrip(std::move(child), count, lvl);
0553                 if ( child )
0554                 {
0555                     lonelychild = &child;
0556                     node->nchild++;
0557                     node->nleaf += child->nleaf;
0558                     if (!node->mi || node->mi > child->mi)
0559                         node->mi = child->mi;
0560                 }
0561             }
0562         }
0563         // tree adjustments
0564         if (node->nchild == 0)
0565         {
0566             (*count)++;
0567             node->nleaf = 1;
0568             node->update_mi();
0569         }
0570         else if (node->nchild == 1)
0571         {
0572             if ((*lonelychild)->nchild == 0)
0573             {
0574                 //remove the <lonelychild> leaf under a 1 child node
0575                 node->nchild = 0;
0576                 node->nleaf = 1;
0577                 node->update_mi();
0578                 lonelychild->reset();
0579             }
0580             else
0581             {
0582                 //make a bridge to <lonelychild> over a 1 child node
0583                 (*lonelychild)->parent = node->parent;
0584                 return std::move(*lonelychild);
0585             }
0586         }
0587     }
0588     return node;
0589 }
0590 
0591 /**
0592  * reduce the leaves of an octree to a given number
0593  */
0594 static std::unique_ptr<Node> octreePrune(std::unique_ptr<Node> ref, int ncolor)
0595 {
0596     int n = ref->nleaf - ncolor;
0597     if ( n <= 0 )
0598         return ref;
0599 
0600     //calling strip with global minimum impact of the tree
0601     while ( n > 0 && ref )
0602     {
0603         auto mi = ref->mi;
0604         ref = ocnodeStrip(std::move(ref), &n, mi);
0605     }
0606 
0607     return ref;
0608 }
0609 
0610 
0611 std::unique_ptr<Node> add_pixels(Node* ref, ColorFrequency* data, int data_size)
0612 {
0613     if ( data_size == 1 )
0614     {
0615         return ocnodeLeaf(data->first, data->second);
0616     }
0617     else if ( data_size > 1 )
0618     {
0619         std::unique_ptr<Node> ref1 = add_pixels(nullptr, data, data_size/2);
0620         std::unique_ptr<Node> ref2 = add_pixels(nullptr, data + data_size/2, data_size - data_size/2);
0621         return octreeMerge(nullptr, ref, std::move(ref1), std::move(ref2));
0622     }
0623 
0624     return {};
0625 }
0626 
0627 } // namespace glaxnimate::utils::quantize::detail::octree
0628 
0629 
0630 std::vector<QRgb> utils::quantize::octree(const QImage& image, int k)
0631 {
0632     using namespace glaxnimate::utils::quantize::detail::octree;
0633 
0634     auto freq = color_frequencies(image);
0635 
0636     // Avoid processing if we don't need to
0637     if ( int(freq.size()) <= k || k <= 1)
0638         return detail::color_frequencies_to_palette(freq, k);
0639 
0640     std::vector<QRgb> colors;
0641     colors.reserve(k);
0642 
0643 
0644     std::unique_ptr<Node> tree = add_pixels(nullptr, freq.data(), freq.size());
0645     tree = octreePrune(std::move(tree), k);
0646 
0647     tree->get_colors(colors);
0648 
0649     return colors;
0650 }
0651 
0652 static inline qint32 pixel_distance(QRgb p1, QRgb p2)
0653 {
0654     int r1 = qRed(p1);
0655     int g1 = qGreen(p1);
0656     int b1 = qBlue(p1);
0657     int a1 = qAlpha(p1);
0658 
0659     int r2 = qRed(p2);
0660     int g2 = qGreen(p2);
0661     int b2 = qBlue(p2);
0662     int a2 = qAlpha(p2);
0663 
0664     qint32 dr = r1 - r2;
0665     qint32 dg = g1 - g2;
0666     qint32 db = b1 - b2;
0667     qint32 da = a1 - a2;
0668 
0669     return dr * dr + dg * dg + db * db + da * da;
0670 }
0671 
0672 static inline qint32 closest_match(QRgb pixel, const QVector<QRgb> &clut)
0673 {
0674     if ( qAlpha(pixel) < 128 )
0675         return clut.size() - 1;
0676 
0677     int idx = 0;
0678     qint32 current_distance = INT_MAX;
0679     for ( int i = 0; i < clut.size(); ++i)
0680     {
0681         int dist = pixel_distance(pixel, clut[i]);
0682         if (dist < current_distance)
0683         {
0684             current_distance = dist;
0685             idx = i;
0686         }
0687     }
0688     return idx;
0689 }
0690 
0691 static QImage convert_with_palette(const QImage &src, const QVector<QRgb> &clut)
0692 {
0693     QImage dest(src.size(), QImage::Format_Indexed8);
0694     dest.setColorTable(clut);
0695     int h = src.height();
0696     int w = src.width();
0697 
0698     QHash<QRgb, int> cache;
0699 
0700     for (int y=0; y<h; ++y)
0701     {
0702         const QRgb *src_pixels = (const QRgb *) src.scanLine(y);
0703         uchar *dest_pixels = (uchar *) dest.scanLine(y);
0704         for (int x = 0; x < w; ++x)
0705         {
0706             int src_pixel = src_pixels[x];
0707             qint32 value = cache.value(src_pixel, -1);
0708             if (value == -1)
0709             {
0710                 value = closest_match(src_pixel, clut);
0711                 cache.insert(src_pixel, value);
0712             }
0713             dest_pixels[x] = (uchar) value;
0714         }
0715     }
0716 
0717     return dest;
0718 }
0719 
0720 QImage utils::quantize::quantize(const QImage& source, const std::vector<QRgb>& colors)
0721 {
0722 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
0723     QVector<QRgb> vcolors(colors.begin(), colors.end());
0724 #else
0725     QVector<QRgb> vcolors;
0726     vcolors.reserve(colors.size()+1);
0727     for ( auto color : colors )
0728         vcolors.push_back(color);
0729 #endif
0730     vcolors.push_back(qRgba(0, 0, 0, 0));
0731 
0732     return convert_with_palette(source.convertToFormat(QImage::Format_ARGB32), vcolors);
0733 }
0734 
0735 namespace glaxnimate::utils::quantize::detail::auto_colors {
0736 
0737 void decrease(QRgb color, HistogramMap& map)
0738 {
0739     auto it = map.find(color | 0xff000000u);
0740     if ( it != map.end() )
0741         it->second -= 1;
0742 }
0743 
0744 } // namespace glaxnimate::utils::quantize::detail::auto_colors
0745 
0746 
0747 std::vector<QRgb> utils::quantize::edge_exclusion_modes(const QImage& image_in, int max_colors, qreal min_frequency)
0748 {
0749     int alpha_threshold = 128;
0750     QImage image = image_in;
0751     if ( image.format() != QImage::Format_RGBA8888 )
0752         image = image.convertToFormat(QImage::Format_RGBA8888);
0753 
0754     detail::HistogramMap colors = detail::color_frequency_map(image, alpha_threshold);
0755 
0756     if ( int(colors.size()) <= max_colors )
0757     {
0758         auto freq = std::vector<ColorFrequency>(colors.begin(), colors.end());
0759         return detail::color_frequencies_to_palette(freq, max_colors);
0760     }
0761 
0762     std::vector<QRgb> output;
0763     int min_amount = min_frequency * image.width() * image.height();
0764 
0765     while ( int(output.size()) < max_colors && !colors.empty() )
0766     {
0767         auto best = colors.begin();
0768         for ( auto it = best; it != colors.end(); ++it )
0769         {
0770             if ( it->second > best->second )
0771                 best = it;
0772         }
0773 
0774         if ( best->second <= min_amount )
0775             break;
0776 
0777         auto color = best->first;
0778         output.push_back(color);
0779         colors.erase(best);
0780 
0781         for ( int y = 1; y < image.height() - 1; y++ )
0782         {
0783             for ( int x = 1; x < image.width() - 1; x++ )
0784             {
0785                 auto pix = image.pixel(x, y);
0786                 if ( pix == color )
0787                 {
0788                     detail::auto_colors::decrease(image.pixel(x, y-1), colors);
0789                     detail::auto_colors::decrease(image.pixel(x, y+1), colors);
0790                     detail::auto_colors::decrease(image.pixel(x-1, y), colors);
0791                     detail::auto_colors::decrease(image.pixel(x+1, y), colors);
0792                 }
0793             }
0794         }
0795     }
0796 
0797     return output;
0798 }