Android 裸眼凸面镜效果实现

前言

最近我们实现的几篇文章主要聚焦在图片分片处理和局部处理,以及3D运动,主要涉及的是贴图技巧和3D立体顶点绘制,其实这个围绕的是一个主线,给3D物体贴图。Canvas 作为2D坐标系绘制工具来绘制3D,其实有大量的工作要做的,常见的3D图形工具open gl es显然好用的多,但是Canvas 也有他自身的好处就是学习门槛低,另外也可以很快速转化为View布局,因此有他本身存在的价值。

看本篇之前也可以看看其他文章,当然不看也不影响看本篇。

本篇的主题是绘制凸镜效果,本篇会用到一个难度较高的Canvas api来绘制。

至于凹镜,没找到合适的方法,包括找了chat-gpt协助,它给的代码比想象的差的多。所以暂时不要慌,人类目前还是领先的。对于写代码这块儿,chat-gpt不仅能处理好上下文问题,还能连续追问,但是给的答案你会发现,越追问结果的自信度越低,因为代码难度也是提升的,这很符合人类思考逻辑,简单的事不需要记忆就能给出,困难的事还得复盘推理,不过话说回来,他的自信度降低的结果依然很具有参考和学习价值,至少可以做些启发性思考,然后去拓展。

当然,本篇的凸镜效果也和chatgpt关系不大,所以aigc和人工智能的路还很长。

凸镜预览

凸镜效果图2

原图1

凸镜效果图2

原图2:

目前效果比较好的是尺寸正方形的图片,比如上面的图基本都是接近720P的正方形,第一张原图稍微差点,为什么这么说呢?

在测试过程中发现,正方形的效果在使用Shader+裁剪之后,锯齿就不明显了,但是非正方形图片就会有裁剪不到的地方,不过也可以优化算法,在正方形区域拉伸,其他区域不动。

原理

学过英文的都知道,英语中对事物的区分有时候是特别细致入微的,比如中文的网可以代表多种意思,但是在英语中有net、network、web等,这也是英文作为交流工具的的优势之一,不过也会产生一些近义词,会变成学习者的负担,但比起同字不同意的语言,显然要简单的多。而我们今天也是使用网格工具。说到网格,大家肯定知道Grid,但很少人知道mesh,可能做过后端部署的人知道mesh,其实这两者也是有差异的,Grid属于固定不可调整的网格,而mesh属于可以拉伸的和调整的网格,所以我们用到的工具事drawBitmapMesh。

drawBitmapMesh 怎么用,能做什么

我们知道贝塞尔曲线,其通过控制点,要求两点之间的连线同时到达终点,最终可以绘制出更多矢量物体,计算机中大量的字体是通过贝塞尔控制点来实现。而Mesh也是通过网格控制点(注意不是像素点)拉伸图形,使得图形变形,和贝塞尔的区别是,Mesh是网格点,仅能作用于图片,且无法矢量化。

drawBitmapMesh 主要用在图像特殊效果绘制,比如波浪、窗帘、甚至有人用作翻书特效,甚至似乎也可以用来作物理仿真,不过涉及算法太复杂,当然是比不上UE或者Unity这些3D软件。

凸镜实现

凸镜实现其实很简单,只需要将图片两侧的x控制点往图片中线位置拉伸,但是拉伸的幅度随着纵向靠近中心点反而减小,当然Y轴也可以,不过这样人像会显得很扁平。在人类视觉中,图片纵向拉伸的可接受度是高于横向拉伸的,这也是为什么你看不惯胖子但喜欢大长腿的原因,所以均衡饮食和有效锻炼还是很重要的。

我们这次其实也是用了和老祖宗类似的方法,就是平分直径,每纵向走一步,那么拉伸的可分配范围是用勾股定理就可算出来

horizontal = sqrt(R * R - (R - offset) * (R-offset)) * 2;

R- 大圆半径 sqrt - 平方根函数

为什么✖️2,主要还是你要理解,平分的范围是不是圆的,下图橘色线条是平分的线段,肯定需要x2

接下来进行平分橘色线条长度

(x* (horizontal / WIDTH))

方法总结

网格Y轴保持不变,x轴拉伸 尽可能选正方形图片 x轴网格平分长度缩小范围长度计算公式 sqrt(R * R - (R - offset) * (R-offset)) * 2 x轴 平分公式

坑点

平分完horizontal你会看到一半的图片,原因是你从x=0的位置偏移的,导致图片整体向左移动了,所以你需要将图片,这一步也是至关重要的一步。

scss 复制代码
float fx  = (float) ((bitmapWidth - horizontal)/2 + (x* (horizontal / WIDTH))); // 偏移

具体实现

初始化

java 复制代码
//横向、纵向划分格数:50*50
private static final int WIDTH = 50;
private static final int HEIGHT = 50;
//顶点数:81*81
private final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
//顶点坐标数组
private final float[] orig = new float[COUNT * 2];
//转换后顶点坐标数组
private final float[] verts = new float[COUNT * 2];

绘制

在这一步我们使用了Shader,主要原因是拉伸图片也会产生轻微的锯齿,为了避免锯齿,需要对圆做下裁剪,减除锯齿区域

绘制到Bitmap上

注意,自己创建的Canvas 默认是软件绘制,因此一定要小心不当的操作,比如saveLayer导致画不出来,setLayerType对其无作用。

java 复制代码
mShaderBitmap.eraseColor(Color.TRANSPARENT);

float radius = Math.min(mBitmap.getWidth(), mBitmap.getHeight()) / 2f;
int bitmapWidth = mBitmap.getWidth();
int bitmapHeight = mBitmap.getHeight();
int index = 0;
for (int y = 0; y <= HEIGHT; y++) {
    float fy = y * (1f * bitmapHeight  / HEIGHT);
    float step = Math.abs(radius - fy);
    double horizontal = Math.sqrt(radius * radius - step * step) * 2;  //直径
    for (int x = 0; x <= WIDTH; x++) {
        float fx  = (float) ((bitmapWidth - horizontal)/2 + (x* (horizontal / WIDTH))); // 偏移
        verts[index * 2 + 0] = fx;
        verts[index * 2 + 1] = fy;
        index++;
    }
}
bitmapCanvas.drawBitmapMesh(mBitmap,WIDTH,HEIGHT,verts,0,null,0,mCommonPaint);

二次编辑并绘制到Canvas上

上面讲过会有锯齿,因此这里剪掉锯齿,裁掉1-2个像素一般足以,除非你的图片不是正方形。

scss 复制代码
 int save = canvas.save();
        //下面注释掉的代码会有轻微锯齿
//        matrix.setTranslate((getWidth() - mShaderBitmap.getWidth())/2f,(getHeight() - mShaderBitmap.getHeight())/2f);
//        canvas.drawBitmap(mShaderBitmap,matrix,mCommonPaint);

//将图片平移到中心位置
        matrix.setTranslate((getWidth() - mShaderBitmap.getWidth())/2f,(getHeight() - mShaderBitmap.getHeight())/2f);
        
        BitmapShader bitmapShader = new BitmapShader(mShaderBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        //移动Shader,具体用法参考放文章开头说的大镜效果
        bitmapShader.setLocalMatrix(matrix);
        
        mCommonPaint.setShader(bitmapShader);
        canvas.drawCircle(getWidth()/2f,getHeight()/2f,radius-2,mCommonPaint);
        mCommonPaint.setShader(null);

        canvas.restoreToCount(save);

另外我们也要注意这里对Shader进行了平移

java 复制代码
//将图片平移到中心位置
        matrix.setTranslate((getWidth() - mShaderBitmap.getWidth())/2f,(getHeight() - mShaderBitmap.getHeight())/2f);
//将图片平移到中心位置
        matrix.setTranslate((getWidth() - mShaderBitmap.getWidth())/2f,(getHeight() - mShaderBitmap.getHeight())/2f);
        BitmapShader bitmapShader = new BitmapShader(mShaderBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        //移动Shader,具体用法参考放文章开头说的大镜效果
        bitmapShader.setLocalMatrix(matrix);

总结

本篇到这里基本就结束了,drawBitmapMesh功能很强大,但是需要很强的算法基础,主要涉及三角函数等几何算法,那么如果实现山丘、海浪、银河系旋涡也有可能呢,这些仍然需要算法支持和不断的探索。

相关推荐
林开落L3 分钟前
前缀和算法习题篇(上)
c++·算法·leetcode
远望清一色3 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
tyler_download5 分钟前
手撸 chatgpt 大模型:简述 LLM 的架构,算法和训练流程
算法·chatgpt
SoraLuna25 分钟前
「Mac玩转仓颉内测版7」入门篇7 - Cangjie控制结构(下)
算法·macos·动态规划·cangjie
我狠狠地刷刷刷刷刷28 分钟前
中文分词模拟器
开发语言·python·算法
鸽鸽程序猿29 分钟前
【算法】【优选算法】前缀和(上)
java·算法·前缀和
九圣残炎35 分钟前
【从零开始的LeetCode-算法】2559. 统计范围内的元音字符串数
java·算法·leetcode
还是大剑师兰特36 分钟前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解36 分钟前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~42 分钟前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf