上一次周例会上,有关专家介绍了一下android5.0的一些新特性。其中一个是主色提取,按照产品经理的意思,下一期需求上我们最好能加上这个特性。于是乎,我们就开始研究一下这个新特性了。这个是放在support包里面的新增接口,也就是说这个接口和android版本无关。看来源码也不会太大。:-)
先看一下google官方说明。https://developer.android.com/reference/android/support/v7/graphics/Palette.html从这个介绍页面,我们大致能够明白:这个新增接口比较简单。一个主要构造方法,传一个bitmap对象,剩下的就是get系列方法。另外,为了方便使用,google还特意新增加了一个异步的构造方法,类似imageloader。不过这里友情提醒一下,以下几个方法,返回值有可能是空,所以使用前要做一次参数有效性判断。
/** * Returns the most vibrant swatch in the palette. Might be null. */ public Swatch getVibrantSwatch() { return mVibrantSwatch; } /** * Returns a light and vibrant swatch from the palette. Might be null. */ public Swatch getLightVibrantSwatch() { return mLightVibrantSwatch; } /** * Returns a dark and vibrant swatch from the palette. Might be null. */ public Swatch getDarkVibrantSwatch() { return mDarkVibrantSwatch; } /** * Returns a muted swatch from the palette. Might be null. */ public Swatch getMutedSwatch() { return mMutedSwatch; } /** * Returns a muted and light swatch from the palette. Might be null. */ public Swatch getLightMutedSwatch() { return mLightMutedColor; } /** * Returns a muted and dark swatch from the palette. Might be null. */ public Swatch getDarkMutedSwatch() { return mDarkMutedSwatch; }
按理,这么好的接口,应该能够很快上手。可是在demo里发现,好多情况下,这个接口并没有取到我们想要的,也就是我们认为这附图片上最多的颜色。比如一张一眼看上去灰白占主要的图片,返回来的确是红色,或者黄色。从图片上取到的颜色,不再正常的理解范围之内。看来得研究一下源码了。
同这个特性开放的接口一样,这个特性的实现源码也不是太复杂。package android.support.v7.graphics下面总共只有四个文件,其中一个是public class。其余的三个类中,有两个class是能力层代码。具体如下:
Palette.java 对外接口类。提供一些方法的获取,以及Swatch的定义。
ColorUtils.java 业务强相关的工具类。里面都是一些静态方法。
ColorHistogram.java 实现了颜色提取算法的能力类。
ColorCutQuantizer.java 夹在ColorHistogram.java和Palette.java中间的一个封装类。不过这个类里面提供了一套颜色分裂算法。
这里我们先看一下ColorHistogram这个类是怎么实现主色提取的。构造方法里面传了一个像素点颜色值的数组。传入之后,对所有颜色值进行一次排序。在countDistinctColors方法里面给出了颜色的种类,在countFrequencies方法里面给出了每种颜色所占的比例/个数。这个算法很常见,第一次遇到的同学结合google的注释看起来也挺快的。
private static int countDistinctColors(final int[] pixels) { if (pixels.length < 2) { // If we have less than 2 pixels we can stop here return pixels.length; } // If we have at least 2 pixels, we have a minimum of 1 color... int colorCount = 1; int currentColor = pixels[0]; // Now iterate from the second pixel to the end, counting distinct colors for (int i = 1; i < pixels.length; i++) { // If we encounter a new color, increase the population if (pixels[i] != currentColor) { currentColor = pixels[i]; colorCount++; } } return colorCount; } private void countFrequencies(final int[] pixels) { if (pixels.length == 0) { return; } int currentColorIndex = 0; int currentColor = pixels[0]; mColors[currentColorIndex] = currentColor; mColorCounts[currentColorIndex] = 1; if (pixels.length == 1) { // If we only have one pixel, we can stop here return; } // Now iterate from the second pixel to the end, population distinct colors for (int i = 1; i < pixels.length; i++) { if (pixels[i] == currentColor) { // We've hit the same color as before, increase population mColorCounts[currentColorIndex]++; } else { // We've hit a new color, increase index currentColor = pixels[i]; currentColorIndex++; mColors[currentColorIndex] = currentColor; mColorCounts[currentColorIndex] = 1; } } }
到这里一切都是正常。该提取的颜色应该都提取出来了。那只能接着向上看。ColorHistogram构造的结果,做为参数直接丢给了ColorCutQuantizer的构造方法。在这个构造方法里面我们找到了原因:
// Now go through all of the colors and keep those which we do not want to ignore mColors = new int[rawColorCount]; int validColorCount = 0; for (int color : rawColors) { if (!shouldIgnoreColor(color)) { mColors[validColorCount++] = color; } }
google按照某个标准,进行了一次颜色筛选。也就是说我们直接用接口拿到的结果,是经过某个特定的业务筛选出来的。如果我们不知道这个业务,我们拿到的颜色当然就不是我们需要的颜色。将这个筛选逻辑去掉之后,拿到的主色,也就是我们需要的,图片里面最多的颜色。
另外从google的官方说明来看,这个接口的速度非常快。在下面这个方法里面,google将图片的大小统一压缩成一个较小的图片,这个应该是处理时间快的一个重要原因:
public static Palette generate(Bitmap bitmap, int numColors) { checkBitmapParam(bitmap); checkNumberColorsParam(numColors); // First we'll scale down the bitmap so it's shortest dimension is 100px final Bitmap scaledBitmap = scaleBitmapDown(bitmap);
个人觉得接口层还是提供能力比较好。如果这个接口层提供两套接口:1.单单的主色提取能力,2.某套颜色提取算法。这样APP在业务层里面用起来可能会更方便一些。