简单使用

Palette.from(bmp).maximumColorCount(16).generate(new Palette.PaletteAsyncListener() {@Overridepublic void onGenerated(@Nullable Palette palette) {/*getDominantSwatch:      获取点数(population)最多的SwatchgetVibrantSwatch();     获取充满活力的色调getDarkVibrantSwatch(); 获取充满活力的黑getLightVibrantSwatch();获取充满活力的亮getMutedSwatch();       获取柔和的色调getDarkMutedSwatch();   获取柔和的黑getLightMutedSwatch();  获取柔和的亮*/}
});

知识补充

首先补充一些观看本文需要的知识

单词补充

swatch 样本

filter过滤器

quantizer量化

hist直方图

HSL / HSI 色域空间

其中H代表Hue 色调
S代表Saturation 饱和度
L / I 代表Itensity 强度

Web色

在该模型中,可以用相应的16制进制值00、33、66、99、CC和FF来表达三原色(RGB)中的每一种。这种基本的Web调色板将作为所有的Web浏览器和平台的标准,它包括了这些16进制值的组合结果。这就意味着,我们潜在的输出结果包括6种红色调、6种绿色调、6种蓝色调。666的结果就给出了216种特定的颜色,这些颜色就可以安全的应用于所有的Web中,而不需要担心颜色在不同应用程序之间的变化。

Median cut 中位切割

中位切割算法(Median cut)是Paul Heckbert于1979年提出来的算法。概念上很简单,却也是最知名、应用最为广泛的减色算法(Color quantization)。
假如有任意一张图片,想要降低影像中的颜色数目到256色。

  1. 将图片内的所有像素加入到同一个区域

  2. 对于所有的区域做以下的事
    计算此区域内所有像素的RGB三元素最大值与最小值的差
    选出相差最大的那个颜色(R或G或B)
    根据那个颜色去排序此区域内所有像素
    分割前一半与后一半的像素到二个不同的区域(这里就是"中位切割"名字的由来)

  3. 重复第二步直到你有256个区域

  4. 将每个区域内的像素平均起来,于是就得到了256色

开始分析

Palette框架,使用了建造者模式,传入Bitmap或者ArrayList<Swatch>生成建造者。

建造者内部有两个重要变量mTargetsmFilters

mTargets存放要取的颜色类别(各种亮度,主副色调)

mFilters存放过滤器,可以过滤不符合规则的rgb/hsl

通过调用建造者的generate,最终生成Palette对象

Builder的generate

public Palette generate() {List<Swatch> swatches;// 首先根据判断mBitmap和mSwatches是否为空的结果// 判断是传入的图片还是样本if (mBitmap != null) {// 对Bitmap降采样  默认112 * 112final Bitmap bitmap = scaleBitmapDown(mBitmap);// 采样的区域final Rect region = mRegion;// 量化所有颜色final ColorCutQuantizer quantizer = new ColorCutQuantizer(getPixelsFromBitmap(bitmap),mMaxColors,//默认16哦mFilters.isEmpty() ? null : mFilters.toArray(new Filter[mFilters.size()]));swatches = quantizer.getQuantizedColors();} else if (mSwatches != null) {swatches = mSwatches;} else {throw new AssertionError();}final Palette p = new Palette(swatches, mTargets);// 初始化p.generate();return p;
}

量化颜色时,传入了Bitmap的所有像素,Filter和划分的颜色数

首先看量化颜色类ColorCutQuantizer

量化实现类ColorCutQuantizer


private static final int QUANTIZE_WORD_WIDTH = 5;// 下文简称QWWColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) {mFilters = filters;// 解释一下这个直方图数组的大小,RGB中,每个元素占8位,这样整张图片占用的空间巨大,所以谷歌采用了压缩方法,即抹去8位的后3位,使其每位占5字节// 又因为,总共R,G,B三个元素,所以总颜色个数为2^5*3 即2*15final 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]++;}// 记录有区别的颜色数量int distinctColorCount = 0;for (int color = 0; color < hist.length; color++) {// shouldIgnoreColor会将565颜色重新变为888颜色,再转为hsl,共同传给Filterif (hist[color] > 0 && shouldIgnoreColor(color)) {// 如果当前颜色被忽略,记得更新直方图hist[color] = 0;}if (hist[color] > 0) {// 更新一下不同色调的个数(翻译成色调感觉不太准确,不过更方便理解)distinctColorCount++;}}// 根据记录的有区别的颜色,构造一个数组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;}}//至此,hist[i] 代表颜色i出现的个数colors[i] 代表第i个有区别的颜色hist的总大小,是2^15,即888颜色量化后的所有颜色个数colors的总大小,仅仅是过滤后,感兴趣的颜色的个数/// 如果感兴趣的颜色少于 最多想要取到的颜色个数if (distinctColorCount <= maxColors) {mQuantizedColors = new ArrayList<>();for (int color : colors) {// 将颜色转换回去(这时已经损失原始颜色精度了),再把出现的个数记录下来mQuantizedColors.add(new Palette.Swatch(approximateToRgb888(color), hist[color]));}} else {// 再量化,通过取平均模板进行量化mQuantizedColors = quantizePixels(maxColors);}}

quantizeFromRgb888函数的具体细节,就不多介绍了。简单来说就是类似将原始RGB变成WEB色。
quantizePixels采用的就是知识补充中提到的Median cut算法。

generate函数中,首先对图像进行了预处理,拿到了样本集合,再创建Palette对象,并调用了他的generate函数。
兜兜转转,终于完成了图像的预处理,进入了Palette。

Palette的generate

    private final Map<Target, Swatch> mSelectedSwatches; void generate() {// Google在这里玩了一手List遍历性能优化,代码风格和上文有些区别// 看来Palette也是一个多人团队开发的框架。for (int i = 0, count = mTargets.size(); i < count; i++) {final Target target = mTargets.get(i);target.normalizeWeights();mSelectedSwatches.put(target, generateScoredTarget(target));}// 因为这里的mUsedColors只是记录Palette内部处理时使用的样本,所以在交给用户使用时,应该清空已节省内存mUsedColors.clear();}

mTargets突然变得有意思起来。寻踪溯源一下

Target追踪

首先,mTargets最开始是在Builder构造函数中被添加的,默认添加了6个Target对象

        public Builder(@NonNull Bitmap bitmap) {mTargets.add(Target.LIGHT_VIBRANT);mTargets.add(Target.VIBRANT);mTargets.add(Target.DARK_VIBRANT);mTargets.add(Target.LIGHT_MUTED);mTargets.add(Target.MUTED);mTargets.add(Target.DARK_MUTED);}

而Target.LIGHT_VIBRANT / VIBRANT等 ,是Target类的static对象

public final class Target{// 只取两个当例子分析public static final Target LIGHT_VIBRANT;public static final Target VIBRANT;...static {LIGHT_VIBRANT = new Target();setDefaultLightLightnessValues(LIGHT_VIBRANT);setDefaultVibrantSaturationValues(LIGHT_VIBRANT);VIBRANT = new Target();setDefaultNormalLightnessValues(VIBRANT);setDefaultVibrantSaturationValues(VIBRANT);...}Target() {setTargetDefaultValues(mSaturationTargets);setTargetDefaultValues(mLightnessTargets);setDefaultWeights();}}

也就是说LIGHT_VIBRANT创建时,先setTargetDefaultValues,再setDefaultWeights,最后再调用setDefaultLightLightnessValues和setDefaultVibrantSaturationValues

    static final int INDEX_MIN = 0;static final int INDEX_TARGET = 1;static final int INDEX_MAX = 2;static final int INDEX_WEIGHT_SAT = 0;static final int INDEX_WEIGHT_LUMA = 1;static final int INDEX_WEIGHT_POP = 2;private static void setTargetDefaultValues(final float[] values) {values[INDEX_MIN] = 0f;values[INDEX_TARGET] = 0.5f;values[INDEX_MAX] = 1f;}    private void setDefaultWeights() {mWeights[INDEX_WEIGHT_SAT] = WEIGHT_SATURATION;mWeights[INDEX_WEIGHT_LUMA] = WEIGHT_LUMA;mWeights[INDEX_WEIGHT_POP] = WEIGHT_POPULATION;}private static void setDefaultLightLightnessValues(Target target) {target.mLightnessTargets[INDEX_MIN] = MIN_LIGHT_LUMA;target.mLightnessTargets[INDEX_TARGET] = TARGET_LIGHT_LUMA;}private static void setDefaultVibrantSaturationValues(Target target) {target.mSaturationTargets[INDEX_MIN] = MIN_VIBRANT_SATURATION;target.mSaturationTargets[INDEX_TARGET] = TARGET_VIBRANT_SATURATION;}

不同Target既有共性,又有彼此的差异。

共性表现在构造函数。构造函数中,干了两件事:

  1. 首先,先把三维饱和度和三维亮度,每个维度设置为0 0.5 1

三维权重,下标1,代表的是最小值,下标2,代表的是目标值,下标3,代表的是最大值
2. 再设置三维权重,每个维度为 0.24 0.52 0.24

三维权重,下标1,代表的是饱和度,下标2,代表的是亮度,下标3,代表的是数量

差异表现在static函数。
static函数中只干了一件事: 即根据不同的Target,设置他们各种维度为初始值

回到Palette的generate函数中

在遍历mTargets时,调用了Target的normalizeWeights函数。

    void normalizeWeights() {float sum = 0;for (int i = 0, z = mWeights.length; i < z; i++) {float weight = mWeights[i];if (weight > 0) {sum += weight;}}if (sum != 0) {for (int i = 0, z = mWeights.length; i < z; i++) {if (mWeights[i] > 0) {mWeights[i] /= sum;}}}}

normalizeWeights函数代码很简单,名字也很明显。实质上就是进行归一化处理,值均衡操作。

接下来调用了generateScoredTarget函数。分析来看,这个函数是关键,实现了根据Target取到Swatch

private Swatch generateScoredTarget(final Target target) {final Swatch maxScoreSwatch = getMaxScoredSwatchForTarget(target);if (maxScoreSwatch != null) {mUsedColors.append(maxScoreSwatch.getRgb(), true);}return maxScoreSwatch;}private Swatch getMaxScoredSwatchForTarget(final Target target) {float maxScore = 0;Swatch maxScoreSwatch = null;// 遍历所有的样本,拿到和当前Target最匹配的样本并返回for (int i = 0, count = mSwatches.size(); i < count; i++) {final Swatch swatch = mSwatches.get(i);// shouldBeScoredForTarget是比较样本的hsl和Target的hsl,判断是否在范围内。同时,判断是否使用(mUsedColors),也在这个函数中实现if (shouldBeScoredForTarget(swatch, target)) {// generateScore是根据样本和Target的hsl差值,并乘以权重生成的final float score = generateScore(swatch, target);if (maxScoreSwatch == null || score > maxScore) {maxScoreSwatch = swatch;maxScore = score;}}}return maxScoreSwatch;}

mUsedColors顾名思义,就是已经被 使用/取出 过的颜色

最后

经过以上的处理,Palette内的mSelectedSwatches已经记录了不同Target对应的样本。

当我们通过函数getDominantSwatch等获取颜色时,内部实际上是一层封装。

    public Swatch getDarkMutedSwatch() {return getSwatchForTarget(Target.DARK_MUTED);}public Swatch getSwatchForTarget(final Target target) {return mSelectedSwatches.get(target);}

结束

至此,Palette的分析之旅终于结束了。学习到了一些传统图像处理的方法。对hsl色域的使用有了新了想法。

安卓Palette原理分析相关推荐

  1. 安卓手机Recovery概述和原理分析

    安卓手机Recovery概述 1.Recovery是用户想要刷机的过程中经常会遇到的一个词.那么什么是Recovery?Recovery模式又是什么意思?手机怎么进入Recovery模式? 2.Rec ...

  2. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用 1...

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用 上一节我们描述了monkey的命令处理入口函数run是如何调用optionPro ...

  3. bmp/gif/jpg图象最底层原理分析

    bmp/gif/jpg图象最底层原理分析(1)---- JPG 转载自:http://www.cnblogs.com/glaivelee/archive/2011/10/10/2205151.html ...

  4. 一篇读懂:Android手机如何通过USB接口与外设通信(附原理分析及方案选型)

    更多技术干货,欢迎扫码关注博主微信公众号:HowieXue,共同探讨软件知识经验,关注就有海量学习资料免费领哦: 目录 0背景 1.手机USB接口通信特点 1.1 使用方便 1.2 通用性强 1.3 ...

  5. AIDL使用以及原理分析

    AIDL使用以及IPC原理分析(进程间通信) 概要 为了大家能够更好的理解android的进程间通信原理,以下将会从以下几个方面讲解跨进程通讯信: 1. 必要了解的概念 2. 为什么要使用aidl进程 ...

  6. Java 数据交换格式反射机制SpringIOC原理分析

    数据交换格式&反射机制&SpringIOC原理分析 什么是数据交换格式? 数据交换格式使用场景 JSON简单使用 什么是JSON? JSON格式的分类 常用JSON解析框架 使用fas ...

  7. Android 框架学习5:微信热修复框架 Tinker 从使用到 patch 加载、生成、合成原理分析

    这篇文章是基于内部分享的逐字稿内容整理的,现在比较喜欢写逐字稿,方便整理成文章. 文章目录 目录 Tinker 介绍 使用 TinkerApplicaition ``SampleApplicaitio ...

  8. VR技术原理分析,【VR原理入门理论篇】

    VR技术原理分析,[VR原理入门理论篇],学习研究VR技术必须要了解的理论知识. 目录 1. VR沉浸感和交互作用产生的原理: 2. 关于沉浸感和交互作用的定义 3. 如何生成符合VR要求的虚拟世界 ...

  9. 【黑科技】腾讯的 IOCanary 监控系统原理分析

    前言 公司的一款app最近在上架厂商的过程中,被对方指出了IO读写过于频繁,然后不给上架: 但是IO读写的操作非常零散,而且很多第三方框架内都会有写入操作 所以就变得非常难以监控和修改,有没有一种非常 ...

最新文章

  1. 图像处理与机器学习(验证码的识别)
  2. SAP MM 外部采购退货的ARM功能实在是鸡肋?
  3. oracle数据泵导入提示00972,oracle数据库使用expdp指定FLASHBACK_TIME遇到ORA-39150错误 | 信春哥,系统稳,闭眼上线不回滚!...
  4. 《服务外包概论》实验报告——版本管理与控制工具的综合应用
  5. linux 指令tftp传输文件_tftp命令_Linux tftp 命令用法详解:在本机和tftp服务器之间使用TFTP协议传输文件...
  6. USB接口的键盘描述符范例
  7. 在ODBC中应用DDX和RFX
  8. Linux 命令(131)—— usermod 命令
  9. 北京特9内环和外环的区别_2021年2月CFA北京机考考点在哪里?
  10. 【笔记】UML核心元素
  11. DotFun Silverlight 整站系统Beta版上线!
  12. 大使馆大师傅活动分工和非都市规划
  13. dhtml gantt所有配置_dhtmlxGantt
  14. 数学分析(1):集合相关公式的证明
  15. 网络安全 Windows用户密码破解 使用破解MD5值的在线网站和监听工具Cain
  16. 新年的开始——关于过去现在和未来
  17. gitlab小记(一)
  18. sobel算子 拉普拉斯算子以及散度与梯度的概念
  19. 马克维茨模型matlab求解,马克维茨投资组合模型的matlab计算
  20. 2019年,SEO关键词KPI考核指标有哪些?

热门文章

  1. 网络传真服务器位置,网络传真服务器_什么传真传真服务器?
  2. 一文讲懂页面置换算法,带例题详解
  3. 用 chrome + excel + VBA + XMLHTTP 爬视频网站 video 标签中的 blob:http m3u8 视频资源,ffmpeg 拼接资源
  4. 借助 GPU 和容器支持,在 Amazon Robomaker 中运行任何高保真模拟
  5. 统计表中百分比的表示方法
  6. 共振峰检测matlab,基于 LPC 系数的共振峰估计
  7. 为何计算机科学领域的女性不多
  8. hyperledger cello部署
  9. 哈工大c语言第四版pdf,哈工大C语言讲义 指针.pdf
  10. 使用node上传到腾讯云对象存储cos---转载