如何在Bitmap截取任意形状
来源:互联网 发布:ios6.1.3可用软件 编辑:程序博客网 时间:2024/04/30 13:10
现在许多截屏应用中都实现了任意形状截图,我一开始有些疑惑:到底是如何判断一个像素点是在曲线内部还是外部的呢,因为多边形是否包含点的判断还是比较复杂的,计算起来复杂度可不低,后来看了一些资料,发现完全不是我想的那么复杂,很简单就能实现。多简单呢,往下看。
先看最终效果:
以全屏截屏并裁剪出任意形状的图形为例,除了在Android上如何实现矩形区域截屏中截屏的操作以外,还需要额外实现两个部分:
1. 根据用户的操作,绘制出选择的曲线图形;
2. 根据这个图形截取图片。
第一步、根据用户的操作,绘制出选择的曲线图形
首先设计一个用于保存用户绘制图形的数据结构,如下:
public static class GraphicPath implements Parcelable { protected GraphicPath(Parcel in) { int size=in.readInt(); int[] x=new int[size]; int[] y=new int[size]; in.readIntArray(x); in.readIntArray(y); pathX=new ArrayList<>(); pathY=new ArrayList<>(); for (int i=0;i<x.length;i++){ pathX.add(x[i]); } for (int i=0;i<y.length;i++){ pathY.add(y[i]); } } public static final Creator<GraphicPath> CREATOR = new Creator<GraphicPath>() { @Override public GraphicPath createFromParcel(Parcel in) { return new GraphicPath(in); } @Override public GraphicPath[] newArray(int size) { return new GraphicPath[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(pathX.size()); dest.writeIntArray(getXArray()); dest.writeIntArray(getYArray()); } public List<Integer> pathX; public List<Integer> pathY; public GraphicPath(){ pathX=new ArrayList<>(); pathY=new ArrayList<>(); } private int[] getXArray(){ int[] x=new int[pathX.size()]; for (int i=0;i<x.length;i++){ x[i]=pathX.get(i); } return x; } private int[] getYArray(){ int[] y=new int[pathY.size()]; for (int i=0;i<y.length;i++){ y[i]=pathY.get(i); } return y; } public void addPath(int x,int y){ pathX.add(x); pathY.add(y); } public void clear(){ pathX.clear(); pathY.clear(); } public int getTop(){ int min=pathY.size()>0?pathY.get(0):0; for (int y:pathY){ if (y<min){ min=y; } } return min; } public int getLeft(){ int min=pathX.size()>0?pathX.get(0):0; for (int x:pathX){ if (x<min){ min=x; } } return min; } public int getBottom(){ int max=pathY.size()>0?pathY.get(0):0; for (int y:pathY){ if (y>max){ max=y; } } return max; } public int getRight(){ int max=pathX.size()>0?pathX.get(0):0; for (int x:pathX){ if (x>max){ max=x; } } return max; } public int size(){ return pathY.size(); }}
这里实现了Parcelable 接口,因为本来要考虑到通过Intent传递数据,后来发现没有这个必要了,但也没有改回来了,请不要在意。
在onTouchEvent中记录用户手指的拖动轨迹,并在onDraw中绘制出来,代码如下:
public boolean onTouchEvent(MotionEvent event) { if (!isEnabled()){ return false; } int x= (int) event.getX(); int y= (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isUp = false; downX = x; downY = y; isMoveMode = false; startX = (int) event.getX(); startY = (int) event.getY(); endX = startX; endY = startY; mGraphicPath.clear(); mGraphicPath.addPath(x,y); break; case MotionEvent.ACTION_MOVE: if (isButtonClicked) { break; } mGraphicPath.addPath(x,y); break; case MotionEvent.ACTION_UP: isUp = true; mGraphicPath.addPath(x,y); break; case MotionEvent.ACTION_CANCEL: isUp = true; break; } postInvalidate(); return true;}protected void onDraw(Canvas canvas) { int width = getWidth(); int height=getHeight(); //draw unmarked canvas.drawRect(0,0,width,height,unMarkPaint); if (isUp) { Path path = new Path(); if (mGraphicPath.size() > 1) { path.moveTo(mGraphicPath.pathX.get(0), mGraphicPath.pathY.get(0)); for (int i = 1; i < mGraphicPath.size(); i++) { path.lineTo(mGraphicPath.pathX.get(i), mGraphicPath.pathY.get(i)); } } else { return; } canvas.drawPath(path, markPaint); }else { if (mGraphicPath.size() > 1) { for (int i = 1; i < mGraphicPath.size(); i++) { canvas.drawLine(mGraphicPath.pathX.get(i-1), mGraphicPath.pathY.get(i-1),mGraphicPath.pathX.get(i), mGraphicPath.pathY.get(i),markPaint); } } }}
其中值得注意的是markPaint这个画笔,其设置如下,它的功能是在半透明的背景上,把选中的区域的背景色去除掉(设置成PorterDuff.Mode.CLEAR):
markPaint=new Paint();markPaint.setColor(markedColor);markPaint.setStyle(Paint.Style.FILL_AND_STROKE);markPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));markPaint.setColor(markedColor);markPaint.setStrokeWidth(strokeWidth);markPaint.setAntiAlias(true);
还要注意在onDraw中,使用isUp来标识是拖动过程中还是拖动完成,这两部分的绘制方式有点区别:拖动过程中绘制的是手指划动的曲线,所以使用drawLine就行了;而拖动完成以后,需要根据划动的路径绘制成封闭图形,所以使用Path进行绘制。
第二步、根据曲线图形截取图片
就像本文开头就说到的,如果要计算一个曲线图形内包含的每个像素,再去bitmap中去拿对应的像素,计算量就会比较大了,感兴趣的朋友可以看看知乎:一个线条自交叉的封闭图形,怎样判断一个点位于图形内部还是外部?
好在系统已经给我们提供了更简单的方法,原理是:
1. 创建一张空的bitmap
2. 在这张bitmap中进行绘制出曲线图形
3. 以PorterDuff.Mode.SRC_IN的方式,再在这个bitmap上把需要截取的图片绘制一次,这时候这张bitmap就是你需要的结果。关于PorterDuff.Mode.SRC_IN的含义,可以看这篇PorterDuff.Mode
代码实现如下:
mRect=new Rect(mGraphicPath.getLeft(),mGraphicPath.getTop(),mGraphicPath.getRight(),mGraphicPath.getBottom());if (mRect.left < 0) mRect.left = 0;if (mRect.right < 0) mRect.right = 0;if (mRect.top < 0) mRect.top = 0;if (mRect.bottom < 0) mRect.bottom = 0;int cut_width = Math.abs(mRect.left - mRect.right);int cut_height = Math.abs(mRect.top - mRect.bottom);if (cut_width > 0 && cut_height > 0) { Bitmap cutBitmap = Bitmap.createBitmap(bitmap, mRect.left, mRect.top, cut_width, cut_height); LogUtil.d(TAG, "bitmap cuted second"); //上面是将全屏截图的结果先裁剪成需要的大小,下面是裁剪成曲线图形区域 Paint paint = new Paint(); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL_AND_STROKE); paint.setColor(Color.WHITE); Bitmap temp = Bitmap.createBitmap(cut_width, cut_height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(temp); Path path = new Path(); if (mGraphicPath.size() > 1) { path.moveTo((float) ((mGraphicPath.pathX.get(0)-mRect.left)), (float) ((mGraphicPath.pathY.get(0)- mRect.top))); for (int i = 1; i < mGraphicPath.size(); i++) { path.lineTo((float) ((mGraphicPath.pathX.get(i)-mRect.left)), (float) ((mGraphicPath.pathY.get(i)- mRect.top))); } } else { return; } canvas.drawPath(path, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); // 关键代码,关于Xfermode和SRC_IN请自行查阅 canvas.drawBitmap(cutBitmap, 0 , 0, paint); LogUtil.d(TAG, "bitmap cuted third"); saveCutBitmap(temp);}
其中bitmap对象,是全屏截屏的结果,可以参考Android上如何实现矩形区域截屏。
完整代码可以参考Bigbang项目的MarkSizeView和ScreenCapture中的startCapture方法。
相关文章:
Android上如何实现矩形区域截屏
Android如何判断NavigationBar是否显示(获取屏幕真实的高度)
- 如何在Bitmap截取任意形状
- 如何在PS中画任意形状虚线
- OpenCV截取图像ROI,任意形状
- opencv3.0 截取任意形状封闭图形
- C#截取图像中的任意部位任意形状
- PS如何将图片截取自己想要的形状
- 任意形状图像、图形
- opencv任意形状ROI
- opencv任意形状ROI
- roi任意形状
- 如何在tableau里自定义形状
- OpenCV截取图像的任意形状区域,规则的图形(圆、椭圆、矩形),不规则鼠标自己选择
- bat中for /f 如何截取任意行
- LoadRunner如何使用lr_save_var截取任意字符串长度
- LoadRunner如何使用lr_save_var截取任意字符串长度
- LoadRunner如何使用lr_save_var截取任意字符串长度
- 实现任意形状连接关系
- 把UIView切成任意形状
- Android上如何实现矩形区域截屏
- 奇人有奇书(李渔、张岱、陈继儒、吴敬梓)
- 高清银行LOGO图片整合分享
- javascript—三元操作符
- win7配置JDK环境变量
- 如何在Bitmap截取任意形状
- 当我有 6 百万时,就发布 Linux 5.0
- magento 相关xml功能的介绍
- sign符号的含义
- Unity 物体根据鼠标移动而转动(可用于物体的360度展示)
- Eclipse部署Maven web项目到tomcat服务器时,没有将lib下的jar复制过去的解决办法
- CentOS7关闭防火墙和selinux
- 设备驱动中的iomem(kernel-4.7)
- spark pairRDD基本操作(三)——附带wordcount程序