Advertisement
Guest User

Calculate the most dominant color in an icon from Chromium

a guest
Apr 18th, 2013
322
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 19.67 KB | None | 0 0
  1. // RGBA KMean Constants
  2. const uint kNumberOfClusters = 4;
  3. const int kNumberOfIterations = 50;
  4. const uint kMaxBrightness = 665;
  5. const uint kMinDarkness = 100;
  6.  
  7. private static uint calls_ = 0;
  8.  
  9. // Support class to hold information about each cluster of pixel data in
  10. // the KMean algorithm. While this class does not contain all of the points
  11. // that exist in the cluster, it keeps track of the aggregate sum so it can
  12. // compute the new center appropriately.
  13. internal class KMeanCluster {
  14.  
  15.   private uint[] centroid = new uint[3]; //[3];
  16.  
  17.   // Holds the sum of all the points that make up this cluster. Used to
  18.   // generate the next centroid as well as to check for convergence.
  19.   private uint[] aggregate = new uint[3]; //[3];
  20.   private uint counter;
  21.  
  22.   // The weight of the cluster, determined by how many points were used
  23.   // to generate the previous centroid.
  24.   private uint weight;
  25.  
  26.  
  27.   public KMeanCluster() {
  28.     Reset();
  29.   }
  30.  
  31.   public void Reset() {
  32.     centroid[0] = centroid[1] = centroid[2] = 0;
  33.     aggregate[0] = aggregate[1] = aggregate[2] = 0;
  34.     counter = 0;
  35.     weight = 0;
  36.   }
  37.  
  38.   public void SetCentroid(uint r, uint g, uint b) {
  39.     centroid[0] = r;
  40.     centroid[1] = g;
  41.     centroid[2] = b;
  42.   }
  43.  
  44.   public void GetCentroid(ref uint r, ref uint g, ref uint b) {
  45.     r = centroid[0];
  46.     g = centroid[1];
  47.     b = centroid[2];
  48.   }
  49.  
  50.   public bool IsAtCentroid(uint r, uint g, uint b) {
  51.     return r == centroid[0] && g == centroid[1] && b == centroid[2];
  52.   }
  53.  
  54.   // Recomputes the centroid of the cluster based on the aggregate data. The
  55.   // number of points used to calculate this center is stored for weighting
  56.   // purposes. The aggregate and counter are then cleared to be ready for the
  57.   // next iteration.
  58.   public void RecomputeCentroid() {
  59.     if (counter > 0) {
  60.       centroid[0] = aggregate[0] / counter;
  61.       centroid[1] = aggregate[1] / counter;
  62.       centroid[2] = aggregate[2] / counter;
  63.  
  64.       aggregate[0] = aggregate[1] = aggregate[2] = 0;
  65.       weight = counter;
  66.       counter = 0;
  67.     }
  68.   }
  69.  
  70.   public void AddPoint(uint r, uint g, uint b) {
  71.     aggregate[0] += r;
  72.     aggregate[1] += g;
  73.     aggregate[2] += b;
  74.     ++counter;
  75.   }
  76.  
  77.   // Just returns the distance^2. Since we are comparing relative distances
  78.   // there is no need to perform the expensive sqrt() operation.
  79.   public uint GetDistanceSqr(uint r, uint g, uint b) {
  80.     return (r - centroid[0]) * (r - centroid[0]) +
  81.            (g - centroid[1]) * (g - centroid[1]) +
  82.            (b - centroid[2]) * (b - centroid[2]);
  83.   }
  84.  
  85.   // In order to determine if we have hit convergence or not we need to see
  86.   // if the centroid of the cluster has moved. This determines whether or
  87.   // not the centroid is the same as the aggregate sum of points that will be
  88.   // used to generate the next centroid.
  89.   public bool CompareCentroidWithAggregate() {
  90.     if (counter == 0)
  91.       return false;
  92.  
  93.     return aggregate[0] / counter == centroid[0] &&
  94.            aggregate[1] / counter == centroid[1] &&
  95.            aggregate[2] / counter == centroid[2];
  96.   }
  97.  
  98.   // Returns the previous counter, which is used to determine the weight
  99.   // of the cluster for sorting.
  100.   public uint GetWeight()  {
  101.     return weight;
  102.   }
  103.  
  104.   static bool SortKMeanClusterByWeight(KMeanCluster a, KMeanCluster b) {
  105.     return a.GetWeight() > b.GetWeight();
  106.   }
  107.  
  108. };
  109.  
  110.  
  111. private static int GridSampler_GetSample(int width, int height) {
  112.   // Hand-drawn bitmaps often have special outlines or feathering at the edges.
  113.   // Start our sampling inset from the top and left edges. For example, a 10x10
  114.   // image with 4 clusters would be sampled like this:
  115.   // ..........
  116.   // .0.4.8....
  117.   // ..........
  118.   // .1.5.9....
  119.   // ..........
  120.   // .2.6......
  121.   // ..........
  122.   // .3.7......
  123.   // ..........
  124.   const int kPadX = 1;
  125.   const int kPadY = 1;
  126.   int x = kPadX + (calls_ / kNumberOfClusters) * ((width - 2 * kPadX) / kNumberOfClusters);
  127.   int y = kPadY + (calls_ % kNumberOfClusters) * ((height - 2 * kPadY) / kNumberOfClusters);
  128.   int index = x + (y * width);
  129.   ++calls_;
  130.   return index % (width * height);
  131. }
  132.  
  133. // Returns the color in an ARGB |image| that is closest in RGB-space to the
  134. // provided |color|. Exported for testing.
  135. public static Color FindClosestColor(LockedBitmap image,int width, int height, Color color) {
  136.   uint in_r = color.R;
  137.   uint in_g = color.G;
  138.   uint in_b = color.B;
  139.  
  140.   // Search using distance-squared to avoid expensive sqrt() operations.
  141.   int best_distance_squared = kint32max;
  142.   Color best_color = color;
  143.   const uint* byte = image;
  144.   for (int i = 0; i < width * height; ++i) {
  145.     uint b = *(byte++);
  146.     uint g = *(byte++);
  147.     uint r = *(byte++);
  148.     uint a = *(byte++);
  149.  
  150.     // Ignore fully transparent pixels.
  151.     if (a == 0)
  152.       continue;
  153.  
  154.     int distance_squared = (in_b - b) * (in_b - b) + (in_g - g) * (in_g - g) +(in_r - r) * (in_r - r);
  155.     if (distance_squared < best_distance_squared) {
  156.       best_distance_squared = distance_squared;
  157.       best_color = new Color(r, g, b);
  158.     }
  159.   }
  160.   return best_color;
  161. }
  162.  
  163. // Returns an Color that represents the calculated dominant color in the png.
  164. // This uses a KMean clustering algorithm to find clusters of pixel colors in
  165. // RGB space.
  166. // |png| represents the data of a png encoded image.
  167. // |darkness_limit| represents the minimum sum of the RGB components that is
  168. // acceptable as a color choice. This can be from 0 to 765.
  169. // |brightness_limit| represents the maximum sum of the RGB components that is
  170. // acceptable as a color choice. This can be from 0 to 765.
  171. //
  172. // RGB KMean Algorithm (N clusters, M iterations):
  173. // 1.Pick N starting colors by randomly sampling the pixels. If you see a
  174. //   color you already saw keep sampling. After a certain number of tries
  175. //   just remove the cluster and continue with N = N-1 clusters (for an image
  176. //   with just one color this should devolve to N=1). These colors are the
  177. //   centers of your N clusters.
  178. // 2.For each pixel in the image find the cluster that it is closest to in RGB
  179. //   space. Add that pixel's color to that cluster (we keep a sum and a count
  180. //   of all of the pixels added to the space, so just add it to the sum and
  181. //   increment count).
  182. // 3.Calculate the new cluster centroids by getting the average color of all of
  183. //   the pixels in each cluster (dividing the sum by the count).
  184. // 4.See if the new centroids are the same as the old centroids.
  185. //     a) If this is the case for all N clusters than we have converged and
  186. //        can move on.
  187. //     b) If any centroid moved, repeat step 2 with the new centroids for up
  188. //        to M iterations.
  189. // 5.Once the clusters have converged or M iterations have been tried, sort
  190. //   the clusters by weight (where weight is the number of pixels that make up
  191. //   this cluster).
  192. // 6.Going through the sorted list of clusters, pick the first cluster with the
  193. //   largest weight that's centroid fulfills the equation
  194. //   |darkness_limit| < SUM(R, G, B) < |brightness_limit|. Return that color.
  195. //   If no color fulfills that requirement return the color with the largest
  196. //   weight regardless of whether or not it fulfills the equation above.
  197. //
  198. // Note: Switching to HSV space did not improve the results of this algorithm
  199. // for typical favicon images.
  200.  
  201. // For a 16x16 icon on an Intel Core i5 this function takes approximately
  202. // 0.5 ms to run.
  203. // TODO(port): This code assumes the CPU architecture is little-endian.
  204. public static Color CalculateKMeanColor(LockedBitmap decoded_data,int img_width,int img_height,uint darkness_limit,uint brightness_limit) {
  205.   Color color = Color.White;
  206.   calls_ = 0;
  207.   if (img_width > 0 && img_height > 0) {
  208.     std::vector<KMeanCluster> clusters;
  209.     clusters.resize(kNumberOfClusters, KMeanCluster());
  210.  
  211.     // Pick a starting point for each cluster
  212.     std::vector<KMeanCluster>::iterator cluster = clusters.begin();
  213.     while (cluster != clusters.end()) {
  214.       // Try up to 10 times to find a unique color. If no unique color can be
  215.       // found, destroy this cluster.
  216.       bool color_unique = false;
  217.       for (int i = 0; i < 10; ++i) {
  218.         int pixel_pos = GridSampler_GetSample(img_width, img_height) % (img_width * img_height);
  219.  
  220.         uint b = decoded_data[pixel_pos * 4];
  221.         uint g = decoded_data[pixel_pos * 4 + 1];
  222.         uint r = decoded_data[pixel_pos * 4 + 2];
  223.         uint a = decoded_data[pixel_pos * 4 + 3];
  224.         // Skip fully transparent pixels as they usually contain black in their
  225.         // RGB channels but do not contribute to the visual image.
  226.         if (a == 0)
  227.           continue;
  228.  
  229.         // Loop through the previous clusters and check to see if we have seen
  230.         // this color before.
  231.         color_unique = true;
  232.         for (std::vector<KMeanCluster>::iterator; cluster_check = clusters.begin(); cluster_check != cluster; ++cluster_check) {
  233.           if (cluster_check->IsAtCentroid(r, g, b)) {
  234.             color_unique = false;
  235.             break;
  236.           }
  237.         }
  238.  
  239.         // If we have a unique color set the center of the cluster to
  240.         // that color.
  241.         if (color_unique) {
  242.           cluster->SetCentroid(r, g, b);
  243.           break;
  244.         }
  245.       }
  246.  
  247.       // If we don't have a unique color erase this cluster.
  248.       if (!color_unique) {
  249.         cluster = clusters.erase(cluster);
  250.       } else {
  251.         // Have to increment the iterator here, otherwise the increment in the
  252.         // for loop will skip a cluster due to the erase if the color wasn't
  253.         // unique.
  254.         ++cluster;
  255.       }
  256.     }
  257.  
  258.     // If all pixels in the image are transparent we will have no clusters.
  259.     if (clusters.empty())
  260.       return color;
  261.  
  262.     bool convergence = false;
  263.     for (int iteration = 0;iteration < kNumberOfIterations && !convergence;++iteration) {
  264.  
  265.       // Loop through each pixel so we can place it in the appropriate cluster.
  266.       uint* pixel = decoded_data;
  267.       uint* decoded_data_end = decoded_data + (img_width * img_height * 4);
  268.       while (pixel < decoded_data_end) {
  269.         uint b = *(pixel++);
  270.         uint g = *(pixel++);
  271.         uint r = *(pixel++);
  272.         uint a = *(pixel++);
  273.         // Skip transparent pixels, see above.
  274.         if (a == 0)
  275.           continue;
  276.  
  277.         uint distance_sqr_to_closest_cluster = UINT_MAX;
  278.         std::vector<KMeanCluster>::iterator closest_cluster = clusters.begin();
  279.  
  280.         // Figure out which cluster this color is closest to in RGB space.
  281.         for (std::vector<KMeanCluster>::iterator cluster = clusters.begin();
  282.             cluster != clusters.end(); ++cluster) {
  283.           uint distance_sqr = cluster->GetDistanceSqr(r, g, b);
  284.  
  285.           if (distance_sqr < distance_sqr_to_closest_cluster) {
  286.             distance_sqr_to_closest_cluster = distance_sqr;
  287.             closest_cluster = cluster;
  288.           }
  289.         }
  290.  
  291.         closest_cluster->AddPoint(r, g, b);
  292.       }
  293.  
  294.       // Calculate the new cluster centers and see if we've converged or not.
  295.       convergence = true;
  296.       for (std::vector<KMeanCluster>::iterator cluster = clusters.begin();
  297.           cluster != clusters.end(); ++cluster) {
  298.         convergence &= cluster->CompareCentroidWithAggregate();
  299.  
  300.         cluster->RecomputeCentroid();
  301.       }
  302.     }
  303.  
  304.     // Sort the clusters by population so we can tell what the most popular
  305.     // color is.
  306.     std::sort(clusters.begin(), clusters.end(), KMeanCluster::SortKMeanClusterByWeight);
  307.  
  308.     // Loop through the clusters to figure out which cluster has an appropriate
  309.     // color. Skip any that are too bright/dark and go in order of weight.
  310.     for (std::vector<KMeanCluster>::iterator cluster = clusters.begin(); cluster != clusters.end(); ++cluster) {
  311.       uint r, g, b;
  312.       cluster->GetCentroid(&r, &g, &b);
  313.       // Sum the RGB components to determine if the color is too bright or too
  314.       // dark.
  315.       // TODO (dtrainor): Look into using HSV here instead. This approximation
  316.       // might be fine though.
  317.       uint summed_color = r + g + b;
  318.  
  319.       if (summed_color < brightness_limit && summed_color > darkness_limit) {
  320.         // If we found a valid color just set it and break. We don't want to
  321.         // check the other ones.
  322.         color = SkColorSetARGB(0xFF, r, g, b);
  323.         break;
  324.       } else if (cluster == clusters.begin()) {
  325.         // We haven't found a valid color, but we are at the first color so
  326.         // set the color anyway to make sure we at least have a value here.
  327.         color = SkColorSetARGB(0xFF, r, g, b);
  328.       }
  329.     }
  330.   }
  331.  
  332.   // Find a color that actually appears in the image (the K-mean cluster center
  333.   // will not usually be a color that appears in the image).
  334.   return FindClosestColor(decoded_data, img_width, img_height, color);
  335. }
  336.  
  337. // Compute color covariance matrix for the input bitmap.
  338. public static gfx::Matrix3F ComputeColorCovariance(LockedBitmap bitmap) {
  339.   // First need basic stats to normalize each channel separately.
  340.   SkAutoLockPixels bitmap_lock(bitmap);
  341.   gfx::Matrix3F covariance = gfx::Matrix3F::Zeros();
  342.   if (!bitmap.getPixels())
  343.     return covariance;
  344.  
  345.   // Assume ARGB_8888 format.
  346.   //DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config);
  347.  
  348.   long r_sum = 0;
  349.   long g_sum = 0;
  350.   long b_sum = 0;
  351.   long rr_sum = 0;
  352.   long gg_sum = 0;
  353.   long bb_sum = 0;
  354.   long rg_sum = 0;
  355.   long rb_sum = 0;
  356.   long gb_sum = 0;
  357.  
  358.   for (int y = 0; y < bitmap.height(); ++y) {
  359.     SkPMColor* current_color = static_cast<uint*>(bitmap.getAddr32(0, y));
  360.     for (int x = 0; x < bitmap.width(); ++x, ++current_color) {
  361.       Color c = SkUnPreMultiply::PMColorToColor(*current_color);
  362.       Color r = SkColorGetR(c);
  363.       Color g = SkColorGetG(c);
  364.       Color b = SkColorGetB(c);
  365.  
  366.       r_sum += r;
  367.       g_sum += g;
  368.       b_sum += b;
  369.       rr_sum += r * r;
  370.       gg_sum += g * g;
  371.       bb_sum += b * b;
  372.       rg_sum += r * g;
  373.       rb_sum += r * b;
  374.       gb_sum += g * b;
  375.     }
  376.   }
  377.  
  378.   // Covariance (not normalized) is E(X*X.t) - m * m.t and this is how it
  379.   // is calculated below.
  380.   // Each row below represents a row of the matrix describing (co)variances
  381.   // of R, G and B channels with (R, G, B)
  382.   int pixel_n = bitmap.width() * bitmap.height();
  383.   covariance.set(
  384.       (static_cast<double>(rr_sum) / pixel_n -
  385.        static_cast<double>(r_sum * r_sum) / pixel_n / pixel_n),
  386.       (static_cast<double>(rg_sum) / pixel_n -
  387.        static_cast<double>(r_sum * g_sum) / pixel_n / pixel_n),
  388.       (static_cast<double>(rb_sum) / pixel_n -
  389.        static_cast<double>(r_sum * b_sum) / pixel_n / pixel_n),
  390.       (static_cast<double>(rg_sum) / pixel_n -
  391.        static_cast<double>(r_sum * g_sum) / pixel_n / pixel_n),
  392.       (static_cast<double>(gg_sum) / pixel_n -
  393.        static_cast<double>(g_sum * g_sum) / pixel_n / pixel_n),
  394.       (static_cast<double>(gb_sum) / pixel_n -
  395.        static_cast<double>(g_sum * b_sum) / pixel_n / pixel_n),
  396.       (static_cast<double>(rb_sum) / pixel_n -
  397.        static_cast<double>(r_sum * b_sum) / pixel_n / pixel_n),
  398.       (static_cast<double>(gb_sum) / pixel_n -
  399.        static_cast<double>(g_sum * b_sum) / pixel_n / pixel_n),
  400.       (static_cast<double>(bb_sum) / pixel_n -
  401.        static_cast<double>(b_sum * b_sum) / pixel_n / pixel_n));
  402.   return covariance;
  403. }
  404.  
  405. // Apply a color reduction transform defined by |color_transform| vector to
  406. // |source_bitmap|. The result is put into |target_bitmap|, which is expected
  407. // to be initialized to the required size and type (SkBitmap::kA8_Config).
  408. // If |fit_to_range|, result is transfored linearly to fit 0-0xFF range.
  409. // Otherwise, data is clipped.
  410. // Returns true if the target has been computed.
  411. public static bool ApplyColorReduction(LockedBitmap source_bitmap, const gfx::Vector3dF& color_transform,bool fit_to_range, LockedBitmap target_bitmap) {
  412.   //DCHECK(target_bitmap);
  413.   SkAutoLockPixels source_lock(source_bitmap);
  414.   SkAutoLockPixels target_lock(*target_bitmap);
  415.  
  416.   //DCHECK(source_bitmap.getPixels());
  417.   //DCHECK(target_bitmap->getPixels());
  418.   //DCHECK_EQ(SkBitmap::kARGB_8888_Config, source_bitmap.config());
  419.   //DCHECK_EQ(SkBitmap::kA8_Config, target_bitmap->config());
  420.   //DCHECK_EQ(source_bitmap.height(), target_bitmap->height());
  421.   //DCHECK_EQ(source_bitmap.width(), target_bitmap->width());
  422.   //DCHECK(!source_bitmap.empty());
  423.  
  424.   // Elements of color_transform are explicitly off-loaded to local values for
  425.   // efficiency reasons. Note that in practice images may correspond to entire
  426.   // tab captures.
  427.   float t0 = 0.0;
  428.   float tr = color_transform.x();
  429.   float tg = color_transform.y();
  430.   float tb = color_transform.z();
  431.  
  432.   if (fit_to_range) {
  433.     // We will figure out min/max in a preprocessing step and adjust
  434.     // actual_transform as required.
  435.     float max_val = std::numeric_limits<float>::min();
  436.     float min_val = std::numeric_limits<float>::max();
  437.     for (int y = 0; y < source_bitmap.height(); ++y) {
  438.       const SkPMColor* source_color_row = static_cast<SkPMColor*>(
  439.           source_bitmap.getAddr32(0, y));
  440.       for (int x = 0; x < source_bitmap.width(); ++x) {
  441.         Color c = SkUnPreMultiply::PMColorToColor(source_color_row[x]);
  442.         float r = SkColorGetR(c);
  443.         float g = SkColorGetG(c);
  444.         float b = SkColorGetB(c);
  445.         float gray_level = tr * r + tg * g + tb * b;
  446.         max_val = std::max(max_val, gray_level);
  447.         min_val = std::min(min_val, gray_level);
  448.       }
  449.     }
  450.  
  451.     // Adjust the transform so that the result is scaling.
  452.     float scale = 0.0;
  453.     t0 = -min_val;
  454.     if (max_val > min_val)
  455.       scale = 255.0 / (max_val - min_val);
  456.     t0 *= scale;
  457.     tr *= scale;
  458.     tg *= scale;
  459.     tb *= scale;
  460.   }
  461.  
  462.   for (int y = 0; y < source_bitmap.height(); ++y) {
  463.     const SkPMColor* source_color_row = static_cast<SkPMColor*>(
  464.         source_bitmap.getAddr32(0, y));
  465.     uint* target_color_row = target_bitmap->getAddr8(0, y);
  466.     for (int x = 0; x < source_bitmap.width(); ++x) {
  467.       Color c = SkUnPreMultiply::PMColorToColor(source_color_row[x]);
  468.       float r = SkColorGetR(c);
  469.       float g = SkColorGetG(c);
  470.       float b = SkColorGetB(c);
  471.  
  472.       float gl = t0 + tr * r + tg * g + tb * b;
  473.       if (gl < 0)
  474.         gl = 0;
  475.       if (gl > 0xFF)
  476.         gl = 0xFF;
  477.       target_color_row[x] = static_cast<uint>(gl);
  478.     }
  479.   }
  480.  
  481.   return true;
  482. }
  483.  
  484. // Compute a monochrome image representing the principal color component of
  485. // the |source_bitmap|. The result is stored in |target_bitmap|, which must be
  486. // initialized to the required size and type (SkBitmap::kA8_Config).
  487. // Returns true if the conversion succeeded. Note that there might be legitimate
  488. // reasons for the process to fail even if all input was correct. This is a
  489. // condition the caller must be able to handle.
  490. public static bool ComputePrincipalComponentImage(LockedBitmap source_bitmap, LockedBitmap target_bitmap) {
  491.  
  492.   gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap);
  493.   gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros();
  494.   gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors);
  495.   gfx::Vector3dF principal = eigenvectors.get_column(0);
  496.   if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF())
  497.     return false;  // This may happen for some edge cases.
  498.   return ApplyColorReduction(source_bitmap, principal, true, target_bitmap);
  499. }
  500.  
  501.  
  502.  
  503. // Un-premultiplies each pixel in |bitmap| into an output |buffer|. Requires
  504. // approximately 10 microseconds for a 16x16 icon on an Intel Core i5.
  505. /*public static void UnPreMultiply(SkBitmap& bitmap, uint* buffer, int buffer_size) {
  506.   SkAutoLockPixels auto_lock(bitmap);
  507.   uint* in = static_cast<uint*>(bitmap.getPixels());
  508.   uint* out = buffer;
  509.   int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size);
  510.   for (int i = 0; i < pixel_count; ++i){
  511.     *out++ = SkUnPreMultiply::PMColorToColor(*in++);
  512.     }
  513. }*/
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement