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 }