android图片涂鸦,具有设置画笔,撤销,缩放移动等功能(二)

2024-08-23 03:58

本文主要是介绍android图片涂鸦,具有设置画笔,撤销,缩放移动等功能(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

该项目的代码已经重构,采用了新框架,强烈建议查看《Android全新图片涂鸦框架Doodle——多功能、可自定义、可扩展》

(旧代码暂时不维护了,所以推荐还是使用最新框架吧!也希望大伙支持!!!)

点击移步到最新涂鸦框架>>>Doodle

点击移步到最新涂鸦框架>>>Doodle

点击移步到最新涂鸦框架>>>Doodle​​​​​​​​​​​​​​

————————————————————————————————————————————————

 

上一篇文章《android图片涂鸦,具有设置画笔,撤销,缩放移动等功能(一)》,讲了涂鸦的主要功能,还没看过的同学建议去看一遍,方便接下来的分析。现在,为大家讲解涂鸦功能的实现原理。

 


  • 放缩与移动

 

当我们进入涂鸦界面后,发现图片刚好适应屏幕居中,这是因为进行了处理,下面三个变量记录此时图片的位置信息,当确定这三个值后,不会再改变,除非显示区域大小发生变化。

 

    private float mPrivateScale; // 图片适应屏幕时的缩放倍数private int mPrivateHeight, mPrivateWidth;// 图片适应屏幕时的大小(View窗口坐标系上的大小)private float mCentreTranX, mCentreTranY;// 图片在适应屏幕时,位于居中位置的偏移(View窗口坐标系上的偏移)

 


之后我们对图片进行的缩放与移动操作,都是相对于这个状态下的图片,位置信息记录下面两个变量中。

 

    private float mScale = 1; // 在适应屏幕时的缩放基础上的缩放倍数 ( 图片真实的缩放倍数为 mPrivateScale*mScale )private float mTransX = 0, mTransY = 0; // 图片在适应屏幕且处于居中位置的基础上的偏移量( 图片真实偏移量为mCentreTranX + mTransX,View窗口坐标系上的偏移)

 

 

 

会发现,当缩放移动图片时,相应的涂鸦操作也会被缩放移动,这里是通过缩放移动GraffitiVIew的画布,来实现整体的变化,而不用分别单独对图片和涂鸦进行变化操作。

 

    private void doDraw(Canvas canvas) {float left = mCentreTranX + mTransX;float top = mCentreTranY + mTransY;// 画布和图片共用一个坐标系,只需要处理屏幕坐标系到图片(画布)坐标系的映射关系canvas.translate(left, top); // 偏移画布canvas.scale(mPrivateScale * mScale, mPrivateScale * mScale); // 缩放画布...}

 

 

(不熟悉矩阵变换的同学可以查看我的另一篇文章《浅谈矩阵变换——Matrix》)

 

经过多次优化后,这里选择画布和图片共用一个坐标系,了解图片的位置信息后,最后需要处理的就是,屏幕坐标系与图片(画布)坐标系的映射,即把屏幕上滑动的轨迹投射到图片中。当手指在屏幕上滑动做绘图操作,此时看到的绘图轨迹其实是画在GraffitiView的画布上的,所以能看到画笔的实时变化,而图片上暂未被改动,即还未真正在图片上涂鸦。当结束此次绘图(松开手指)时,才把真实的绘图操作画在图片上。

从上图的分析中,我们可以得出如下映射关系:

图片坐标x=(屏幕坐标x-图片在x轴上的偏移量)/图片缩放倍数

图片坐标y=(屏幕坐标y-图片在y轴上的偏移量)/图片缩放倍数

 

/**
* 将屏幕触摸坐标x转换成在图片中的坐标
*/
public final float toX(float touchX) {return (touchX - mCentreTranX - mTransX) / (mPrivateScale * mScale);
}
/**
* 将屏幕触摸坐标y转换成在图片中的坐标
*/
public final float toY(float touchY) {return (touchY - mCentreTranY - mTransY) / (mPrivateScale * mScale);
}


可见,屏幕坐标投射到图片上时,需要减去偏移量,因为图片的位置是一直不变的,我们对图片进行偏移,其实是对GraffitView的画布进行偏移。

 

 

  • 设置画笔及形状

通过Paint类充当画笔,根据不同选择来设置画笔属性,当画笔的底色为颜色值时,通过Paint.setColor(int)设置颜色,当画笔的底色为一张图片时,则通过Paint.setShader(BitmapShader)把图片设置为画笔底色。代码中我定义了一个类GraffitiColor表示画笔底色,定义如下:

public static class GraffitiColor {// 底色类型public enum Type {COLOR, // 颜色值BITMAP // 图片}private int mColor; // 颜色值private Bitmap mBitmap; // 图片private Type mType; private Shader.TileMode mTileX = Shader.TileMode.MIRROR;private Shader.TileMode mTileY = Shader.TileMode.MIRROR;  // 镜像
}


同理,当画笔为仿制(COPY)和橡皮擦(ERASER)时,通过Paint.setShader(BitmapShader)直接把画笔底色设置为当前涂鸦的图片,即可实现擦除的效果,只不过当为仿制时,需要对图片进行偏移处理,相关信息记录在CopyLocation类中:

private class CopyLocation {private float mCopyStartX, mCopyStartY; // 仿制的坐标private float mTouchStartX, mTouchStartY; // 开始触摸的坐标private float mX, mY; // 当前位置private Paint mPaint;private boolean isRelocating = true; // 正在定位中private boolean isCopying = false; // 正在仿制绘图中
}


每当偏移位置发生改变时,重新设置需要仿制的图片的偏移位置,可通过下面的方法设置:

ShaderMatrix.postTranslate(float, float);
BitmapShader.setLocalMatrix(ShaderMatrix);

 

 

由上图的分析,绿色虚线为仿制图片的偏移量,我们可以得出图片X轴上偏移的位置(图中的黑色虚线)为

CopyLocation.mTouchStartX - CopyLocation.mCopyStartX ,

 

Y轴上偏移的位置(图中的黑色虚线长度)为

CopyLocation.mTouchStartY - CopyLocation.mCopyStartY 。

 

 

每一次的涂鸦操作(从手指点击到抬起)都会记录在GraffitiPath类中:

private static class GraffitiPath {Pen mPen; // 画笔类型Shape mShape; // 画笔形状float mStrokeWidth; // 大小GraffitiColor mColor; // 颜色Path mPath; // 画笔的路径float mSx, mSy; // 映射后的起始坐标,(手指点击)float mDx, mDy; // 映射后的终止坐标,(手指抬起)Matrix mMatrix; // 仿制图片的偏移矩阵
}

 

  • 撤销及清屏

上面说到了GraffitiPath的作用,因此我们可以通过该类还原出每一次的涂鸦操作,我们把操作记录在集合CopyOnWriteArrayList<GraffitiPath>中,通过删除集合的最后一个元素即可实现撤销上一步操作的功能,同理,清除集合内的元素即可实现清屏,当然,删除后需要在原始图片上按次序还原集合中所有操作。

 

/*** 撤销
*/
public void undo() {if (mPathStack.size() > 0) {mPathStack.remove(mPathStack.size() - 1);initCanvas();draw(mBitmapCanvas, mPathStack, false);invalidate();}
}

 

 

最新源码在Github上的地址为:https://github.com/1993hzw/Graffiti , 欢迎大家反馈问题,我会及时在上面更新代码,谢谢支持。

建议大家先阅读v3.0的代码,因为3.0之后加入了旋转等功能,原理变得更为复杂。v3.0的代码:

https://github.com/1993hzw/Graffiti/tree/v3.0

这篇关于android图片涂鸦,具有设置画笔,撤销,缩放移动等功能(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1098290

相关文章

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

python设置环境变量路径实现过程

《python设置环境变量路径实现过程》本文介绍设置Python路径的多种方法:临时设置(Windows用`set`,Linux/macOS用`export`)、永久设置(系统属性或shell配置文件... 目录设置python路径的方法临时设置环境变量(适用于当前会话)永久设置环境变量(Windows系统

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

小白也能轻松上手! 路由器设置优化指南

《小白也能轻松上手!路由器设置优化指南》在日常生活中,我们常常会遇到WiFi网速慢的问题,这主要受到三个方面的影响,首要原因是WiFi产品的配置优化不合理,其次是硬件性能的不足,以及宽带线路本身的质... 在数字化时代,网络已成为生活必需品,追剧、游戏、办公、学习都离不开稳定高速的网络。但很多人面对新路由器

Java实现预览与打印功能详解

《Java实现预览与打印功能详解》在Java中,打印功能主要依赖java.awt.print包,该包提供了与打印相关的一些关键类,比如PrinterJob和PageFormat,它们构成... 目录Java 打印系统概述打印预览与设置使用 PageFormat 和 PrinterJob 类设置页面格式与纸张

MySQL 8 中的一个强大功能 JSON_TABLE示例详解

《MySQL8中的一个强大功能JSON_TABLE示例详解》JSON_TABLE是MySQL8中引入的一个强大功能,它允许用户将JSON数据转换为关系表格式,从而可以更方便地在SQL查询中处理J... 目录基本语法示例示例查询解释应用场景不适用场景1. ‌jsON 数据结构过于复杂或动态变化‌2. ‌性能要

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过