修改Bitmap的配色

Posted 疯狂小芋头

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了修改Bitmap的配色相关的知识,希望对你有一定的参考价值。

1. 修改Bitmap的配色

对于 Drawable 可以使用的系统提供的方法进行 Drawable 的配色设置

DrawableCompat.setTint(drawable, color);

但是对于Bitmap系统目前没有直接的设置方法。由于图片资源实际上也是一个Bitmap,Drawable的setTint()生效肯定是进行了某些处理,所以可以参考Bitmap对应的Drawable的处理实现对Bitmap的配色修改。

最终解决方案:

    //设置bitmap图片为主题配色
    @Nullable
    public Bitmap setBitmapTintColor(int colorTint, @Nullable Bitmap bitmap) 
        if (bitmap != null && colorTint != 0) 
            //创建一个与原图一样大小的空图片
            Bitmap newBmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());

            //创建一个新的bitmap在上面绘制出指定的颜色的配色,mode使用默认值
            ColorFilter filter = new PorterDuffColorFilter(colorTint, PorterDuff.Mode.SRC_IN);
            //创建画笔及设置过滤器
            Paint paint = new Paint();
            paint.setColorFilter(filter);
            //创建canvas并绘制出图片
            Canvas canvas = new Canvas(newBmp);
            canvas.drawBitmap(bitmap, 0, 0, paint);

            return newBmp;
         else 
            return null;
        
    

1.1. 了解如何设置图片配色

处理Bitmap的Drawable是BitmapDrawable,查看其setTint()的方法看是如何实现对Bitmap的配色修改。

    public void setTintList(ColorStateList tint) 
        final BitmapState state = mBitmapState;
        if (state.mTint != tint) 
            state.mTint = tint;
            mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
            invalidateSelf();
        
    

从代码中可以查看到在setTintList()的方法中,并没有直接对Bitmap进行立即修改,而是更新了mTintFilter的配色信息,后续通过调用invalidateSelf()重新绘制自身Drawable才完成对显示图片的配色修改,所以重点在于Drawable的绘制方法中

1.2. 了解如何修改bitmap配色并绘制

Drawable的绘制方法是通过draw()方法绘制出图形,所以重点在draw()方法的实现

    @Override
    public void draw(Canvas canvas) 
        //....
        //设置绘制的paint的tintFilter,在setTintList()方法中修改的信息在此处被使用了
        final boolean clearColorFilter;
        if (mTintFilter != null && paint.getColorFilter() == null) 
            paint.setColorFilter(mTintFilter);
            clearColorFilter = true;
         else 
            clearColorFilter = false;
        
        //...
        //绘制图片对象,使用了上述配置了tint的paint
            canvas.drawBitmap(bitmap, null, mDstRect, paint);

省略无关的代码,所以这里可以暂时推断修改Bitmap配色的方法关键在于paint.setColorFilter(),在这里修改了绘制的画笔的信息,所以导致了图片配色变更。那么接下来要解决tintFilter这个配色对象如何产生。

1.3. 了解如何生成配色过滤器

根据上面setTintList()的方法,可以知道在设置配色时,已经调用了方法updateTintFilter()更新了画笔的colorFilter,那么如何产生这个tintFilter关键就在这个方法。

    PorterDuffColorFilter updateTintFilter(@Nullable PorterDuffColorFilter tintFilter,
            @Nullable ColorStateList tint, @Nullable PorterDuff.Mode tintMode) 
        if (tint == null || tintMode == null) 
            return null;
        

        final int color = tint.getColorForState(getState(), Color.TRANSPARENT);
        if (tintFilter == null) 
            return new PorterDuffColorFilter(color, tintMode);
        

        tintFilter.setColor(color);
        tintFilter.setMode(tintMode);
        return tintFilter;
    

由于我们是直接对Bitmap进行修改,所以肯定是全新创建一个ColorFilter的,因此参考其中的filter如何创建即可。

这个方法中更新或创建一个tintFilter的话,ColorStateListPorterDuff.Mode都是必须的;ColorStateList我们可以理解,是配色信息,并且创建PorterDuffColorFilter时只需要用到颜色值即可。
但是目前我们并不知道要使用什么PorterDuff.Mode,我们通过Drawable的修改配色操作来反推mode的值。

1.4. 获取生成ColorFilter的参数

1.4.1.

修改Drawable的tint时,我们只是调用了DrawableCompat.setTint(drawable, color),这个方法并不要求提供mode,所以可以从这个方法查看mode的值如何产生。

    public void setTint(@ColorInt int tintColor) 
        setTintList(ColorStateList.valueOf(tintColor));
    

从以上的方法我们得到了ColorStateList如何从一个颜色值转换成ColorStateList的颜色状态类;

1.4.2.

查看setTintList()方法时,可以发现mode的参数使用的是BitmapDrawable本身状态中保存的mTintMode

updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode)

每当通过资源图片获取到一个BitmapDrawable后,我们并没有对drawable的tintMode进行特殊的修改,直接就使用了,所以mTintMode是默认BitmapDrawable中创建时初始化的值。我们查看该值初始化的值是什么即可。

1.4.3.

查看mTintMode值的初始值,发现是使用了BitmapState中的值,并且该值来自于父类ConstantState

    final static class BitmapState extends ConstantState 
        final Paint mPaint;

        // Values loaded during inflation.
        int[] mThemeAttrs = null;
        Bitmap mBitmap = null;
        ColorStateList mTint = null;
        Mode mTintMode = DEFAULT_TINT_MODE;
        //...
    

由于DEFAULT_TINT_MODE并不是在BitmapState中定义的,所以查看其父类确认该值的初始

public abstract class Drawable 
    //...
    static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
    //...

至此,我们可以已经可以确定生成一个colorFilter的参数值需要是什么了。

1.5. 确定修改Bitmap的步骤

  1. 加载一个Bitmap
  2. 使用指定配色创建一个ColorFilter
  3. 创建一个Paint用于绘制修改配色后的Bitmap
  4. 使用Canvas绘制出Bitmap

基于以上步骤可以得到以下的实现方式:

    //设置bitmap图片为主题配色
    public Bitmap setBitmapTintColor(int colorTint, @Nullable Bitmap bitmap) 
        if (bitmap != null && colorTint != 0) 
            //创建一个与原图一样大小的空图片
            Bitmap newBmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());

            //创建一个新的bitmap在上面绘制出指定的颜色的配色,mode使用默认值
            ColorFilter filter = new PorterDuffColorFilter(colorTint, PorterDuff.Mode.SRC_IN);
            //创建画笔及设置过滤器
            Paint paint = new Paint();
            paint.setColorFilter(filter);
            //创建canvas并绘制出图片
            Canvas canvas = new Canvas(newBmp);
            canvas.drawBitmap(bitmap, 0, 0, paint);

            return newBmp;
         else 
            return null;
        
    

1.6. 其它

实际上,修改图片配色就是为了将图片中的非透明像素设置成主题色,还有一种方式也可以解决,就是直接修改bitmap中的像素颜色值。大致实现方式如下:

//复制一个图片对象
Bitmap newBmp=bitmap.copy(bitmap.getConfig(),true);
//获取图片的宽高
int width=bitmap.getWidth();
int height=bitmap.getHeight();
for(int i=0;i<width;i++)
    for(int j=0;j<height;j++)
        //遍历原图的所有像素
        int srcColor=bitmap.getPixel(i,j);
        if(srcColor!=0)
            //非透明像素全部设置为指定配色
            newBitmap.setPixel(i,j,colorTint);
        
    

注意:实测该方案虽然可行,但是修改后得到的图片可能存在模糊和锯齿的结果,还是使用上述的解决方案更靠谱。

最后,更简单的方式是百度查一下相应的解决方案,会有相关的链接:
https://blog.csdn.net/nongminkouhao/article/details/104659195

以上所有步骤只是解释了如何从源码分析得到链接中的解决方案。感谢互联网的知识与分享。

以上是关于修改Bitmap的配色的主要内容,如果未能解决你的问题,请参考以下文章

bitmap缩放时抗锯齿

创建副本图片

Bitmap处理--BitmapShader、目标大小Bitmap、遮罩图层

android Canvas 裁剪clipPath的时候怎么过滤锯齿

Android Canvas 抗锯齿的两种方式

防止 TrueType 字体的抗锯齿(或子像素渲染)