自定义控件绘制(Paint之setXfermode、硬件加速)篇八

内容来自:

  1. https://blog.csdn.net/harvic880925/article/details/51264653

不知不觉,已经来到了 xfermode 这部分了。非常感谢原博主提供的一系列非常优秀的教程,剖析、细节方面讲的非常好,这边对着源博客进行练习,做记录(内容几乎照搬源博客),不然不使用,就特别容易忘,特别是前端,变化是非常快的;现在rn很火,混合开发也很流行,但我觉得还是先补补native基础,这些东西不用就忘;

典型如: xfermode,一个应用场景就是 蒙版图,通过获取页面上具体的控件位置,结合xfermode实现掏空,呈现完美蒙版页面;(还记得当年我们可是拿图当做蒙版的,😝);

好,开始!

GPU硬件加速

什么是硬件加速

GPU英文全称Graphic Processing Unit,中文翻译为“图形处理器”。与CPU不同,GPU是专门为处理图形任务而产生的芯片。 为了专门处理多媒体的计算、存储任务,GPU就应运而生了,GPU中自带处理器和存储器,以用来专门计算和存储多媒体任务。

对于Android来讲,在API 11之前是没有GPU的概念的,在API 11之后,在程序集中加入了对GPU加速的支持,在API 14之后,硬件加速是默认开启的!我们可以显式地强制图像计算时使用GPU而不使用CPU.

在CPU绘制和GPU绘制时,在流程上区别的:

在基于软件的绘制模型下,CPU主导绘图,视图按照两个步骤绘制:

  1. 让View层次结构失效
  2. 绘制View层次结构

在基于硬件加速的绘制模式下,GPU主导绘图,绘制按照三个步骤绘制:

  1. 让View层次结构失效
  2. 记录、更新显示列表;
  3. 绘制显示列表

GPU加速时,流程中多了一项“记录、更新显示列表”,表示在第一步View层次结构失效后,将这些View的绘制函数作为绘制指令记录在一个显示列表中,然后再读取列表中的绘制指令并调用openGL相关函数完成实际绘制;
所以在GPU加速时,实际是使用OpenGL的函数来完成绘制的。

GPU硬件加速缺点
GPU加速硬件提高了Android系统显示和刷新的速度,但也有以下缺点:

  • 兼容性问题:由于是将绘制函数转换成OpenGL命令来绘制,定然会存在OpenGL并不能完全支持原始绘制函数的问题,所以这就会造成在打开GPU加速时,效果会失效的问题。
  • 内存消耗问题:由于需要OpenGL的指令,所以需要把系统中的OpenGL相关的包加载到内存中来,所以单纯OpenGL API调用就会占用8MB,而实际上会占用更多内存;
  • 电量消耗问题:多使用了一个部件,当然会更耗电……

下图显示了一些特殊函数硬件加速开始支持的平台等级:(红叉表示任何平台都不支持,不在列表中的默认在API 11就开始支持)


Canvas相关函数

Paint相关函数

Xfermode与Shader

http://developer.android.com/guide/topics/graphics/hardware-accel.html

关闭GPU硬件加速

现在的APP,很多都是从Android 4.0开始支持的,即API 必然大于14了,就就是默认开启了硬件加速,如果遇到了不支持硬件加速的函数时,就考虑到要关闭硬件加速了;

针对不同类型的(如上图),Android给我们提供了不同的禁用方法:
硬件加速分全局(Application)、Activity、Window、View 四个层级 ;

  1. 全局
    在AndroidManifest.xml文件为application标签添加如下的属性即可为整个应用程序开启/关闭硬件加速:
    <application android:hardwareAccelerated="true" ...>
  2. Activity
    在Activity 标签下使用 hardwareAccelerated 属性开启或关闭硬件加速:
    <activity android:hardwareAccelerated="false" />
  3. Window
    在Window 层级使用如下代码开启硬件加速:(Window层级不支持关闭硬件加速)
    getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
  4. View
    View 级别如下关闭硬件加速:(view 层级上不支持开启硬件加速
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    或使用android:layerType=”software”关闭硬件加速
    <LinearLayout 
        android:orientation="vertical" 
        android:layerType="software"  >
    

Xfermode

混合图形模式;读原博客时,这里有 AvoidXfermode,因为 AvoidXfermode 不支持硬件加速,在android API > 16 时,此类过时了,这里就不记录了;具体参考原博客吧;

其他一些子类,在我的源码中,都找不到了,就不记录;

我们主要看一下 其子类PorterDuffXfermode的使用;

PorterDuffXfermode有些函数也不支持硬件加速,在涉及到使用不支持硬件加速的函数时,我们需要在View层禁用掉硬件加速;

Xfermode使用流程:

  1. 禁用硬件加速(根据使用的函数来确定是否禁):
    setLayerType(View.LAYER_TYPE_SOFTWARE, null); 
    
  2. 启用离屏绘制(图层)
  //新建图层  
  int layerID   = canvas.saveLayer(0,0,width,height,mPaint,Canvas.ALL_SAVE_FLAG);  
//TODO 核心绘制代码  
//还原图层  
 canvas.restoreToCount(layerID);  

Android在绘图时会先检查该画笔Paint对象有没有设置Xfermode,如果没有设置Xfermode,那么直接将绘制的图形覆盖Canvas对应位置原有的像素;如果设置了Xfermode,那么会按照Xfermode具体的规则来更新Canvas中对应位置的像素颜色。

Xfermode 之 PorterDuffXfermode

构造函数:

public PorterDuffXfermode(PorterDuff.Mode mode)

看到了熟悉的PorterDuff.Mode,在setColorFilter时已经用过它,(模式真多18个)
更多参考原博客:
https://blog.csdn.net/harvic880925/article/details/51264653

在这里涉及到2个比较重要的概念,目标图 DST与源图 SRC;

示例代码(来自原博客):

    private val wid = 400
    private val hei = 400
    private lateinit var dstBmp: Bitmap
    private lateinit var srcBmp: Bitmap
    private var paint: Paint

    init {
        paint = Paint(Paint.ANTI_ALIAS_FLAG)
        dstBmp = makeDst(wid, hei)
        srcBmp = makeSrc(wid, hei)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // 根据使用绘制流程类,这里没有使用相关函数,不需要关闭硬件加速
        val layerID = canvas.saveLayer(0f, 0f, width * 2.toFloat(), height * 2.toFloat(), paint)
        // 1. 先画目标图像(圆)
        canvas.drawBitmap(dstBmp, 0f, 0f, paint)
        // 2.设置xfermode
        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
        // 3.画原图(方),最后在源图像上生成结果图并更新到目标图像上
        canvas.drawBitmap(srcBmp, wid / 2.toFloat(), hei / 2.toFloat(), paint)
        paint.xfermode = null

        canvas.restoreToCount(layerID)
    }

    fun makeSrc(w: Int, h: Int): Bitmap {
        val bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
        val c = Canvas(bm)
        val p = Paint(Paint.ANTI_ALIAS_FLAG)
        p.color = -0x995501
        c.drawRect(0f, 0f, w.toFloat(), h.toFloat(), p)
        return bm
    }

    fun makeDst(w: Int, h: Int): Bitmap {
        val bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
        val c = Canvas(bm)
        val p = Paint(Paint.ANTI_ALIAS_FLAG)
        p.color = -0x33bc
        c.drawOval(RectF(0f, 0f, w.toFloat(), h.toFloat()), p)
        return bm
    }
SRC_IN效果图

对比setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.XXXX));中的混合过程

在PorterDuffColorFilter中的混合过程与这里的setXfermode()设置混合模式的计算方式和效果是完全相同的,只是在PorterDuffColorFilter中只能使用纯色彩,而且是完全覆盖在图片上方;而setXfermode()则不同,它只会在目标图像和源图像交合的位置起作用,而且源图像不一定是纯色的。

需要关注的2点

图片来自原博客-模式PorterDuff.Mode.LIGHTEN

计算效果图像时,是以源图像所在区域为计算目标的,把计算后的源图像更新到对应区域内。

  • 区域一是源图像和目标图像的相交区域,由于在这个区域源图像和目标图像像素都不是空白像素,所以可以明显看出颜色的计算效果。
  • 在区域二中,源图像所在区域的目标图像是空白像素,所以这块区域所表示的意义就是,当某一方区域是空白像素时,此时的计算结果。

总而言之:我们在下面的各个模式计算时,只需要关注图示中的区域一和区域二;其中区域一表示当源图像和目标图像像素都不是空白像素时的计算结果,而区域二则表示当某一方区域是空白像素时,此时的计算结果。

18种模式

18种模式对比图

18种模式说明

颜色叠加相关模式(6个)

涉及到的几个模式有6个(上图的第3,4列):

  • Mode.ADD(饱和度相加)
    计算公式是Saturate(S + D);ADD模式简单来说就是对SRC与DST两张图片相交区域饱和度进行相加;
  • Mode.DARKEN(变暗)
  • Mode.LIGHTEN(变亮)
    两个图像重合的区域才会有颜色值变化,所以只有重合区域才有变亮的效果,源图像非重合的区域,由于对应区域的目标图像是空白像素,所以直接显示源图像。
LIGHTEN
  • Mode.MULTIPLY(正片叠底)
    计算公式:[Sa * Da, Sc * Dc],在计算alpha值时的公式是Sa * Da,是用源图像的alpha值乘以目标图像的alpha值;由于源图像的非相交区域所对应的目标图像像素的alpha是0,所以结果像素的alpha值仍是0,所以源图像的非相交区域在计算后是透明的。

  • Mode.OVERLAY(叠加)

  • Mode.SCREEN(滤色)

SRC相关模式(5个)

在遇到当图像相交时,需要显示源图像时,就需要从SRC相关的模式考虑了:

  • Mode.SRC
    在处理源图像所在区域的相交问题时,全部以源图像显示

  • Mode.SRC_IN
    计算公式为:[Sa * Da, Sc * Da]
    在这个公式中结果值的透明度和颜色值都是由Sa,Sc分别乘以目标图像的Da来计算的。所以当目标图像为空白像素时,计算结果也将会为空白像素。

  • Mode.SRC_OUT
    计算公式为:[Sa * (1 - Da), Sc * (1 - Da)]
    计算结果的透明度=Sa * (1 - Da);也就是说当目标图像图像完全透明时,计算结果将是透明的;
    源图像与目标图像的相交部分由于目标图像的透明度为100%,所以相交部分的计算结果为空白像素(1-100%=0)。在目标图像为空白像素时,完全以源图像显示。

    以目标图像的透明度的补值来调节源图像的透明度和色彩饱和度。即当目标图像为空白像素时,就完全显示源图像,当目标图像的透明度为100%时,交合区域为空像素.

  • Mode.SRC_OVER
    计算公式为:[Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc] ;
    在目标图像的顶部绘制源图像。从公式中也可以看出目标图像的透明度为Sa + (1 - Sa)*Da;即在源图像的透明度基础上增加一部分目标图像的透明度。增加的透明度是源图像透明度的补量;目标图像的色彩值的计算方式同理,所以当源图像透明度为100%时,就原样显示源图像;

  • Mode.SRC_ATOP
    计算公式为:[Da, Sc * Da + (1 - Sa) * Dc] ;对比SRC_IN
    SRC_IN: [Sa * Da, Sc * Da]
    SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]
    先看透明度:在SRC_IN中是Sa * Da,在SRC_ATOP是Da
    SRC_IN是源图像透明度乘以目标图像的透明度做为结果透明度,而SRC_ATOP是直接使用目标图像的透明度做为结果透明度
    再看颜色值:
    SRC_IN的颜色值为 Sc * Da,SRC_ATOP的颜色值为Sc * Da + (1 - Sa) * Dc;SRC_ATOP在SRC_IN的基础上还增加了(1 - Sa) * Dc;

    区别:
    1. 当透明度只有100%和0%时,SRC_ATOP是SRC_IN是通用的
    2. 当透明度不只有100%和0%时,SRC_ATOP相比SRC_IN源图像的饱和度会增加,即会显得更亮!

SRC模式相关应用示例

示例1:SRC_IN实现圆角图片

目标为圆角矩形,然后将原图放入进去,圆角图片就出来了;

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    val srcBmp = BitmapFactory.decodeResource(resources, R.mipmap.juntuan)
    val bmpWidth = srcBmp.width
    val bmpHeight = srcBmp.height

    // .1目标图为圆角矩形
    val dstBmp = makeDest(bmpWidth, bmpHeight)
    val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    val layerID = canvas.saveLayer(0f, 0f, bmpWidth.toFloat(), bmpHeight.toFloat(), paint)
    canvas.drawBitmap(dstBmp, 0f, 0f, paint)
    // 2.设置xfermode
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)  // OVERLAY
    canvas.drawBitmap(srcBmp, 0f, 0f, paint)
    paint.xfermode = null
    canvas.restoreToCount(layerID)
}

// dst 为圆角
fun makeDest(w: Int, h: Int): Bitmap {
    val bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
    val c = Canvas(bm)
    val p = Paint(Paint.ANTI_ALIAS_FLAG)
    p.color = Color.GRAY
    c.drawRoundRect(RectF(0f, 0f, w.toFloat(), h.toFloat()), 40f, 40f, p)
    return bm
}
SRC_IN模式-圆角图片

注意:其实这样实现圆角图片,是有问题的,问题在于重绘

示例2:SRC_IN实现倒影

在相交时利用目标图像的透明度来改变源图像的透明度和饱和度;利用这个特性,结合matrix形成倒影;
参考原博客,没有那个透明图,(。•ˇ‸ˇ•。);

  1. 我们这,用drawable构建一个渐变图形 (上面半透明-渐变到完全透明):
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <!-- 渐变色 -->
    <gradient
        android:angle="270"
        android:endColor="#00ffffff"
        android:startColor="#80ffffff"
        android:type="linear" />
</shape>
  1. 生成目标图dstBm:
val dstDrawable = resources.getDrawable(R.drawable.drawable_liner_gradient_white)
val dstBm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888)
val c = Canvas(dstBm)
dstDrawable.setBounds(0, 0, bmpWidth, bmpHeight / 1.5f.toInt())
dstDrawable.draw(c)
  1. 旋转原图180度,通过模式 SRC_IN,draw 到 dst 上, 整体如下(这里的原图,不是指的SRC):
 val srcBmp = BitmapFactory.decodeResource(resources, R.mipmap.juntuan)
 val bmpWidth = srcBmp.width
 val bmpHeight = srcBmp.height
 val paint = Paint(Paint.ANTI_ALIAS_FLAG)

 // 1.画出原图,注意不是 SRC
 canvas.drawBitmap(srcBmp, 0f, 0f, paint)

 // 有渐变度的drawable
 val dstDrawable = resources.getDrawable(R.drawable.drawable_liner_gradient_white)
 val dstBm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888)
 val c = Canvas(dstBm)
 dstDrawable.setBounds(0, 0, bmpWidth, bmpHeight / 2f.toInt())
 dstDrawable.draw(c)

 // 倒立原图,形成 SRC
 val matrix = Matrix()
 matrix.setScale(1f, -1f)
 val revertBmp = Bitmap.createBitmap(srcBmp, 0, 0, srcBmp.width, srcBmp.height, matrix, true)

 // 2.再画出倒影,倒影在原图的下面(向下translate原图的高度)
 canvas.translate(0f, bmpHeight.toFloat())
 val layerID2 = canvas.saveLayer(0f, 0f, bmpWidth.toFloat(), bmpHeight.toFloat(), paint)
 canvas.drawBitmap(dstBm,0f,0f, paint)
 paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
 canvas.drawBitmap(revertBmp, 0f,0f,paint)
 paint.xfermode = null
 canvas.restoreToCount(layerID2)

 canvas.restore()
倒影效果

示例三:Mode.SRC_OUT 橡皮擦效果

把手指轨迹做为目标图像,在与源图像计算时,有手指轨迹的地方就变为空白像素了,形成橡皮檫。

注意:这里是在 DST 上做得文章 因为:Sa * (1 - Da)

val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    style = Paint.Style.STROKE
    strokeWidth = 10f
    color = Color.BLACK
}

val srcBmp = BitmapFactory.decodeResource(resources, R.mipmap.sanjing)
var dstBmp = Bitmap.createBitmap(srcBmp.width, srcBmp.height, Bitmap.Config.ARGB_8888)
val path = Path()
var preX = 0f
var preY = 0f

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // create src, draw path to srcBmp, 手势画到 dst 上
    val c = Canvas(dstBmp)
    c.drawPath(path, paint)

    val layerID2 = canvas.saveLayer(0f, 0f, srcBmp.width.toFloat(), srcBmp.height.toFloat(), paint)
    canvas.drawBitmap(dstBmp, 0f, 0f, paint)
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
    canvas.drawBitmap(srcBmp, 0f, 0f, paint)
    paint.xfermode = null
    canvas.restoreToCount(layerID2)
}

override fun onTouchEvent(event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            path.moveTo(event.x, event.y)
            preX = event.x
            preY = event.y
            return true
        }
        MotionEvent.ACTION_MOVE -> {  // 平滑过渡
            val endX = (preX + event.x) / 2
            val endY = (preY + event.y) / 2
            path.quadTo(preX, preY, endX, endY)
            preX = event.x
            preY = event.y
        }
    }
    postInvalidate()
    return super.onTouchEvent(event)
}
三井,永不放弃的男人!

类似刮刮卡效果,请参考原博客;

DST相关模式(5个)

在DST相关的模式中,在处理相交区域时,优先以目标图像显示为主。

  • Mode.DST
    计算公式为:[Da, Dc]
    在处理源图像所在区域的相交问题时,正好与Mode.SRC相反,全部以目标图像显示 ;

  • Mode.DST_IN
    计算公式为:[Da * Sa,Dc * Sa]
    Mode.SRC_IN计算公式为[Sa * Da, Sc * Da] 正好与SRC_IN相反,Mode.DST_IN是在相交时利用源图像的透明度来改变目标图像的透明度和饱和度。当源图像透明度为0时,目标图像就完全不显示。

  • Mode.DST_OUT
    计算公式为:[Da * (1 - Sa), Dc * (1 - Sa)]
    Mode.SRC_OUT是利用目标图像的透明度的补值来改变源图像的透明度和饱和度。而Mode.DST_OUT反过来,是通过源图像的透明度补值来改变目标图像的透明度和饱和度。
    简单来说,在Mode.DST_OUT模式下,就是相交区域显示的是目标图像,目标图像的透明度和饱和度与源图像的透明度相反,当源图像透明底是100%时,则相交区域为空值。当源图像透明度为0时,则完全显示目标图像。非相交区域完全显示目标图像。

  • Mode.DST_OVER
    计算公式为:[Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc]
    对比Mode.SRC_OVER

  • Mode.DST_ATOP
    计算公式为:[Sa, Sa * Dc + Sc * (1 - Da)]
    对比Mode.SRC_ATOP

DST相关模式是完全可以使用SRC对应的模式来实现的,只不过需要将目标图像和源图像对调一下即可。
在SRC模式中,是以显示源图像为主,通过目标图像的透明度来调节计算结果的透明度和饱和度,而在DST模式中,是以显示目标图像为主,通过源图像的透明度来调节计算结果的透明度和饱和度。

示例一:Mode.DST_IN 区域内波浪效果

DST目标图像为波纹图;目标图片,除了文字部分为白色,其他均为透明像素;

val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    style = Paint.Style.FILL_AND_STROKE
    strokeWidth = 2f
    color = Color.BLUE
}

val srcBmp = BitmapFactory.decodeResource(resources, R.mipmap.havric)
val dstBmp = Bitmap.createBitmap(srcBmp.width, srcBmp.height, Bitmap.Config.ARGB_8888)
val waveWidth = srcBmp.width * 2.0f
val originY = srcBmp.height / 2.0f - 10f
var dx = 0f
val path = Path()

val anim = ValueAnimator.ofFloat(0f, waveWidth).apply {
    interpolator = LinearInterpolator()
    duration = 2000
    repeatMode = RESTART
    repeatCount = ValueAnimator.INFINITE
    addUpdateListener {
        dx = it.animatedValue as Float
        postInvalidate()
    }
}

init {
    anim.start()
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawColor(Color.BLACK)
    canvas.save()

    createWave()

    // 生成dst 并清空
    val c = Canvas(dstBmp)
    c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR)
    c.drawPath(path, paint)

    // === 画原图
    canvas.drawBitmap(srcBmp, 0f, 0f, paint)
    val layerID2 = canvas.saveLayer(0f, 0f, srcBmp.width.toFloat(), srcBmp.height.toFloat(), paint)
    canvas.drawBitmap(dstBmp, 0f, 0f, paint)
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
    canvas.drawBitmap(srcBmp, 0f, 0f, paint)
    paint.xfermode = null
    canvas.restoreToCount(layerID2)

    canvas.restore()
}
// 创建波浪
private fun createWave() {
    path.apply {
        path.reset()
        // path的起始位置向左移一个波长
        path.moveTo(-waveWidth + dx, originY)
        val halfWaveWidth = waveWidth / 2
        var i = -halfWaveWidth
        while (i <= srcBmp.width + halfWaveWidth) {
            rQuadTo(halfWaveWidth / 2, -50f, halfWaveWidth, 0f)
            rQuadTo(halfWaveWidth / 2, 50f, halfWaveWidth, 0f)
            i += halfWaveWidth
        }

        lineTo(srcBmp.width * 1.0f, srcBmp.height * 1.0f)
        lineTo(0f, srcBmp.height.toFloat())
        close()
    }
}

override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    anim?.let {
        it.cancel()
    }
}
波浪效果

其他模式

  • Mode.CLEAR
    计算公式:[0, 0]
    计算结果直接就是[0,0]即空像素。也就是说,源图像所在区域都会变成空像素;
    如上面的:
      val c = Canvas(dstBmp)
      c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR)
    
  • Mode.XOR
    计算公式:[Sa + Da - Sa*Da,Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]
    效果图:像异或,从计算公式上看:
    公式中透明度部分:Sa + Da - Sa*Da,就是将目标图像和源图像的透明度相加,然后减去它们的乘积,所以计算结果的透明度会增大(即比目标图像和源图像都大,当其中一个图像的透明度为1时,那结果图像的透明度肯定是1)
    然后再看颜色值部分:Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc);表示源图像和目标图像分别以自己的透明度的补值乘以对方的颜色值,然后相加得到结果。最后再加上Sc, Dc中的最小值。

如何应用

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

推荐阅读更多精彩内容