Palette使用簡介與實現原理

Palette顧名思義就是一個調色板,它根據傳入的bitmap,提取出幾種主體顏色,這些顏色若是設置到背景或者字體上,就能使界面色彩元素豐富的同時也不失協調。java

使用簡介

它是一個建造者模式的實例化模式,用法很簡單,總的說來分三步:
1.經過builder添加參數:算法

Palette p = Palette.from(BitMap)//將圖片傳入調色板中
                    .addFilter(Filter)//添加一個過濾器,根據HSL過濾(能夠不設置,有默認值)
                    .maximumColorCount(20)//設置一個顏色種數的臨界值,有效的顏色塊數量小於這個值,直接用這些顏色做爲最終統計到的樣本。有效的顏色塊後面解釋(能夠不設置,有默認值)
                    .resizeBitmapSize(480)//bitmap放入調色板時,會先壓縮,提升計算速率,這裏就是設置壓縮後的尺寸。(能夠不設置,有默認值)
                    .clearFilters()//清除顏色過濾器(能夠不調用)
                    .generate();//第二步,建立Palette實例。

2.構建Palette實例。產生實例的方法generate有無參和有參數的兩個重載方法。數組

  • 無參的直接返回Palette,是同步方法。
  • 有參的返回的是一個異步任務,經過傳入的參數回調。由於統計圖片顏色是比較耗時的,顏色種類越多,耗時越長。

3.經過Palette獲取顏色。Palette取得的顏色並非一種,有可能有多個樣本。它提供了6種風格的顏色:app

p.getDarkMutedColor(defaultColor);
p.getDarkVibrantColor(defaultColor);
p.getLightMutedColor(defaultColor);
p.getLightVibrantColor(defaultColor);
p.getMutedColor(defaultColor);
p.getVibrantColor(defaultColor);~~~~

柔和三種,生動三種。但並不必定每種都採集到了合適的顏色。異步

實現原理

使用方法講完, 接下來是分析源碼。首先,咱們須要瞭解幾個概念:ide

  • RGB彩色空間:把顏色抽象到空間模型中,「Color Space」,彩色空間,又稱做色域。RGB彩色空間是Color Space的一種,將R、G、B三色做爲xyz軸,其顏色模型以下圖。在這個彩色空間裏能夠表示全部RGB的顏色,固然,這並不表明天然界的全部顏色。另外,RGB顏色也根據精度不一樣,有RGB88八、ARGB888八、RGB56五、RGB555等這些格式。
  • HSL:工業界的一種顏色標準,是經過對色相(H)、飽和度(S)、明度(L)三個顏色通道的變化以及它們相互之間的疊加來獲得各式各樣的顏色的,它和RGB是能夠互相轉換的。這裏就不提供轉化算法了。
  • VBox:java對象,表明一個RGB彩色空間,或者說是子彩色空間,下圖中的每一個方塊均可以是一個Vbox。Vbox的三邊就是顏色的red、blue、green三個份量,因此其體積表明的含義就是該彩色空間的色域大小。

色彩空間

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

  1. 建立一個顏色直方圖來統計圖片中的顏色,以下圖:
    顏色統計直方圖.png
    圖中橫座標表明的顏色爲RGB555,rgb份量分別只有5bit,因此顏色總數爲2的15次方,因此源碼中的mHistogram就是表明的這個直方圖的數組,數組的索引其實也表明的就是對應的顏色。將RGB888或ARGB8888轉換爲RGB555的目的是簡化了須要統計的顏色數量,提升效率,但損失的精度對結果影響很小。
  2. 將圖片中每一個像素的顏色轉換爲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;
     }
  3. 根據Palette中默認的filter或者經過addFilter()添加的filter過濾掉不須要統計的顏色。
  4. 只保留統計值>0的顏色,用新的直方圖保存。
  5. 經過中位切分法進一步量化顏色直至顏色種數小於maxColors。若是圖片顏色數量自己就少於maxColors,直接返回這些顏色對應的樣本就行。
  6. 經過獲得的樣本和配置的配色方案,獲得各類配色方案下的顏色。
中位切分法

下面先了解下中位切分算法,做爲一種量化顏色的算法,對彩色空間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的解析差很少就講到這裏。

相關文章
相關標籤/搜索