Android 上的调色板 —— Palette

Android 上有一个比较短小精悍的库 —— Palette,整个库只有PaletteTargetColorCutQuantizer三个文件, 作用是从图像中提取突出的颜色提供UI使用。

如何使用

导入依赖包

implementation 'com.android.support:palette-v7:27.0.2'

简单使用

//需要传入要提取的图片 bitmap
val builder = Palette.from(bitmap)
builder.generate(Palette.PaletteAsyncListener { palette ->
     /**
     * palette.getVibrantSwatch();       //获取到充满活力的色调
     * palette.getDarkVibrantSwatch();    //获取充满活力的黑
     * palette.getLightVibrantSwatch();   //获取充满活力的亮
     * palette.getMutedSwatch();          //获取柔和的色调
     * palette.getDarkMutedSwatch();     //获取柔和的黑
     * palette.getLightMutedSwatch();   //获取柔和的亮
     */
    //获取color值
    val swatch: Palette.Swatch? = palette.lightVibrantSwatch
    val color = swatch.rgb
    }
})

如上代码所示,提取图片上的颜色需要传入 bitmap,Palette 默认给了6种提取色调的种类。

源码解析

Palette有两种初始化方法,也就是同步和异步方法,使用 Builder构造器时默认使用的是异步方法。每种方法下都可以提供调色板大小参数来设置

// 最好在线程中使用
// 默认调色板大小.
private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
Palette p = Palette.generate(bitmap);
//设置调色板大小numcolor
Palette p = Palette.generate(bitmap, numcolor);

// 内部使用AsyncTask
Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
    @Override
    public void onGenerated(Palette palette) {
        // palette为生成的调色板
    }
});
// 设置调色板大小
Palette.generateAsync(bitmap, numcolor, new Palette.PaletteAsyncListener() {
    @Override
    public void onGenerated(Palette palette) {
        // palette为生成的调色板
    }
})

当我们传入 Bitmap 后会首先对 Bitmap 做处理

// First we'll scale down the bitmap if needed
final Bitmap bitmap = scaleBitmapDown(mBitmap);

/**
 * Scale the bitmap down as needed.
 */
private Bitmap scaleBitmapDown(final Bitmap bitmap) {
    double scaleRatio = -1;

    if (mResizeArea > 0) {
        final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
        if (bitmapArea > mResizeArea) {
            scaleRatio = Math.sqrt(mResizeArea / (double) bitmapArea);
        }
    } else if (mResizeMaxDimension > 0) {
        final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
        if (maxDimension > mResizeMaxDimension) {
            scaleRatio = mResizeMaxDimension / (double) maxDimension;
        }
    }

    if (scaleRatio <= 0) {
        // Scaling has been disabled or not needed so just return the Bitmap
        return bitmap;
    }

    return Bitmap.createScaledBitmap(bitmap,
            (int) Math.ceil(bitmap.getWidth() * scaleRatio),
            (int) Math.ceil(bitmap.getHeight() * scaleRatio),
            false);
}

这样做为了防止 Bitmap 太大而导致计算消耗大量资源,默认的ResizeArea大小是112*112。

处理完 Bitmap 后通过 getPixelsFromBitmap(bitmap) 方法获得图像上的像素数组,然后通过 ColorCutQuantizer 来处理图像上色值。
获取色值后,就可以将这些色值填入 Palette 中以便使用。

颜色量化算法

ColorCutQuantizer 是一个基于 中位切分法(Median cut) 的颜色量化器,主要作用就是从彩色图像中提取其中的主题颜色。

中位切分算法的原理很简单直接,将图像颜色看作是色彩空间中的长方体(VBox),从初始整个图像作为一个长方体开始,将RGB中最长的一边从颜色统计的中位数一切为二,使得到的两个长方体所包含的像素数量相同,重复上述步骤,直到最终切分得到长方体的数量等于主题颜色数量为止。


VBox.png

其中RGB最长的一边意思是,比如说在所有像素中Red颜色的分布范围是(10-50),Green的分布范围是(5-100),Blue的分布范围是(0-200)。那么此时就应该以Blue为基准,分成左右两堆,一堆的像素Blue值比中值小,另一堆像素Blue值比中值大。

但是有时候某些条件下VBOX里面的像素数量很少,比如实现过滤白色和黑色时候,这时候就不能以类似二分法来切割VBOX,需要使用优先级队列进行排序,刚开始时这一队列以VBox仅以VBox所包含的像素数作为优先级考量,当切分次数变多之后,将体积*包含像素数作为优先级。

除此之外,算法中最重要的部分是统计色彩分布直方图。我们需要将三维空间中的任意一点对应到一维坐标中的整数,这样才能以最快地速度定位这一颜色。如果采用全部的24位信息,那么我们用于保存直方图的数组长度至少要是224=16777216,既然是要提取颜色主题(或是颜色量化),我们可以将颜色由RGB各8位压缩至5位,这样数组长度只有215=32768:

量化压缩,举例:
24bit RGB888 -> 16bit RGB565 的转换

24bit RGB888
R7 R6 R5 R4 R3 R2 R1 R0 G7 G6 G5 G4 G3 G2 G1 G0 B7 B6 B5 B4 B3 B2 B1 B0

16bit RGB656
R7 R6 R5 R4 R3 G7 G6 G5 G4 G3 G2 B7 B6 B5 B4 B3

说明:在24bit上以8位为一组数据,在16bit上以5或者6为一组数据,
量化位数从8bit到5bit或6bit,取原8bit的高位,量化上做了压缩,却损失了精度。

量化补偿,举例:16bit RGB565 -> 24bit RGB888 的转换

16bit RGB656 R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0

24ibt RGB888 R4 R3 R2 R1 R0 0 0 0 G5 G4 G3 G2 G1 G0 0 0 B4 B3 B2 B1 B0 0 0 0

24ibt RGB888 R4 R3 R2 R1 R0 R2 R1 R0 G5 G4 G3 G2 G1 G0 G1 G0 B4 B3 B2 B1 B0 B2 B1 B0

说明:第二行的 24bit RGB888 数据为转换后,未进行补偿的数据,在精度上会有损失
第三行的 24bit RGB888 数据为经过量化补偿的数据,对低位做了量化补偿


参考资料:
wiki Median_cut
RGB565 与 RGB888的相互转换

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容