Android關於Color你所知道的和不知道的一切

張風捷特烈發表於2018-11-10

零、前言

1.做安卓的大多應該對顏色不太敏感,畢竟我們是敲程式碼的,顏色有設計師呢。
2.不過作為一名在大學被顏色薰(陶)過四年的人,對顏色多少還是挺親切的(雖然當時挺討厭的)
3.紀念也好,記錄也罷,為它寫篇總結也理所應當
4.如果你覺得並不需要了解關於顏色的知識,那你可以將本文當做一篇科普文(出去跟人家吹吹牛還是夠用的)


一、顏色知識科普:

這一切都要從光開始:
有個叫牛頓的人拿一塊三稜鏡將太陽光折射出了彩色產生色散現象:
----色散現象說明光在介質中的速度v=c/n(或折射率n)隨光的頻率f而變,從而:證明了光具有波動性
複製程式碼
光的色散 光的色散圖示
timg.jpg
光的色散.jpg
關於黑與白
問:如果把所有非黑的顏料混合,會得到什麼?----感覺一團糟,應該是黑色吧
問:如果把所有非白的光混合,會得到什麼?----感覺越來越亮,應該是白色吧
複製程式碼

為何光的疊加和顏料的疊加會產生相反的效果?
從一開始,這個問題就困擾著我,也將一直困擾這我...
如果說[肉眼所見到的光線,是由波長範圍很窄的電磁波產生的,不同波長的電磁波表現為不同的顏色],
那光的疊加也就是波的疊加,貌似有個[波動方程]吧...只能這樣說服自己:

光色疊加是減色模式:會越來約"淡"
顏料色疊加是加色模式:會越來約"濃"
複製程式碼

原色.png


色彩模式

1.RGB:裝置相關的顏色模型

安卓敲程式碼的多少都用過#ffff00表示黃色吧,這是RGB的一種表現形式
你也可以用"R:255,G:255,B:0"來表示黃色,其實兩者是一個意思,只不過是10進位制和16進位制的轉化

RGB.png

RGB的位數:

RGB還有位數的區別,也就是一個顏色佔幾位,一般是8位,
也就是用1個位元組表示一種顏色(一個位元組8位)
1個位元組(8位)每種顏色有0~255共256種中顏色,三色共表達:256*256*256=16,777,216種顏色
所以RGB並不能代表所有顏色,它只是一個子集,自然界的顏色是無窮的。人類只能模擬
16位是2個位元組代表一種顏色,每種顏色有0~65535共65536中顏色,  
三色共表達:65536*65536*65536=279213318656種顏色
複製程式碼
2.ARGB:裝置相關的顏色模型(加透明度)

即在RGB的基礎上新增新增透明度
顏色通道的概念(自己的理解,僅供參考):

大學那會學ps,動不動紅色通道,Alpha通道的,搞得雲裡霧裡,現在想想  
拿ARGB_8888(八位)來說,就相當有四道牆,每道牆上有256扇門分別標上0~255數字  
第一道牆叫Alpha(透明度)牆,第二道牆叫R(紅)牆,第二道牆叫G(綠)牆,第二道牆叫B(藍)牆  
現在你要從這四道牆的門走到終點,每次進門拿著門牌號,當你走到終點時,門牌號加起來就是顏色
那門,就是通道,如果進紅色的0門(俗稱:紅色通道關閉),從表現上來看最終顏色不帶紅色,如下下圖
複製程式碼

通道.png

不開紅色通道.png

為什麼程式設計裡用int作為顏色?

眾所周知:一個int佔4個位元組,也就是4*8個位,剛好用來盛放ARGB_8888。
複製程式碼

int表示顏色.png

3.CMYK:C(青)M(品紅)Y(黃)K(黑色)
作為程式設計師對CMYK瞭解估計不多,畢竟都在螢幕上,是ARGB的的天下
對於列印使用CMYK,符號是%,以不同顏色的百分比調色,理論上只應該有CMY就行了  
但實際黑色(K)的要比其他三色更容易生產,用三張一百塊合成為一張十塊錢估計沒人會這麼做
至於為什麼叫K...也許是RGB先出來的,然後不能叫B,只能叫K了
複製程式碼
4.HSV:

看到值就能想到大概的顏色

顏色有三個維度屬性:色相、明度和飽和度  

HSV模型對應於:
圓柱座標系中的一個圓錐形子集,圓錐的頂面對應於V=1。
它包含RGB模型中的R=1,G=1,B=1三個面,所代表的顏色較亮。
色彩H由繞V軸的旋轉角給定。紅色對應於角度0°,綠色對應於角度120°,藍色對應於角度240°。
在HSV顏色模型中,每一種顏色和它的補色相差180°。飽和度S取值從0到1,所以圓錐頂面的半徑為1。
複製程式碼

hsv.png

5.看一下黃色的幾種表達方式:

黃色.png

RGB:R:255 G:255 B:0        #ffff00
CMYK:C:10% M:0 Y:83% K:0
HSV:H:60° S:100% V:100%
複製程式碼

好了,科普結束,下面進入正題


一、Android中的Color

顏色使用場景:
1.基本使用:背景、陰影、文字顏色
2.基於Color建立的Bitmap以及疊合模式:Xfermode
3.paint中的著色、顏色過濾器 4.ColorMatrix的使用

1.常量:

Color中的預設色常量.png

2.建構函式

可見只有無引數構造可以用

Color建構函式.png

3.常用方法:
int blue = Color.BLUE;//第一種獲取藍色方法
blue = Color.parseColor("#0000FF");//第二種獲取藍色方法
blue = Color.rgb(0, 0, 255);//第三種獲取藍色方法
blue = Color.argb(255, 0, 0, 255);//第四種獲取藍色方法
blue = Color.HSVToColor(new float[]{240.0f, 1.0f, 1.0f});//第五種獲取藍色方法
blue = 0xff0000FF;//第六種獲取藍色方法
//(吐槽:怎麼有種孔乙己說茴香豆的茴字有多少種寫法一樣...,看哪個順手就用哪個吧)

float[] hsv = {0, 0, 0};//hsv陣列
Color.RGBToHSV(0, 0, 255, hsv);//將RGB轉為hsv
Log.e(TAG, "onDraw: " + hsv[0]+","+hsv[1]+","+hsv[2]);
//onDraw: 240.0,1.01.0
複製程式碼

其實Color的本身並沒有太多的知識點,畢竟就是一個int而已,難點在於顏色的拼合與變換

二、Android點陣圖封裝類:Bitmap

什麼是點陣圖,前面講過顏色是按位儲存的,ARGB_8888每種顏色佔8位
相信大家都知道一張jpg或png放大後會是一個個小格子,稱為一個畫素(px),而且一個小格子是一種顏色
也就是一張jpg或png圖片就是很多顏色的合集,而這些合集資訊都被封裝到了Bitmap類中
你可以使用Bitmap獲取任意畫素點,並修改它,對與某畫素點而言,顏色資訊是其主要的部分

畫素.png

1.重新認識Bitmap

我們一般使用Bitmap是都是用BitmapFactory來decode資源,所以並未設計太多Bitmap的操作,以致認為Bitmap=圖片
Bitmap實際是一個封裝圖片畫素資訊的類,它能顯示出來是因為View及手機的硬體

1).建立一個Bitmap:

注意區別bitmapCanvas和View中OnDraw中Canvas的區別:
這裡:bitmapCanvas是負責在點陣圖(Bitmap)上繪製,讓點陣圖記錄影素點位資訊的
OnDraw中Canvas是用來在View上繪製,顯示在螢幕上的。

打個不恰當的比方:
你是bitmapCanvas,負責畫一張圖(Bitmap),你畫完後不能直接交給印刷人員(View)去印
需要交給審稿員(OnDraw中canvas),經過他允許才能給印刷人員

/**
 * 建立一個Bitmap
 *
 * @param color 背景色
 * @return bitmap
 */
private Bitmap createBitmap(int color) {
    //建立一個ARGB_8888,寬高200的bitmap
    Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
    //使用Bitmap建立一個canvas畫板,畫板上的一切都會保留在bitmap上
    Canvas bitmapCanvas = new Canvas(bitmap);
    //接下來就是在畫板上操作
    Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
    p.setColor(color);
    Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
    bitmapCanvas.drawRect(rect, p);
    p.setColor(Color.GRAY);
    p.setStrokeWidth(3);
    bitmapCanvas.drawLine(0, 0, 200, 200, p);
    bitmapCanvas.drawLine(200, 0, 0, 200, p);
    return bitmap;
}
複製程式碼

OnDraw中使用Bitmap,使用Bitmap,使用Bitmap...

//審稿人統一,印刷到View上
canvas.drawBitmap(mBitmap, 100, 100, mMainPaint);
複製程式碼

繪製bitmap.png


三、Xfermode:圖片疊合時的處理方式

Xfermode.png

終於寫到這裡了,總算與Xfermode相遇了,最喜歡分析很多的情況,這裡有18種模式,想想都激動...。
做開發的,我們應該知道src和dst吧src是源,dst是目標,在react裡就有src的原始檔,和dest的輸出檔案
圖片疊合顧名思義,必須有兩個圖片才行,這裡原圖src用藍色正方形,目標dst用綠色圓形

1.src和dst圖片

源與目標.png

    /**
     * 建立源圖片
     *
     * @return bitmap
     */
    private Bitmap createSrcBitmap() {
        //建立一個ARGB_8888,寬高200的bitmap
        Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
        //使用Bitmap建立一個canvas畫板,畫板上的一切都會保留在bitmap上
        Canvas bitmapCanvas = new Canvas(bitmap);
        //接下來就是在畫板上操作
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(0x882045F3);
        Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        bitmapCanvas.drawRect(rect, p);
        return bitmap;
    }

    /**
     * 建立目標
     *
     * @return bitmap
     */
    private Bitmap createDstBitmap() {
        //建立一個ARGB_8888,寬高200的bitmap
        Bitmap bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
        //使用Bitmap建立一個canvas畫板,畫板上的一切都會保留在bitmap上
        Canvas bitmapCanvas = new Canvas(bitmap);
        //接下來就是在畫板上操作
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(0xff43F41D);
        bitmapCanvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, bitmap.getHeight() / 2,p);
        return bitmap;
    }
複製程式碼
2.疊合模式18種:android.graphics.PorterDuff.Mode

別怕,別怕,一幅圖展示一下:

    public enum Mode {
        CLEAR       (0),
        SRC         (1),
        DST         (2),
        SRC_OVER    (3),
        DST_OVER    (4),
        SRC_IN      (5),
        DST_IN      (6),
        SRC_OUT     (7),
        DST_OUT     (8),
        SRC_ATOP    (9),
        DST_ATOP    (10),
        XOR         (11),
        DARKEN      (16),
        LIGHTEN     (17),
        MULTIPLY    (13),
        SCREEN      (14),
        ADD         (12),
        OVERLAY     (15);

        Mode(int nativeInt) {
            this.nativeInt = nativeInt;
        }
        public final int nativeInt;
    }
複製程式碼
3.如何優雅地繪製下面一幅圖:

注意:測試了一下,開不開硬體加速對這東西有影響,下面在無有硬體加速:android:hardwareAccelerated="false"
mMainPaint.setXfermode(XXX);放置的順序也很重要,在下面的是疊合的源
網上有一組圖,不過沒有透明度,我對源(藍色)加了88的透明度,顯示的更清楚些
注意:看正方形框裡的內容,看正方形框裡的內容,看正方形框裡的內容!因為它是被疊合的物件

Screenshot_2018-11-10-11-17-14-46.png

    private void init() {
        mMainPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mMainPaint.setStyle(Paint.Style.FILL);
        mMainPaint.setStrokeCap(Paint.Cap.ROUND);

        src = createSrcBitmap();
        dst = createDstBitmap();

        //背景圖層的筆
        mLayerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mLayerPaint.setStyle(Paint.Style.FILL);
        mLayerPaint.setFilterBitmap(false);
        
        //文字的筆
        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.setTextSize(45);
        Typeface typeface = Typeface.create(Typeface.MONOSPACE, Typeface.BOLD);
        mTextPaint.setTypeface(typeface);
        mTextPaint.setColor(0xffF98D1F);

        //虛線畫筆
        mDashPaint = new Paint();
        mDashPaint.setStrokeWidth(3);
        mDashPaint.setColor(Color.RED);
        mDashPaint.setStyle(Paint.Style.STROKE);
        //設定虛線效果new float[]{可見長度, 不可見長度},偏移值
        mDashPaint.setPathEffect(new DashPathEffect(new float[]{10, 5}, 0));

        mModes = new PorterDuffXfermode[]{
                new PorterDuffXfermode(PorterDuff.Mode.CLEAR),//0
                new PorterDuffXfermode(PorterDuff.Mode.SRC),//1
                new PorterDuffXfermode(PorterDuff.Mode.DST),//2
                new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),//3
                new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),//4
                new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),//5
                new PorterDuffXfermode(PorterDuff.Mode.DST_IN),//6
                new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),//7
                new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),//8
                new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),//9
                new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),//10
                new PorterDuffXfermode(PorterDuff.Mode.XOR),//11
                new PorterDuffXfermode(PorterDuff.Mode.DARKEN),//12
                new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),//13
                new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),//14
                new PorterDuffXfermode(PorterDuff.Mode.SCREEN),//15
                new PorterDuffXfermode(PorterDuff.Mode.ADD),//16
                new PorterDuffXfermode(PorterDuff.Mode.OVERLAY),//17
        };

        mModeText = new String[]{"CLEAR", "SRC", "DST", "SRC_OVER", "DST_OVER", "SRC_IN",
                "DST_IN", "SRC_OUT", "DST_OUT", "SRC_ATOP", "DST_ATOP", "XOR", "DARKEN",
                "LIGHTEN", "MULTIPLY", "SCREEN", "ADD", "OVERLAY"
        };
    }
複製程式碼
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //建立一個圖層,在圖層上演示圖形混合後的效果
        int sc = 0;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            sc = canvas.saveLayer(new RectF(0, 0, 2500, 2500), mLayerPaint);
        }
        for (int i = 0; i < 18; i++) {
            int line = i % 6;
            int row = i / 6;

            canvas.drawBitmap(dst, 350 * line, row * 350, mMainPaint);//目標圖象
            mMainPaint.setXfermode(mModes[i]);//設定對源的疊合模式
            canvas.drawBitmap(src, 100 + 350 * line, 100 + row * 350, mMainPaint);
            //輔助資訊
            canvas.drawText(mModeText[i],100 + 350 * line, 300 + row * 350,mTextPaint);
            canvas.drawCircle(100 + 350 * line, 100 + row * 350, 100, mDashPaint);
            canvas.drawRect(100 + 350 * line, 100 + row * 350, 100 + 200 + 350 * line, 100 + 200 + row * 350, mDashPaint);
        }
        canvas.restoreToCount(sc);
    }
複製程式碼

四、著色器:Shader(本節在Paint篇也有,為保全Color篇的完整性,這裡cv了一下)

一個很簡單的類,有5個子類:

Shader.png

1.線性漸變:
1).new LinearGradient(漸變起點x,y,漸變終點x,y,漸變色1,漸變色2,漸變模式)

漸變模式:Shader.TileMode.[MIRROR|CLAMP|REPEAT] (圖中很形象,就不解釋了)

        int colorStart = Color.parseColor("#84F125");
        int colorEnd = Color.parseColor("#5825F1");
        canvas.save();
        canvas.translate(mCoo.x, mCoo.y);
        mRedPaint.setStyle(Paint.Style.FILL);
        mRedPaint.setShader(
                new LinearGradient(
                        -200, 0, 200, 0,
                        colorStart, colorEnd,
                        Shader.TileMode.MIRROR
                ));
        canvas.drawRect(-400,-200,400,-100,mRedPaint);

        canvas.translate(0, 150);
        mRedPaint.setShader(
                new LinearGradient(
                        -100, 0, 100, 0,
                        colorStart, colorEnd,
                        Shader.TileMode.CLAMP
                ));
        canvas.drawRect(-400,-200,400,-100,mRedPaint);

        canvas.translate(0, 150);
        mRedPaint.setShader(
                new LinearGradient(
                        -100, 0, 100, 0,
                        colorStart, colorEnd,
                        Shader.TileMode.REPEAT
                ));
        canvas.drawRect(-400,-200,400,-100,mRedPaint);
複製程式碼

線性漸變.png


2).多色多點漸變:LinearGradient(漸變起點x,y,漸變終點x,y,顏色陣列,位置百分點陣列0~1,漸變模式)

多色漸變.png

int[] colors = new int[]{
        Color.parseColor("#F60C0C"),//紅
        Color.parseColor("#F3B913"),//橙
        Color.parseColor("#E7F716"),//黃
        Color.parseColor("#3DF30B"),//綠
        Color.parseColor("#0DF6EF"),//青
        Color.parseColor("#0829FB"),//藍
        Color.parseColor("#B709F4"),//紫
};
float[] pos = new float[]{
        1.f / 7, 2.f / 7, 3.f / 7, 4.f / 7, 5.f / 7, 6.f / 7, 1
};

canvas.translate(0, 150);
mRedPaint.setShader(
        new LinearGradient(
                -300, 0, 300, 0,
                colors, pos,
                Shader.TileMode.CLAMP

        ));
canvas.drawRect(-400, -200, 400, -100, mRedPaint);
複製程式碼

2.徑向漸變:RadialGradient
1).兩色漸變:RadialGradient(漸變中心,漸變半徑,顏色1,顏色2,漸變模式)
canvas.translate(mCoo.x, mCoo.y);
int colorStart = Color.parseColor("#84F125");
int colorEnd = Color.parseColor("#5825F1");
mRedPaint.setStyle(Paint.Style.FILL);
mRedPaint.setShader(
        new RadialGradient(
               0,0,50,
                colorStart, colorEnd,
                Shader.TileMode.MIRROR

        ));

canvas.drawCircle(0, 0, 150, mRedPaint);

canvas.translate(350, 0);
mRedPaint.setShader(
        new RadialGradient(
                0,0,50,
                colorStart, colorEnd,
                Shader.TileMode.CLAMP

        ));
canvas.drawCircle(0, 0, 150, mRedPaint);


canvas.translate(350, 0);
mRedPaint.setShader(
        new RadialGradient(
                0,0,50,
                colorStart, colorEnd,
                Shader.TileMode.REPEAT

        ));
canvas.drawCircle(0, 0, 150, mRedPaint);
複製程式碼

徑像漸變.png


2).多色多點徑向漸變:

RadialGradient(漸變中心,漸變半徑,漸變模式,顏色陣列,位置百分點陣列0~1,漸變模式)

多色徑向漸變.png

int[] colors = new int[]{
        Color.parseColor("#F60C0C"),//紅
        Color.parseColor("#F3B913"),//橙
        Color.parseColor("#E7F716"),//黃
        Color.parseColor("#3DF30B"),//綠
        Color.parseColor("#0DF6EF"),//青
        Color.parseColor("#0829FB"),//藍
        Color.parseColor("#B709F4"),//紫
};
float[] pos = new float[]{
        1.f / 7, 2.f / 7, 3.f / 7, 4.f / 7, 5.f / 7, 6.f / 7, 1
};
mRedPaint.setStyle(Paint.Style.FILL);
mRedPaint.setShader(
        new RadialGradient(
                0, 0, 200,
                colors, pos,
                Shader.TileMode.CLAMP
        ));
canvas.drawCircle(0, 0, 250, mRedPaint);
複製程式碼
3.掃描漸變:SweepGradient

這個要比上面的簡單一點,沒有漸變的模式 雙色掃描漸變:SweepGradient(中心點x,y,顏色1,顏色2) 多色掃描漸變:SweepGradient(中心點x,y,顏色陣列,位置百分點陣列0~1)

掃描漸變.png

int colorStart = Color.parseColor("#84F125");
int colorEnd = Color.parseColor("#5825F1");
mRedPaint.setStyle(Paint.Style.FILL);
mRedPaint.setShader(
        new SweepGradient(0, 0, colorStart, colorEnd));
canvas.drawCircle(0, 0, 150, mRedPaint);

canvas.translate(400, 0);
int[] colors = new int[]{
        Color.parseColor("#F60C0C"),//紅
        Color.parseColor("#F3B913"),//橙
        Color.parseColor("#E7F716"),//黃
        Color.parseColor("#3DF30B"),//綠
        Color.parseColor("#0DF6EF"),//青
        Color.parseColor("#0829FB"),//藍
        Color.parseColor("#B709F4"),//紫
};
float[] pos = new float[]{
        1.f / 7, 2.f / 7, 3.f / 7, 4.f / 7, 5.f / 7, 6.f / 7, 1
};
mRedPaint.setShader(
        new SweepGradient(0, 0, colors, pos));
canvas.drawCircle(0, 0, 150, mRedPaint);
複製程式碼
4.圖片著色器:BitmapShader(圖片,著色模式x,著色模式y)

用圖片的所有畫素點作為畫筆的顏色

1).文字的圖片底色:
//載入圖片,生成圖片著色器
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.menu_bg);
BitmapShader bs = new BitmapShader(bitmap, Shader.TileMode.CLAMP,Shader.TileMode.CLAMP);
mRedPaint.setShader(bs);
mRedPaint.setTextSize(150);
mRedPaint.setStrokeWidth(10);
mRedPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawText("張風捷特烈", 0, 500, mRedPaint);
複製程式碼

圖片著色.png

2)路徑+圖片著色器實現裁剪圖片:路徑Path相關知識見上一篇:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.menu_bg);
BitmapShader bs = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mRedPaint.setShader(bs);

mRedPaint.setStyle(Paint.Style.FILL);
Path path = CommonPath.nStarPath(8, 500, 250);
canvas.drawPath(path, mRedPaint);
複製程式碼

使用路徑裁剪圖片.png

還有一個ComposeShader比較複雜,以後有需求會專門寫一篇


七、顏色過濾器:(Paint篇有,但本篇更加深入)

ColorFilter只有三個子類

顏色過濾器.png

1.LightingColorFilter(顏色1,顏色2):

LightingColorFilter測試.png

        Bitmap mainBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.menu_bg);
        mRedPaint.setStyle(Paint.Style.FILL);
        mRedPaint.setColorFilter(new LightingColorFilter(
                Color.parseColor("#F00000"),//紅
                Color.parseColor("#0000ff")//藍
        ));
        canvas.drawBitmap(mainBitmap, 0, 0, mRedPaint);

        canvas.translate(350, 0);
        mRedPaint.setColorFilter(new LightingColorFilter(
                Color.parseColor("#FF0000"),//紅
                Color.parseColor("#00ff00")//綠
        ));
        canvas.drawBitmap(mainBitmap, 0, 0, mRedPaint);

        canvas.translate(350, 0);
        mRedPaint.setColorFilter(new LightingColorFilter(
                Color.parseColor("#FF0000"),//紅
                Color.parseColor("#000000")//黑
        ));
        canvas.drawBitmap(mainBitmap, 0, 0, mRedPaint);
        canvas.restore();
複製程式碼

下面分析一下紅藍配的結果:開啟LightingColorFilter原始碼:

    /**
     * Create a colorfilter that multiplies the RGB channels by one color,
     * and then adds a second color. The alpha components of the mul and add
     * arguments are ignored.
建立一個顏色過濾器:用mul顏色乘以RGB通道的顏色,再加上add顏色,mul和add的透明度將被忽略
     */
    public LightingColorFilter(@ColorInt int mul, @ColorInt int add) {
        mMul = mul;
        mAdd = add;
    }
    
//看了沒什麼感覺,又是native的方法,往上一看,有註釋
 * Given a source color RGB, the resulting R'G'B' color is computed thusly:
 * R' = R * colorMultiply.R + colorAdd.R  
 * G' = G * colorMultiply.G + colorAdd.G
 * B' = B * colorMultiply.B + colorAdd.B
複製程式碼

這下明白了,就是顏色變換嘛----草稿紙準備好,要演算了:
注意:當相乘數大於255時,便會溢位,相當於8位容不下那麼多數,後面再進來,前面的就被推出來了
這裡為了區別,特意用#F00000來測試,結果有一點點偏差,畢竟兩次選點的點位可能有偏差

顏色運算.png

活了這麼大,第一次對顏色進行乘法和加法,對於一張圖片,加上綠色就是對每個畫素點進行這樣的運算

2.PorterDuffColorFilter(顏色,模式--PorterDuff.Mode):

PorterDuff.Mode是不是很熟悉,看上面的疊加模式吧

PorterDuffColorFilter測試.png

Bitmap mainBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.menu_bg);
mRedPaint.setStyle(Paint.Style.FILL);

mRedPaint.setColorFilter(new PorterDuffColorFilter(
        Color.parseColor("#0000ff"), PorterDuff.Mode.DARKEN));
canvas.drawBitmap(mainBitmap, 0, 0, mRedPaint);

canvas.translate(350, 0);
mRedPaint.setColorFilter(new PorterDuffColorFilter(
        Color.parseColor("#0000ff"),PorterDuff.Mode.LIGHTEN
));
canvas.drawBitmap(mainBitmap, 0, 0, mRedPaint);

canvas.translate(350, 0);
mRedPaint.setColorFilter(new PorterDuffColorFilter(
        Color.parseColor("#0000ff"),PorterDuff.Mode.SCREEN
));
canvas.drawBitmap(mainBitmap, 0, 0, mRedPaint);

canvas.translate(350, 0);
mRedPaint.setColorFilter(new PorterDuffColorFilter(
        Color.parseColor("#0000ff"),PorterDuff.Mode.OVERLAY
));
canvas.drawBitmap(mainBitmap, 0, 0, mRedPaint);
複製程式碼
3.ColorMatrixColorFilter(顏色變換矩陣或20個float數)

本文的重中之重便是ColorMatrix:

它是有一個5*4的矩陣對某個顏色進行運算,是不是有種眾星捧月的但覺,沒錯,20個數,是不是很開心

顏色矩陣.png


1.關閉RGB顏色通道(變為黑色)

顏色ARBG佔了int的四個位元組,所以不可能是負數,至於如何處理負數,要看ColorMatrix的處理
測試了一下,應該是0,ARGB都沒了

斜對角-1.jpg

test-1.png

設為1後,結果[-R,-G,-B,A],黑色,符合預期:

test1.png


2.關閉RGB顏色通道(變為黑色),後偏移紅色255

由於只有G、B不通,所以顯示是不同的紅色

紅色.jpg

只過濾出紅色.png


3.關閉RGB顏色通道(變為黑色),後偏移藍色255
-1,0,0,0,0
0,-1,0,0,0
0,0,-1,0,255
0,0,0,1,0
複製程式碼

只過濾出藍色.png


4.關閉RGB顏色通道(變為黑色),後偏移三色255
-1,0,0,0,255
0,-1,0,0,255
0,0,-1,0,255
0,0,0,1,0
複製程式碼

反色.png


5.調節亮度:增加RGB的偏移顏色(-255~255)

亮度調節

亮度調節.png


6.灰度
//只要把RGB三通道的色彩資訊設定成一樣:即:R=G=B,
//為了保證影像亮度不變,同一個通道中的R+G+B=1
0.3086, 0.6094, 0.0820, 0, 0
0.3086, 0.6094, 0.0820, 0, 0
0.3086, 0.6094, 0.0820, 0, 0
0    ,  0    ,  0    ,  1, 0
複製程式碼

灰度.png


7.飽和度:(這裡我亂調的,可參考色彩方面的書)

飽和度測試.png

(R-1)*X + 1, G*X    ,       B*X    ,        0, 0,
R*X   ,     (G-1)*X + 1,    B*X    ,        0, 0,
R*X   ,     G*X      ,      (B-1)*X + 1,    0, 0,
0     ,     0        ,      0        ,      1, 0 


R=0.3086,G=0.6094,B=0.0820
複製程式碼
    /**
     * 飽和度調節
     * @param R 紅色保留比
     * @param G 綠色保留比
     * @param B 藍色保留比
     * @param X 值越小越飽和----0為原圖
     * @return
     */
    private float[] colorM(float R, float G, float B, float X) {
      float[] array=  new float[]{
                (R - 1) * X + 1, G * X, B * X, 0, 0,
                R * X, (G - 1) * X + 1, B * X, 0, 0,
                R * X, G * X, (B - 1) * X + 1, 0, 0,
                0, 0, 0, 1, 0

        };
        return array;
    }

複製程式碼

8.對比度:

對比度測試.png

X,0,0,0,128*(1-X)       R       X*R+128*(1-X)
0,X,0,0,128*(1-X)   *   G       G*R+128*(1-X)
0,0,X,0,128*(1-X)       B  =    B*R+128*(1-X)
0,0,0,1,0               A       A
                        1
複製程式碼
private float[] colorM(float X) {
    float[] array = new float[]{
            X, 0, 0, 0, 128 * (1 - X),
            0, X, 0, 0, 128 * (1 - X),
            0, 0, X, 0, 128 * (1 - X),
            0, 0, 0, 1, 0
    };
    return array;
}
複製程式碼

安卓自帶的圖片處理API

public void setSaturation(float sat) {
    reset();
    float[] m = mArray;

    final float invSat = 1 - sat;
    final float R = 0.213f * invSat;
    final float G = 0.715f * invSat;
    final float B = 0.072f * invSat;

    m[0] = R + sat; m[1] = G;       m[2] = B;
    m[5] = R;       m[6] = G + sat; m[7] = B;
    m[10] = R;      m[11] = G;      m[12] = B + sat;
}
複製程式碼
mCmx.setSaturation(folat );
複製程式碼

色彩飽和度.gif

終於寫完了,完結散花。


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1--無 2018-11-10 Android關於Color你所知道的和不知道的一切
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的CSDN 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援

相關文章