• 【Android】解析Paint类中Xfermode的使用


    Paint类提供了setXfermode(Xfermode xfermode)方法,Xfermode指明了原图像和目标图像的结合方式。谈到Xfermode就不得不谈它的派生类PorterDuffXfermode,PorterDuffXfermode类只有一个构造函数如下:

    public PorterDuffXfermode (PorterDuff.Mode mode)

    创建PorterDuffXfermode实例,需要指定porter-duff模式。porter-duff实际上是Thomas Porter和Tom Duff的简写,他们在1984年发表的“Compositing Digital Images”文章中指出了该项发现。

    PorterDuff.Mode是一个枚举类,下面是不同模式的结合图:

    观察上面的16种模式。我们可以把一副图像分成两部分透明度(Alpha)和颜色(Color),源图像透明度(Alphasrc)和目标图像透明度(Alphadst)按照某种合成关系就产生了结果图像透明度(Alphaout),源图像颜色(Colorsrc)和目标图像颜色(Colordst)按照某种合成关系就产生了结果图像颜色(Colorout),然后组合Alphaout和Colorout就可以得到图像的一个像素点值了。这里的合成关系依据不同的PorterDuff.Mode会规定不同的合成关系。

    在官方文档中,对每种模式的合成关系有更为详细的描述:PorterDuff.Mode、


    图象组合实现的代码:

    Paint paint = new Paint();
    canvas.drawBitmap(destinationImage, 0, 0, paint);
    
    PorterDuff.Mode mode = // choose a mode
    paint.setXfermode(new PorterDuffXfermode(mode));
    
    canvas.drawBitmap(sourceImage, 0, 0, paint);

    接下来笔者将会展示这些模式的实现,希望能够对你所有帮助。

    众所周知,Bitmap中保存着Canvas中绘制的数据。为了更好的展示Xfermode功能,我们首先应该创建一张透明的画布层Canvas,然后在该画布层上完成一些列的porter-duff模式,最后将该画布层中的数据Bitmap绘制到屏幕层的画布中。

    //set hardware layer
    setLayerType(View.LAYER_TYPE_HARDWARE, null);
    
    Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth,screenHeight,Config.ARGB_8888);
    Canvas separate_canvas=new Cavnas(bitmap);
    
    separate_canvas.drawBitmap(destinationImage,0,0,paint);
    
    PorterDuff.Mode mode=//choose a mode
    paint.setXfermode(new PorterDuffXfermode(mode));
    separate_canvas.drawBitmap(sourceImage,0,0,paint);
    
    //retore to software layer
    setLayerType(View.LAYER_TYPE_SOFTWARE,null);
    
    //drawing separate_bitmap to base canvas
    canvas.drawBitmap(separate_bitmap, 0, 0, paint);

    首先用separate_bitmap创建一个separate_canvas,因为separate_bitmap上没有任何数据,所以separate_canvas这时候是完全透明的,这时候我们可以在separate_canvas上顺利地完成Xfermode的操作。之所以不在屏幕canvas上的进行这些操作,这是因为屏幕canvas不是无色的和透明的(默认是白色的和不透明的),也就是说屏幕的Bitmap在为Config.ARGB_8888的情况下,那么它的ARGB值就分别是255、255、255、255(上面提过屏幕的canvas默认是白色的和不透明的),这显然将会对Xfermode的合成造成影响。但如果这样的影响是在你的预期范围内的话,可以考虑直接绘制到屏幕canvas上。

    然后将separate_canvas已经绘制完的separate_bitmap数据再绘制屏幕canvas的bitmap中。

    canvas.drawBitmap(separate_bitmap, 0, 0, paint);

    有一点不得不提就是hardware

    //set hardware layer
    setLayerType(View.LAYER_TYPE_HARDWARE, null);
    
    //do Xfermode combine.
    ....
    
    //retore to software layer
    setLayerType(View.LAYER_TYPE_SOFTWARE,null);

    视图默认的图层类型不是hardware,而是software。设置图层类型类型为hardware,就会开启硬件加速,在渲染图形时就会强制使用android硬件渲染通道。在AndroidManifest.xml进行如下配置也会开启硬件加速:

    android:hardwareAccelerated="true"

    hardware layers对于复杂图形树的绘制更快、更高效。不仅仅是Xfermode,任何需要进行开发复杂视图或是快速动画,笔者都强烈建议开启此硬件加速。

    Canvas还提供了方法saveLayer,利用这个方法可以达到分层开发,它的原理图:

    但是笔者不建议在开发中使用saveLayer这个方法,尤其在复制界面或是动画中,因为saveLayer是一个非常“昂贵”的方法,它通常会占用更多的资源。

    activity_main.xml

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context=".MainActivity" >
    
        <ImageView
            android:id="@+id/image"
            android:background="@android:drawable/edit_text"
            android:src="@drawable/ic_launcher"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_alignParentTop="true"/>
        
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/image"
             >
        <RadioGroup
            android:id="@+id/radioGroup1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
            <RadioButton
                android:id="@+id/radio0"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:checked="true"
                android:text="Src" />
            <RadioButton
                android:id="@+id/radio1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Dst" />
            <RadioButton
                android:id="@+id/radio2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="SrcOver" />
            <RadioButton
                android:id="@+id/radio3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="DstOver" />
            <RadioButton
                android:id="@+id/radio4"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="SrcIn" />
            <RadioButton
                android:id="@+id/radio5"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="DstIn" />
            <RadioButton
                android:id="@+id/radio6"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="SrcOut" />
            <RadioButton
                android:id="@+id/radio7"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="DstOut" />
            <RadioButton
                android:id="@+id/radio8"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="SrcATop" />
            <RadioButton
                android:id="@+id/radio9"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="DstATop" />
            <RadioButton
                android:id="@+id/radio10"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Xor" />
            <RadioButton
                android:id="@+id/radio11"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Darken" />
            <RadioButton
                android:id="@+id/radio12"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Lighten" />
            <RadioButton
                android:id="@+id/radio13"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Multiply" />
            <RadioButton
                android:id="@+id/radio14"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Screen" />
            <RadioButton
                android:id="@+id/radio15"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Clear" />
        </RadioGroup>
        </ScrollView>
    </RelativeLayout>

    MainActivity.java

    public class MainActivity extends Activity{
        private int screenWidth=0;
        private int screenHeight=0;
        private PorterDuff.Mode porterduffmodel=null;
        private ImageView imageView=null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            //initial porter-duff mode is Src
            porterduffmodel=PorterDuff.Mode.SRC;
            
            Point point=new Point();
            getWindowManager().getDefaultDisplay().getSize(point);
            //width of screen
            screenWidth=point.x;
            //height of screen
            screenHeight=point.y;
            
            
            imageView= ((ImageView)findViewById(R.id.image));
            setXfermodeImage(imageView);
            
            //choose src
            findViewById(R.id.radio0).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.SRC;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose Dst
            findViewById(R.id.radio1).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.DST;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose SrcOver
            findViewById(R.id.radio2).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.SRC_OVER;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose DstOver
            findViewById(R.id.radio3).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.DST_OVER;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose SrcIn
            findViewById(R.id.radio4).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.SRC_IN;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose DstIn
            findViewById(R.id.radio5).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.DST_IN;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose SrcOut
            findViewById(R.id.radio6).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.SRC_OUT;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose DstOut
            findViewById(R.id.radio7).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.DST_OUT;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose SrcATop
            findViewById(R.id.radio8).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.SRC_ATOP;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose DstATop
            findViewById(R.id.radio9).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.DST_ATOP;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose Xor
            findViewById(R.id.radio10).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.XOR;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose Darken
            findViewById(R.id.radio11).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.DARKEN;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose lighten
            findViewById(R.id.radio12).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.LIGHTEN;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose multiply
            findViewById(R.id.radio13).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.MULTIPLY;
                    setXfermodeImage(imageView);
                }
            });
            
            //choose screen
            findViewById(R.id.radio14).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.SCREEN;
                    setXfermodeImage(imageView);
                }
            });
            
            //clear
            findViewById(R.id.radio15).setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View arg0) {
                    porterduffmodel=PorterDuff.Mode.CLEAR;
                    setXfermodeImage(imageView);
                }
            });
        }
        
        private void setXfermodeImage(ImageView imageView){
            //open hardware accelerate rendering, it's software rendering default.
            imageView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
            
            Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888);
            Canvas separate_canvas=new Canvas(separate_bitmap);
            
            
            Bitmap destination_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888);
            //create destination canvas
            Canvas destination_canvas=new Canvas(destination_bitmap);
            Paint destination_paint=new Paint();
            destination_paint.setColor(Color.YELLOW);
            destination_paint.setStyle(Style.FILL);
            //drawing destination circle to destination canvas
            destination_canvas.drawCircle(screenWidth/2,screenHeight/4,screenHeight/8,destination_paint);
            
            
            Bitmap source_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888);
            //create source canvas
            Canvas source_canvas=new Canvas(source_bitmap);
            Paint source_paint=new Paint();
            source_paint.setColor(Color.BLUE);
            source_paint.setStyle(Style.FILL);
            //drawing source rectangle to source canvas
            source_canvas.drawRect(screenWidth/8, screenHeight/4, screenWidth/2, 7*screenHeight/16, source_paint);
            
            
            Paint separate_paint=new Paint();
            separate_canvas.drawBitmap(destination_bitmap, 0, 0, separate_paint);
            separate_paint.setXfermode(new PorterDuffXfermode(porterduffmodel));
            separate_canvas.drawBitmap(source_bitmap, 0, 0,separate_paint);
            
            //set separate_bitmap to target view
            imageView.setImageBitmap(separate_bitmap);
            
            //retrieve layer type to software layer
            imageView.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
        }
    }

    效果图:


    利用这些功能,我们可以轻易的完成一些较为复杂的功能。


    接下来笔者实现一个绘制圆形头像的功能,DstIn只会绘制目标图形中与源图像相交的部分。因此我们只需要设定源图像为圆圈,目标图像设置为图片,然后再使用DstIn便可以完成显示圆头像的功能了。

    public class MainActivity extends Activity {
        int screenWidth=0;
        int screenHeight=0;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Point point=new Point();
            getWindowManager().getDefaultDisplay().getSize(point);
            //initialize screen width
            screenWidth=point.x;
            //initialize screen height
            screenHeight=point.y;
            
            setContentView(new MyView(this));
        }
        class MyView extends View{
            Bitmap destination_bitmap=null;
            public MyView(Context context){
                super(context);
                destination_bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.scene);
            }
            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);            
                
                //create an empty separate_bitmap and a separate_canvas
                Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888);
                Canvas separate_canvas=new Canvas(separate_bitmap);
                
                
                //create an empty source canvas
                Bitmap source_bitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888);
                Canvas source_canvas=new Canvas(source_bitmap);
                //drawing a circle on source_bitmap
                Paint source_paint=new Paint();
                source_paint.setStyle(Style.FILL);
                source_canvas.drawCircle(screenWidth/2,screenHeight/4,200,source_paint);
                
                
                Paint paint=new Paint();
                //drawing destination_bitmap to separate_canvas
                separate_canvas.drawBitmap(destination_bitmap, 0, 0, paint);
                //set porter-duff mode is DST_IN
                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
                //drawing source_bitmap to separate_canvas
                separate_canvas.drawBitmap(source_bitmap, 0, 0, paint);
                            
                //drawing separate_bitmap to screen canvas
                canvas.drawBitmap(separate_bitmap, 0, 0, new Paint());
                
            }
        }
    }

    效果图:


    最后笔者简单展示一下,如何利用DST_OUT模式,实现刮刮乐的功能。

    public class MainActivity extends Activity {
        int screenWidth=0;
        int screenHeight=0;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Point point=new Point();
            getWindowManager().getDefaultDisplay().getSize(point);
            //screen width
            screenWidth=point.x;
            //screen height
            screenHeight=point.y;
            
            setContentView(new MyView(this));
        }
        class MyView extends View{
            private Bitmap resultBitmap=null;
            private Bitmap showBitmap=null;
            private Bitmap tempBitmap=null;
            private Canvas tempCanvas=null;
            private float start_x = 0;
            private float start_y = 0;
             private float end_x=0;
             private float end_y=0;
             private Paint paint=null;
             private Path path=null;
            public MyView(Context context){
                super(context);
                //create result bitmap with R.drawable.result which representing the answer.
                resultBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.result);
                //create show bitmap with R.drawable.show which used to cover answer.
                showBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.show);
                
                tempBitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888);
                tempCanvas=new Canvas(tempBitmap);
                tempCanvas.drawBitmap(showBitmap, 0, 0, new Paint());
                
                path=new Path();
                paint=new Paint();
                paint.setStrokeWidth(20);
                paint.setStyle(Style.STROKE);
                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
                paint.setStrokeCap(Paint.Cap.ROUND);
            }
            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
                canvas.drawBitmap(resultBitmap, 0, 0,new Paint());
                
                canvas.drawBitmap(tempBitmap, 0, 0, new Paint());
            }
            
            @Override
            public boolean onTouchEvent(MotionEvent event) {
                if(event.getAction()==MotionEvent.ACTION_DOWN){
                    start_x=event.getX();
                    start_y=event.getY();
                    path.moveTo(start_x, start_y);//setting start point
                }else if(
                        event.getAction()==MotionEvent.ACTION_MOVE
                        ||
                        event.getAction()==MotionEvent.ACTION_UP){
                    end_x=event.getX();
                    end_y=event.getY();
                    path.lineTo(end_x,end_y);
                    tempCanvas.drawPath(path,paint);
                    start_x=end_x;
                    start_y=end_y;
                    
                    postInvalidate();
                }
                return true;
            }
        }
    }

    效果图:


    这篇Blog并未详细的讲解每种模式的详细合成模式过程,关于这些读者可以到Google官方文档查阅。最后,希望这篇可以对你所有帮助。

  • 相关阅读:
    B-树和B+树
    线程与内核对象的同步-2
    线程与内核对象的同步
    高级线程同步 临界区
    Levenshtein Distance (编辑距离) 算法详解
    平衡二叉树
    静态查找表
    C++中的容器类详解
    How do I list all tables/indices contained in an SQLite database
    SmartGit STUDY 2
  • 原文地址:https://www.cnblogs.com/HDK2016/p/9733560.html
Copyright © 2020-2023  润新知