可以通過改變Android Framework界面元素的默認藍色來使應用更加獨特。
最簡單的方式就是給Action Bar加上一層自定義的背景,但是在我現在寫的一個APP中我希望可以更靈活一些(做到自適應),自適應顏色的最好例子就是iTunes了,它會從專輯中獲取配色方案,作用于彈出的曲目列表。
所以,我準備在Android上實現這個技術。
基本理論知識
在網上搜索一遍后,我發現了很多開源的實現方式,不過是用其他語言寫的。最好的版本是一個JavaScript庫,叫Color Thief,我從里面學到了很多實現這個技術需要的知識,正好是我需要的。
圖像量化
這里要做的第一步就是量化源圖像,通俗地說,就是減少圖像上使用的顏色種類。如果你喜歡動態的GIF,那么只能用8位的色板,所以每一幀最多可以使用256種顏色。
為此,我們就需要減少顏色使用,只使用一些主要的顏色,那我們就用默認的色板吧,再根據需要弄出一些其他的顏色。稍后將詳細介紹。
現在需要選擇量化算法了,Color Thief用了一個修改版的MCQ(Median Cut Quantization)算法,另稱作MMCQ(Modified Median Cut Quantization),如果想了解更多關于MMCQ的信息,可以來 這里 。其他的比較著名的量化技術還有NeuQuant和OctTree。
我還在《Principles of Digital Image Processing 》這本書上找到了一個JAVA的MCQ實現,托管在GitHub上。
這個MCQ算法有很多很棒的特性,所以我決定就用它了:
它很快。它比NeuQuant和OctTree還快,在移動設備上這點尤其重要;
它內部使用了統計直方圖,每種色塊都綁定了一個數值,之后排序的時候更方便。
雖然MCQ算法生成的圖像質量不是最好的,但是這里只是需要它生成的調色板,不用展示生成的圖像,所以,還不錯。
處理結果
以下就是處理后的結果,使用Color Thief的例子里的圖像。之前說過MCQ里面帶有統計直方圖,所以我們可以排列出每種顏色使用的頻率,它顯示了調色板排序后的列表。當然,這還是可以繼續改進。
這些結果和Color Thief生成的圖像有點不一樣:
我的版本選擇藍色作為主要的顏色;
Color Thief挑選了藍色,銀色和綠色作為主要顏色;
Color Thief沒有選到那些灰色的陰影。所以還需要改進。
接著上文講的,可以調用MedianCutQuantizer對象的getQuantizedColors()這個方法可以獲取調色板。我們可以以顏色使用的數量和比重來對這個集合進行降序顯示。很不幸的是結果表明大多數圖像用的顏色是黑色和白色(或相近的顏色),這顏色根本不能讓我們的應用顯得更獨特,所以我們要考慮到底選擇什么顏色了。
對于我自己的應用來說,我準備使用以下的調色方案:
第一位的主色是一種鮮艷的顏色;
第二位主色是區別一于第一位主色的另一種亮色;
第三位主色是和第一位和第二位主色對比強烈的顏色;
一種主要的字體顏色,和整體主色對比明顯,可讀性強;
第二種主要字體顏色就是白色或者黑色,取決于整體主色的亮度,可讀性強。
這篇文章主要講的也就是怎么選擇這些顏色。
主色
根據以上我的需求,我決定使用以下因素的平均值:
鮮艷度;
熱度(受歡迎程度)。
鮮艷度
這個其實也很簡單,首先要把RGB顏色模型轉化成HSV顏色模型,使用Android內置的方法可以做到。如果不明白HSV顏色模型可以看 這里。
簡單地說,這個圓柱形就代表了RGB顏色模型,通過三個坐標來表示顏色:Hue,Saturation和Value(明度)。
HSV顏色模型,來自 Wikipedia
我使用一個簡單的方式去計算鮮艷度,通過飽和度(saturation )和色度(value)。在人眼看來這兩個值越高,鮮艷度就越高。
public float[] getHsv() { float[] hsv = new float[3]; Color.RGBToHSV(r, g, b, hsv); return hsv; } public float calculateColorfulness() { float[] hsv = getHsv(); return hsv[1] * hsv[2]; }
計算的結果會在0.0到1.0的范圍內。
熱度
還記得之前說過每個顏色都有一個綁定的值嗎?這里可以使用這個值來決定一種顏色在調色板中的受歡迎程度。記住值得范圍是在0.0到1.0之間。
也就是說我們得到了如下的簡單的調色板:
| Color | Count |
----------------------
| White | 200 |
| Purple | 175 |
| Black | 150 |
| Red | 125 |
| Orange | 100 |
| Blue | 50 |
----------------------
| Total | 800 |
我們可以通過圖像中的這個比例來計算出顏色占有的比例,上圖中有800像素,以紫色為例,它的顏色比例為:175 / 800 = ~0.22。可是這個值很小,只能接近1而已。
反之我們可以選擇調色板中最受歡迎的顏色作為基準來計算這個比例。還是用上一個例子,白色是最受歡迎的顏色,所以紫色的比例就是:175 / 200 = 0.87。相對于顏色的受歡迎程度來說,這個更具代表性。
最終值
這里要使用這些值來結合成一個最終的值,這樣簡單合成沒有問題,但是之前說了黑白色是最受歡迎的顏色,考慮到這個,這里我們做一個權重,來決定一些屬性的重要程度,這種情況下我們提高了鮮艷度:
static float weightedAverage(float... values) { assert values.length % 2 == 0; float sum = 0; float sumWeight = 0; for (int i = 0; i = SECONDARY_MIN_DIFF_HUE_PRIMARY) { return candidate; } } // If we get here, just return the second weighted color return mWeightedPalette[1];
第三位主色
這種顏色和上面第二位主色很相似,但是這次就不找Hue值了,我們直接對比前兩種顏色就可以了。
// Contrast values are in the range 0-255. private static final int TERTIARY_MIN_CONTRAST_PRIMARY = 20; private static final int TERTIARY_MIN_CONTRAST_SECONDARY = 90; ... // Find the first color which has sufficient contrast from both the primary & secondary for (ColorNode color : mWeightedPalette) { if (ColorUtils.calculateContrast(color, primary) >= TERTIARY_MIN_CONTRAST_PRIMARY && ColorUtils.calculateContrast(color, secondary) >= TERTIARY_MIN_CONTRAST_SECONDARY) { return color.getRgb(); } } // We couldn't find a colour. In that case use the primary colour, modifying it's // brightness by 45% return ColorUtils.changeBrightness(secondary.getRgb(), 0.45f);
來看一下,calculateContrast()這個方法哪來的?這個我也想了很久,其實它來自這篇文章 color contrast。
最后我再把RGB顏色模型轉換成了YIQ顏色模型,僅僅攜帶了Y(亮度)值,之后你可以對比下兩種顏色的亮度值,看看在明度上有什么不一樣,再用臨界值來試試。
/** * @return difference in luma. Possible values are 0 (no difference) to * 255 (max difference). */ private static final int calculateContrast(int rgbColor1, int rgbColor2) { return Math.abs(calculateYiqLuma(rgbColor1) - calculateYiqLuma(rgbColor2)); } /** * @return luma value. Values are in the range 0-255. */ public static final int calculateYiqLuma(int color) { return (299 * Color.red(color) + 587 * Color.green(color) + 114 * Color.green(color)) / 1000; }
代碼
這代碼也許跑不起來,所以需要修改一下然后包含到你的APP中,這是僅僅是為了讓你知道怎么把它集成到APP中,所有重要的東西都在這里了,你只需要考慮怎么集成進你的APP就行了。加油吧。