2024年Android最全【Android Bitmap】Bitmap解析与应用,面试题解析已整理成文档

总结

首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。

另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。

之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。

下一步还是要查漏补缺,进行针对性复习。

最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

canvas.drawBitmap(splitBitmap, width, height, null);
return bitmap;

}


拼接图片前先创建了一个宽高都为splitBitmap宽高2倍的Bitmap作为容器,然后通过Canvas在容器的四个位置绘制4张一样的图片,最终效果如下。


![](https://img-blog.csdnimg.cn/e5751a6fddb545408c4ac6f6e36a5bae.png#pic_center)


##### 2.3 矩阵变换


Bitmap是像素点的集合,我们可以通过矩阵运算改变每个像素点的位置,达到图形变换的效果。Android中可以通过Matrix类来进行变换,Matrix本身是一个3x3的矩阵,可以通过`Matrix m = new Matrix()`新建一个单位矩阵,原始矩阵的值如下所示。



[1 0 0]
[0 1 0]
[0 0 1]


Matrix中各个位置的变换信息如下所示,scale表示缩放,skew表示错切,trans表示平移,persp等表示透视参数。Bitmap中的每个像素点可以使用一个3x1的矩阵表示,其中x表示当前像素点的横坐标,y表示纵坐标。用该矩阵左乘Bitmap中的所有像素后,就能得到变换后的图像。



[scaleX skewX transX] [x] [scaleX * x + skewX * y + transX]
[skewY scaleY transY] x [y] = [skewY * x + scaleY * y + transY]
[persp0 persp1 persp2] [1] [persp0 * x + persp1 * y + persp2]


简单来说,Matrix是一个容器,保存了用户期望的矩阵变换信息。在将Matrix应用于Bitmap之前,我们可以对其进行各种操作,将变换信息保存进去。矩阵运算可以实现平移、旋转、缩放、错切,因此Matrix也为提供了类似方法。



setTranslate(float dx,float dy): 控制 Matrix 进行位移。
setSkew(float kx,float ky): 控制 Matrix 进行倾斜,kx、ky为X、Y方向上的比例。
setSkew(float kx,float ky,float px,float py): 控制 Matrix 以 px, py 为轴心进行倾斜,kx、ky为X、Y方向上的倾斜比例
setRotate(float degrees): 控制 Matrix 进行 depress 角度的旋转,轴心为(0,0)
setRotate(float degrees,float px,float py): 控制 Matrix 进行 depress 角度的旋转,轴心为(px,py)
setScale(float sx,float sy): 设置 Matrix 进行缩放,sx, sy 为 X, Y方向上的缩放比例。
setScale(float sx,float sy,float px,float py): 设置 Matrix 以(px,py)为轴心进行缩放,sx、sy 为 X、Y方向上的缩放比例


很多时候矩阵变换并不是单一的平移、旋转或缩放,这些变换经常结合在一起使用,此时`setXXX()`方法无法满足要求。因此Matrix提供了`preXXX()`和`postXXX()`方法来组合多个矩阵操作,多个矩阵操作之间通过乘法运算。由于矩阵的乘法是不满足交换律的,因此进行矩阵运算时需要注意乘法的顺序。


在使用`preXXX()`和`postXXX()`时,我们可以将矩阵变换的所有计算看成一个乘法列表,调用`preXXX()`方法时就是向列表头部添加操作,调用`postXXX()`时就是向列表尾部添加操作。例如以下代码中矩阵乘法的执行顺序就是2->1->3->4。需要注意的是,`setXXX()`方法会重置Matrix的变换,如果对下方执行完4个运算的Matrix调用`setTranslate()`方法,那么该Matrix就只有平移效果了。



Matrix matrix = new Matrix();
matrix.preScale(…); // 1
matrix.preTranslate(…); // 2
matrix.postTranslate(…); // 3
matrix.postRotate(…); // 4


##### 2.4 颜色变换


颜色变换主要通过ColorFilter进行,通过`Paint.setColorFilter(ColorFilter filter)`可以设置颜色过滤器,该过滤器会对每一个像素的颜色进行过滤,得到最终的图像。ColorFilter有3个子类,这里主要介绍ColorMatrixColorFilter。


###### 2.4.1 ColorMatrixColorFilter


该颜色过滤器通过矩阵进行色彩变换,先来介绍一下色彩矩阵,Android中的色彩是以ARGB的形式存储的,我们可以通过ColorMatrix修改颜色的值,ColorMatrix定义了一个4x5的float矩阵,矩阵的4行分别表示在RGBA上的向量,其范围值在0f-2f之间,如果为1就是原效果。每一行的第5列表示偏移量,就是指在当前通道上增大或减小多少。



ColorMatrix colorMatrix = new ColorMatrix(new float[]{
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, 1, 0,
});


ColorMatrix与颜色之间的运算如下所示,其实就是矩阵运算,与上一节的Matrix类似。



[a, b, c, d, e] [R] [aR + bG + cB + dA + e]
[f, g, h, i, j] [G] [fR + gG + hB + iA + j]
[k, l, m, n, o] x [B] = [kR + lG + mB + nA + o]
[p, q, r, s, t] [A] [pR + qG + rB + sA + t]
[1]


有了ColorMatrix,就可以对Bitmap上所有的颜色进行修改,例如调整每个通道的值,将初始值1改为0.5,就可以将Bitmap变暗。不过我不太了解色彩,也没在项目中实际使用过,感兴趣的朋友可以看参考1。


##### 2.5 图像混合


图像混合是指对两张原始图像(我们称为DST和SRC)的内容按某种规则合成,从而形成一张包含DST和SRC特点的新图像。例如DST为圆形图像,SRC为照片,可以将它们合成为圆形照片。


Android通过PorterDuffXfermode实现图像混合,它实际上是通过公式对两张图像在Canvas上的所有像素进行ARGB运算,最终在每个像素点得到新的ARGB值。需要注意的是,在`onDraw(Canvas)`方法中进行图像混合时,先绘制的图像为DST,后绘制的图像为SRC,因此需要注意图像的绘制顺序。


PorterDuffXfermode一共提供了18种混合模式,它们的计算公式如下,Sa表示SRC的ALPHA通道,Sc表示SRC的颜色;Da表示DST的ALPHA通道,Dc表示DST的颜色。以CLEAR为例,该模式会清除SRC区域的所有内容。




| 合成模式 | 公式 |
| --- | --- |
| CLEAR | [0, 0] |
| SRC | [Sa, Sc] |
| DST | [Da, Dc] |
| SRC\_OVER | [Sa + (1 - Sa)\*Da, Rc = Sc + (1 - Sa)\*Dc] |
| DST\_OVER | [Sa + (1 - Sa)\*Da, Rc = Dc + (1 - Da)\*Sc] |
| SRC\_IN | [Sa \* Da, Sc \* Da] |
| DST\_IN | [Sa \* Da, Sa \* Dc] |
| SRC\_OUT | [Sa \* (1 - Da), Sc \* (1 - Da)] |
| DST\_OUT | [Da \* (1 - Sa), Dc \* (1 - Sa)] |
| SRC\_ATOP | [Da, Sc \* Da + (1 - Sa) \* Dc] |
| DST\_ATOP | [Sa, Sa \* Dc + Sc \* (1 - Da)] |
| XOR | [Sa + Da - 2 \* Sa \* Da, Sc \* (1 - Da) + (1 - Sa) \* Dc] |
| DARKEN | [Sa + Da - Sa \* Da, Sc \* (1 - Da) + Dc \* (1 - Sa) + min(Sc, Dc)] |
| LIGHTEN | [Sa + Da - Sa \* Da, Sc \* (1 - Da) + Dc \* (1 - Sa) + max(Sc, Dc)] |
| MULTIPLY | [Sa \* Da, Sc \* Dc] |
| SCREEN | [Sa + Da - Sa \* Da, Sc + Dc - Sc \* Dc] |
| ADD | Saturate(S + D) |


如果你使用过PorterDuffXfermode,你可能见过下面这张图,Android官方的样例就是这个效果,不过官方只提供了16种混合模式的样例,我在Demo中把ADD和OVERLAY也添加了进去。  
 ![](https://img-blog.csdnimg.cn/6201b557d6694ff8b45538e83ab176aa.png#pic_center)  
 当然你也可能见过这张图。  
 ![](https://img-blog.csdnimg.cn/f89b49526df641a4a521f35fb331d04b.png#pic_center)  
 乍一看,这两张图中的DST和SRC原始图像都是一样的,但是为什么使用了同样的混合模式后,显示的结果不同呢?


关键就在于DST和SRC的大小,第一张图中的DST和SRC都是Bitmap,它们的大小与Canvas相等,只是在Bitmap的某个区域绘制了圆和矩形。Demo代码如下,可以看到`makeDst()`和`makeSrc()`中创建的Bitmap与整个View(或者说Canvas)是相等的。这也解释了为什么第一张图的CLEAR模式下的结果是空白的,因为SRC的大小是整个View的大小,CLEAR模式表示清除SRC区域的内容,最终将整个View的内容清除了。



public class XFerModeView extends View {

private Paint mPaint;
private PorterDuffXfermode mPorterDuffXfermode;
private int mWidth;
private int mHeight;

// 省略构造方法......

private void init() {
    setLayerType(LAYER_TYPE_SOFTWARE, null);
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    if (mWidth != w || mHeight != h) {
        mWidth = w;
        mHeight = h;
        invalidate();
    }
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawBackground(canvas);
    int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
    drawCompositionInFullSize(canvas);
    canvas.restoreToCount(sc);
}

private void drawBackground(Canvas canvas) {
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(5);
    mPaint.setColor(Color.BLACK);
    canvas.drawRect(0, 0, mWidth, mHeight, mPaint);
}

private void drawCompositionInFullSize(Canvas canvas) {
    mPaint.setStyle(Paint.Style.FILL);
    Bitmap dst = makeDst();
    Bitmap src = makeSrc();
    // 绘制DST
    canvas.drawBitmap(dst, 0, 0, mPaint);
    // 设置图像混合模式
    mPaint.setXfermode(mPorterDuffXfermode);
    // 绘制SRC
    canvas.drawBitmap(src, 0, 0, mPaint);
    // 清除图像混合模式
    mPaint.setXfermode(null);
}

private Bitmap makeDst() {
    Bitmap bm = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(bm);
    mPaint.setColor(0xFFFFCC44);
    c.drawOval(10, 10, mWidth * 3f / 4, mHeight * 3f / 4, mPaint);
    return bm;
}

private Bitmap makeSrc() {
    Bitmap bm = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(bm);
    mPaint.setColor(0xFF66AAFF);
    c.drawRect(mWidth * 1f / 3, mHeight * 1f / 3,
            mWidth * 19f / 20, mHeight * 19f / 20, mPaint);
    return bm;
} 

在第二张图中,绘制SRC和DST时创建的图像大小就是圆或矩形的大小,最终的结果也与第一张图有所不同,修改后的代码如下。还是以CLEAR模式为例,此时清除的就只是矩形区域SRC的图像,可以看到DST中与SRC相交的部分被清除了。



public class XFerModeView extends View {

// 省略重复代码......

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawBackground(canvas);
    int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
    drawCompositionInSelfSize(canvas);
    canvas.restoreToCount(sc);
}

/**
 * 混合图像的大小只有可见区域大小
 */
private void drawCompositionInSelfSize(Canvas canvas) {
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setColor(0xFFFFCC44);
    canvas.drawOval(10, 10, mWidth * 3f / 4, mHeight * 3f / 4, mPaint);
    mPaint.setXfermode(mPorterDuffXfermode);
    mPaint.setColor(0xFF66AAFF);
    canvas.drawRect(mWidth * 1f / 3, mHeight * 1f / 3,
            mWidth * 19f / 20, mHeight * 19f / 20, mPaint);
    mPaint.setXfermode(null);
}

}


使用图像混合时还有一个要注意的地方:上述代码在`onDraw(Canvas)`方法中绘制混合图像时会先调用`int sc = canvas.saveLayer(...)`生成一个新的图层(Layer),sc表示图层的编号,随后在新Layer上绘制DST和SRC,绘制完后将该Layer添加到Canvas上。那么这里为什么需要新的Layer来绘制DST和SRC,而不是直接在Canvas上绘制呢?


Layer可以理解为画布Canvas的一个层级,默认情况下Canvas只有一个Layer,所有的绘制都在同一图层上。当需要绘制多层图像时,可以通过`canvas.saveLayer(...)`生成新的Layer,在新Layer上绘制的内容是独立的,不会影响到其他Layer的内容,调用`canvas. restoreToCount(int sc)`时将该Layer覆盖到Canvas现有的图像上。Canvas通过栈的形式管理Layer,示意图如下。


![](https://img-blog.csdnimg.cn/44f6e96861f74737a50538726277d901.png#pic_center)


之前提到进行图像混合时,先绘制的内容是DST,后绘制的是SRC。如果不新建Layer的话,在绘制SRC时,Canvas上的所有内容都会被当作DST,所以背景等内容也会参与图像混合,很容易得到错误的效果。


以上就是图像混合的基本介绍,图像混合的应用场景比较广泛,这里介绍几种常见的场景。


###### 2.5.1 图像切割


图像切割用于将图像切割成特定的形状。可以是常见形状如圆形或圆角矩形,也可以切割为五角星这样的非常规形状,使用这一类非常规形状时需要该形状的底图。


将图像裁剪为圆角矩形时比较简单,在`onDraw(Canvas)`中新建图层,绘制圆角矩形作为DST,再绘制原图作为SRC即可,此时图像混合模式应设置为SRC\_IN,代码如下,`decodeSampledBitmapFromResource(...)`就是1.2节的大图采样。



public class RoundCornerView extends View {

private Paint mPaint;
private PorterDuffXfermode mFerMode;
private Bitmap mBitmap;
private Rect mBitmapRect;
private int mWidth;
private int mHeight;

// 省略构造函数...

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mFerMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    if (mWidth != w || mHeight != h) {
        mWidth = w;
        mHeight = h;
        mBitmap = BitmapUtils.decodeSampledBitmapFromResource(
                getContext().getResources(), R.drawable.compress_test, mWidth, mHeight);
        mBitmapRect = new Rect(0, 0, mWidth, mHeight);
        invalidate();
    }
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
    canvas.drawRoundRect(0, 0, mWidth, mHeight, 50, 50, mPaint);
    mPaint.setXfermode(mFerMode);
    canvas.drawBitmap(mBitmap, null, mBitmapRect, mPaint);
    mPaint.setXfermode(null);
    canvas.restoreToCount(sc);
}

......

}


最终效果如下,同理可以将图像切割为圆形等基础形状。


![](https://img-blog.csdnimg.cn/e1adadddafad4cd9bab7e5109faeedaf.png)  
 如果要将图像切割为五角星这样的图案,就需要使用一张五角星的底图,需要注意的是,底图上五角星以外的部分应该是透明的,否则切割出来还是原来的形状。其代码与切割为圆角矩形大同小异,只需要将绘制圆角矩形的部分换成绘制五角星即可。



public class StarPicView extends View {

private Paint mPaint;
private PorterDuffXfermode mMode;
private Bitmap mBgBitmap;
private Bitmap mBitmap;
private int mWidth, mHeight;
private Rect mDrawRect;

// 省略构造函数......

private void init() {
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    if (mWidth != w || mHeight != h) {
        mWidth = w;
        mHeight = h;
        mBgBitmap = BitmapUtils.decodeSampledBitmapFromResource(
                getContext().getResources(), R.drawable.star4, mWidth, mHeight);
        mBitmap = BitmapUtils.decodeSampledBitmapFromResource(
                getContext().getResources(), R.drawable.icon3, mWidth, mHeight);
        mDrawRect = new Rect(0, 0, mWidth, mHeight);
        invalidate();
    }
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
    canvas.drawBitmap(mBgBitmap, null, mDrawRect, mPaint);
    mPaint.setXfermode(mMode);
    canvas.drawBitmap(mBitmap, null, mDrawRect, mPaint);
    mPaint.setXfermode(null);
    canvas.restoreToCount(sc);
}

}


最终的效果如下。  
 ![](https://img-blog.csdnimg.cn/7b4d0360ef954978af2cd5e5462a8c60.png)


###### 2.5.2 色彩合成


色彩合成可以为图片添加新的效果,当使用纯色与照片混合时,可以改变图片整体的色调。例如黄色可以让图片具有泛黄的怀旧效果,红色可以让图片更温暖。以下代码通过SCREEN混合模式将半透明的红色与图片混合。



public class ColorComposeView extends View {

......

private void init() {
    setLayerType(LAYER_TYPE_SOFTWARE, null);
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mMode = new PorterDuffXfermode(PorterDuff.Mode.SCREEN);
}

......

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.WHITE);
    int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null);
    canvas.drawColor(0x44FF0000);
    mPaint.setXfermode(mMode);
    canvas.drawBitmap(mBitmap, null, mRect, mPaint);
    mPaint.setXfermode(null);
    canvas.restoreToCount(sc);
}

}


怎么样,是不是觉得小姐姐看上去都温柔了一些?  
 ![](https://img-blog.csdnimg.cn/af72dd05347b4e33aee7f27a4e54a3a8.png)  
 出了纯色混合,也可以将两张图片进行合成,例如通过一张毛玻璃的底图,可以为照片添加一定的模糊效果,底图如下所示。  
 ![](https://img-blog.csdnimg.cn/27165b39a24942dea9226b87b3b099f2.png)  
 绘制时将底图作为DST,将照片作为SRC,混合模式使用OVERLAY,代码与绘制切割五角星的大同小异,不再赘述。最终得到如下的效果。  


### 尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

**进阶学习视频**

![](https://img-blog.csdnimg.cn/img_convert/a6749ab8ed20ed11964dd5916b08f512.webp?x-oss-process=image/format,png)

**附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题** (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

![](https://img-blog.csdnimg.cn/img_convert/3822dd1c45211ab238992b5154ad5e98.webp?x-oss-process=image/format,png)



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

学习视频**

[外链图片转存中...(img-OW0w39bv-1714834422273)]

**附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题** (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中...(img-22GYxWNJ-1714834422274)]



**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化学习资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值