Palette顧名思義就是一個調色板,它根據傳入的bitmap,提取出幾種主體顏色,這些顏色若是設置到背景或者字體上,就能使界面色彩元素豐富的同時也不失協調。java
它是一個建造者模式的實例化模式,用法很簡單,總的說來分三步:
1.經過builder添加參數:算法
Palette p = Palette.from(BitMap)//將圖片傳入調色板中 .addFilter(Filter)//添加一個過濾器,根據HSL過濾(能夠不設置,有默認值) .maximumColorCount(20)//設置一個顏色種數的臨界值,有效的顏色塊數量小於這個值,直接用這些顏色做爲最終統計到的樣本。有效的顏色塊後面解釋(能夠不設置,有默認值) .resizeBitmapSize(480)//bitmap放入調色板時,會先壓縮,提升計算速率,這裏就是設置壓縮後的尺寸。(能夠不設置,有默認值) .clearFilters()//清除顏色過濾器(能夠不調用) .generate();//第二步,建立Palette實例。
2.構建Palette實例。產生實例的方法generate有無參和有參數的兩個重載方法。數組
3.經過Palette獲取顏色。Palette取得的顏色並非一種,有可能有多個樣本。它提供了6種風格的顏色:app
p.getDarkMutedColor(defaultColor); p.getDarkVibrantColor(defaultColor); p.getLightMutedColor(defaultColor); p.getLightVibrantColor(defaultColor); p.getMutedColor(defaultColor); p.getVibrantColor(defaultColor);~~~~
柔和三種,生動三種。但並不必定每種都採集到了合適的顏色。異步
使用方法講完, 接下來是分析源碼。首先,咱們須要瞭解幾個概念:ide
Palette的取色原理:經過builder構建palette的過程就是量化統計顏色的過程,這個過程是發生在builder的generate()方法裏,首先壓縮圖片至指定(resizeBitmapSize(int size))或者默認大小,從而減小計算量,而後構建一個ColorCutQuantizer用於顏色採集,採集知足要求顏色的邏輯就是它來實現的。接下來把採集到的樣本交給Palette裏的Generator,經過Generator篩選出Muted、vibrant等6種風格顏色。字體
在這個過程當中,最重要的當屬ColorCutQuantizer的採集過程。這裏用到了Median-cut(中位切分法算法)。ui
final class ColorCutQuantizer { ColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) { mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null; mFilters = filters; //建立顏色直方圖的數組,即橫座標。 final int[] hist = mHistogram = new int[1 << (QUANTIZE_WORD_WIDTH * 3)]; //將圖片中每一個像素的顏色值做近似值處理 for (int i = 0; i < pixels.length; i++) { final int quantizedColor = quantizeFromRgb888(pixels[i]); pixels[i] = quantizedColor; //將統計直方圖中的縱座標值 hist[quantizedColor]++; } if (LOG_TIMINGS) { mTimingLogger.addSplit("Histogram created"); } // 根據filters定義的規則過濾掉不須要統計的顏色 int distinctColorCount = 0; for (int color = 0; color < hist.length; color++) { if (hist[color] > 0 && shouldIgnoreColor(color)) { // If we should ignore the color, set the population to 0 hist[color] = 0; } if (hist[color] > 0) { // If the color has population, increase the distinct color count distinctColorCount++; } } if (LOG_TIMINGS) { mTimingLogger.addSplit("Filtered colors and distinct colors counted"); } //只保留直方圖中統計值大於0的顏色值 final int[] colors = mColors = new int[distinctColorCount]; int distinctColorIndex = 0; for (int color = 0; color < hist.length; color++) { if (hist[color] > 0) { colors[distinctColorIndex++] = color; } } if (LOG_TIMINGS) { mTimingLogger.addSplit("Distinct colors copied into array"); } if (distinctColorCount <= maxColors) { // 圖片顏色較少時,直接將這些顏色做爲樣本返回。 mQuantizedColors = new ArrayList<>(); for (int color : colors) { mQuantizedColors.add(new Palette.Swatch(approximateToRgb888(color), hist[color])); } if (LOG_TIMINGS) { mTimingLogger.addSplit("Too few colors present. Copied to Swatches"); mTimingLogger.dumpToLog(); } } else { // 若是圖片有效顏色多餘設定的顏色提取最大值,須要經過中位切分算法提取主要顏色 mQuantizedColors = quantizePixels(maxColors); if (LOG_TIMINGS) { mTimingLogger.addSplit("Quantized colors computed"); mTimingLogger.dumpToLog(); } } } }
ColorCutQuantizer量化顏色的步驟以下:spa
將圖片中每一個像素的顏色轉換爲RGB555的顏色值,並統計到對應直方圖中。量化位數從8bit到5bit,取原8bit的高位,量化上作了壓縮,固然損失了精度。code
private static int quantizeFromRgb888(int color) { int r = modifyWordWidth(Color.red(color), 8, QUANTIZE_WORD_WIDTH); int g = modifyWordWidth(Color.green(color), 8, QUANTIZE_WORD_WIDTH); int b = modifyWordWidth(Color.blue(color), 8, QUANTIZE_WORD_WIDTH); return r << (QUANTIZE_WORD_WIDTH + QUANTIZE_WORD_WIDTH) | g << QUANTIZE_WORD_WIDTH | b; }
下面先了解下中位切分算法,做爲一種量化顏色的算法,對彩色空間Vbox進行切分。要知道切分原則前,咱們先舉例瞭解下Vbox,假設當前定義了4個彩色空間Vbox0、Vbox一、Vbox二、Vbox3,Vbox0爲Vbox一、Vbox二、Vbox3的並集,且Vbox一、Vbox二、Vbox3互斥,且他們都有本身的顏色數量P。那麼選Vbox0較長邊做爲方向,將Vbox一、Vbox二、Vbox3進行該方向上進行排序,並按順序累加他們的P,當累加值大於等於Vbox0的P時,此時的索引位置就爲切分點,就此進行切分。把Vbox0切成新的兩個Vbox0一、Vbox02,Vbox一、Vbox二、Vbox3分別包含於Vbox0一、Vbox02之中,那麼,這是就選Vbox0一、Vbox02中p值較大的,繼續上面的才分過程。這就是中位切分的過程。那麼看看源碼:
final class ColorCutQuantizer { ... private List<Palette.Swatch> quantizePixels(int maxColors) { // Create the priority queue which is sorted by volume descending. This means we always // split the largest box in the queue final PriorityQueue<Vbox> pq = new PriorityQueue<>(maxColors, VBOX_COMPARATOR_VOLUME); // To start, offer a box which contains all of the colors pq.offer(new Vbox(0, mColors.length - 1)); // Now go through the boxes, splitting them until we have reached maxColors or there are no // more boxes to split splitBoxes(pq, maxColors); // Finally, return the average colors of the color boxes return generateAverageColors(pq); } /** * Iterate through the {@link java.util.Queue}, popping * {@link ColorCutQuantizer.Vbox} objects from the queue * and splitting them. Once split, the new box and the remaining box are offered back to the * queue. * * @param queue {@link java.util.PriorityQueue} to poll for boxes * @param maxSize Maximum amount of boxes to split */ @SuppressWarnings("NullAway") // mTimingLogger initialization and access guarded by LOG_TIMINGS. private void splitBoxes(final PriorityQueue<Vbox> queue, final int maxSize) { while (queue.size() < maxSize) { final Vbox vbox = queue.poll(); if (vbox != null && vbox.canSplit()) { // First split the box, and offer the result queue.offer(vbox.splitBox()); if (LOG_TIMINGS) { mTimingLogger.addSplit("Box split"); } // Then offer the box back queue.offer(vbox); } else { if (LOG_TIMINGS) { mTimingLogger.addSplit("All boxes split"); } // If we get here then there are no more boxes to split, so return return; } } } private List<Palette.Swatch> generateAverageColors(Collection<Vbox> vboxes) { ArrayList<Palette.Swatch> colors = new ArrayList<>(vboxes.size()); for (Vbox vbox : vboxes) { Palette.Swatch swatch = vbox.getAverageColor(); if (!shouldIgnoreColor(swatch)) { // As we're averaging a color box, we can still get colors which we do not want, so // we check again here colors.add(swatch); } } return colors; } ... }
先構造一個PriorityQueue優先隊列,並設定根據體積大小進行排序的規則,以下:
private static final Comparator<Vbox> VBOX_COMPARATOR_VOLUME = new Comparator<Vbox>() { @Override public int compare(Vbox lhs, Vbox rhs) { return rhs.getVolume() - lhs.getVolume(); } };
源碼中,mColors中的每一個顏色值和其對應的mHistogram中的數據組合起來都表明着一個概念上的Vbox,只是代碼中並無所有建立Vbox實例。而後根據每次都從隊列中取出最大Vbox,進行切分,切完再把切下來的Vbox放進隊列中,直到Vbox數量等於maxColors。
Palette的解析差很少就講到這裏。