热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

实现Bitmap任意形状截图的技术解析

当前,许多屏幕截图应用程序支持任意形状的截图功能。这引发了一个技术问题:如何高效地判断一个像素点是否位于指定的曲线或形状内部?本文将深入探讨这一问题,并提供一种简洁有效的解决方案。

如今,许多屏幕截图应用程序不仅限于矩形区域的截图,还支持用户自定义任意形状的截图。这背后的技术实现涉及到如何判断一个像素点是否位于特定的曲线或形状内部。虽然多边形包含点的判断可能较为复杂,但实际上,实现这一功能并不如想象中困难。

最终效果展示

曲线截图效果

为了实现全屏截图并裁剪出任意形状,除了基本的截屏操作外,还需完成以下两个步骤:
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 是否显示(获取屏幕真实高度)


推荐阅读
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 本文基于刘洪波老师的《英文词根词缀精讲》,深入探讨了多个重要词根词缀的起源及其相关词汇,帮助读者更好地理解和记忆英语单词。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 本文详细介绍了如何使用 Yii2 的 GridView 组件在列表页面实现数据的直接编辑功能。通过具体的代码示例和步骤,帮助开发者快速掌握这一实用技巧。 ... [详细]
  • 使用 Azure Service Principal 和 Microsoft Graph API 获取 AAD 用户列表
    本文介绍了一段通用代码示例,该代码不仅能够操作 Azure Active Directory (AAD),还可以通过 Azure Service Principal 的授权访问和管理 Azure 订阅资源。Azure 的架构可以分为两个层级:AAD 和 Subscription。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文详细记录了在基于Debian的Deepin 20操作系统上安装MySQL 5.7的具体步骤,包括软件包的选择、依赖项的处理及远程访问权限的配置。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 本文深入探讨 MyBatis 中动态 SQL 的使用方法,包括 if/where、trim 自定义字符串截取规则、choose 分支选择、封装查询和修改条件的 where/set 标签、批量处理的 foreach 标签以及内置参数和 bind 的用法。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
author-avatar
雨后彩虹fen
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有