作者:雨后彩虹fen | 来源:互联网 | 2024-12-08 18:47
当前,许多屏幕截图应用程序支持任意形状的截图功能。这引发了一个技术问题:如何高效地判断一个像素点是否位于指定的曲线或形状内部?本文将深入探讨这一问题,并提供一种简洁有效的解决方案。
如今,许多屏幕截图应用程序不仅限于矩形区域的截图,还支持用户自定义任意形状的截图。这背后的技术实现涉及到如何判断一个像素点是否位于特定的曲线或形状内部。虽然多边形包含点的判断可能较为复杂,但实际上,实现这一功能并不如想象中困难。
最终效果展示
为了实现全屏截图并裁剪出任意形状,除了基本的截屏操作外,还需完成以下两个步骤:
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 CREATOR = new Creator() { @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 pathX; public List pathY; public GraphicPath() { pathX = new ArrayList<>(); pathY = new ArrayList<>(); } private int[] getXArray() { int[] x = new int[pathX.size()]; for (int i = 0; i 0 ? pathY.get(0) : 0; for (int y : pathY) { if (y 0 ? pathX.get(0) : 0; for (int x : pathX) { if (x 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(); } }
在触摸事件处理中记录用户的操作,并在绘制时显示这些路径:
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(); 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 1) { for (int i = 1; i
特别需要注意的是,`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.setStrokeWidth(strokeWidth); markPaint.setAntiAlias(true);
在 `onDraw` 方法中,`isUp` 变量用于区分拖动过程和拖动结束,两者的绘制方式有所不同:拖动过程中绘制的是手指划过的路径,使用 `drawLine`;拖动结束后则需绘制封闭图形,使用 `Path`。
根据曲线图形裁剪图片
直接计算曲线内每个像素点的位置并从原始 `Bitmap` 中提取对应像素,计算量较大。幸运的是,Android 系统提供了一种更简便的方法:
1. 创建一个新的空 `Bitmap`。
2. 在该 `Bitmap` 上绘制曲线图形。
3. 使用 `PorterDuff.Mode.SRC_IN` 模式,将原始图像绘制到新的 `Bitmap` 上,最终结果即为所需。
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
其中,`bitmap` 对象是全屏截图的结果,具体实现可以参考如何在 Android 上实现矩形区域截屏。
参考资料
完整代码可以参考 Bigbang 项目的 MarkSizeView 和 ScreenCapture 类中的 startCapture 方法。
相关文章:
- Android 上如何实现矩形区域截屏
- Android 如何判断 NavigationBar 是否显示(获取屏幕真实高度)