Unity自带的Image组件基本可以满足大多显示固定尺寸图片的需求,但是对于一些提前不知道尺寸以及宽高比例,同时显示空间又固定的场景,比如一个图片列表,其中有些可能来自网络、也可能来自用户从本地上传,有着大大小小的尺寸和比例,填入固定大小的Image组件时,便会图片变形、或是填不满item空间,又或是使用Mask遮罩裁剪额外增加DC。
1. 简言
既然展示的空间固定,那么就需要调整展示图片的区域。刚拿到这个需求时,最直接的想法就是通过写个Shader,通过控制控制Material的参数来控制绘制哪些像素,后来看到Image的源码中onPopulateMesh
方法时,发现通过重写这个方法更合适,可以在代码里直接控制需要把图片的哪个区域绘制到Image组件的哪个区域上。
举个例子,比如Image组件设定的大小是100x100像素,而图片的大小是200x300像素,需求是不对图片进行任何缩放,能展示多少内容就展示多少内容,那么最终就是将图片中心区域100x100的像素,绘制到Image组件上。
当需求变成必须填满Image组件,但图片不能变形,这个时候就需要按照Image组件的比例从图片中截取同样比例的最大区域的像素,也就是选取图片中心区域200x200的像素,绘制到Image组件上。
借鉴Android中ImageView,我设计了7种不同的缩放模式,分别是:
- Center
- CenterInside
- CenterCrop
- FitXY
- FitCenter
- FitStart
- FitEnd
接下来一一介绍。
2. 缩放模式:ScaleType
下面说明中所用到的图片尺寸是:220x229
2.1 Center
这种模式下,不对图片进行任何缩放,Image能展示多少便展示多少,示例图:
如图所示,假定了3种情况,当Image比图片大,图片在Image中间显示,白色边框是Image的尺寸;尺寸相同时,刚好可以完整显示;当Image小于图片尺寸时,则只显示图片中心同等大小区域的像素。
2.2 CenterInside
与Center类似,Image能完全显示图片时候则不进行任何缩放,仅显示;当不能完全显示时,则会按照原试图比例缩放,使得至少有一个方向完全填满Image。
2.3 CenterCrop
这种模式下会将Image区域填满,如果图片的大小过大或者过小,则按照原试图比例缩放,直到将Image的两个方向都填满,超出的部分则裁剪。
2.4 FitXY
这种模式最好理解,直接用图片的所有像素填满Image区域,所以当两个比例不同时,会出现图片变形。
2.5 FitCenter
这种模式下,图片会以尽可能大的原始比例尺寸去显示,即当图片过大或者过小时,则按照原试图的比例进行缩放,直到至少填满Image的一个方向,如果比例相同则可以填满两个方向。乍一看和CenterInside有些像,二者的区别是,当可以完全显示时,CenterInside只是将图片放到Image中心而不进行缩放,而FitCenter会进行缩放,看示例图可以直观看出区别。
2.6 FitStart
与FitCenter类似,区别在于当图片不能填满Image区域时,会对齐Start方向,水平方向有空白区域时则对齐左侧,垂直方向有空白区域时,则对齐顶部。
2.7 FitEnd
看过FitCenter和FitStart,这个模式就很好理解了,当图片不能填满Image区域时,则对齐右侧或者底部。
3. 圆角
除了有多种可选的缩放模式,ScaleImage组件还实现了常见的圆角功能。常见的实现方式是用一张带圆角的图片,结合Mask遮罩来实现,这种方式可能会打断DrawCall合批,在网上搜索时,发现一种新思路:圆角处的扇形,也可以拆成多个三角形,当三角形的数量足够多时,就会无限接近扇形。两种方式在大多时候都没有太大区别,只是后者更灵活些。
3.1 思路
Image代码中的onPopulateMesh
方法,是通过VertexHelper
来添加顶点和三角形来绘制图片。通过4个顶点和2个三角形,便可以绘制一个矩形的图片:
所以,在明确了上一点之后,如果按照如下的方式拆分顶点和三角形,便可以实现类似圆角的效果:
3.2 效果
以上是不同数量的三角时,圆角呈现的效果,可以看出,当使用6个三角形时,棱角分明的感觉就很弱了,当三角形数量来到10时,视觉效果已经很圆滑,数量继续增加时,对效果不再有明显的提升。
4. 使用
GitHub上打包了一个unitypackage,导入项目后,Hierarchy窗口中右击菜单的UI子菜单中就可以看到ScaleImage选项,点击之后就可以添加。之后就可以在Inspector中看到如下图所示的设置:
因为继承自UGUI的Image组件,所以上半部分是原始的属性,下面是额外增加的属性,因为本身增加的是处理缩放的内容,所以Image组件原本的ImageType属性隐藏了,不处理Sliced、Tiled和Filled这几种情况。
Set Native Size
按钮用来快速将自身尺寸设置成图片的尺寸,Use Parent Size
按钮用来快速将自身尺寸设置成和父组件的尺寸。
勾选Round Corner后,将处理圆角参数,不勾选时则跳过。Radius Ratio是圆角的半径和边长的比例,变化范围0到0.5,实现时会选择使用较短的边;而Triangle Num则是动态设定圆角处三角形的数量,可根据实际情况调整,本着效果刚刚好设置即可,过高对效果没有帮助,反而会增加顶点和三角形的数量,增加绘制成本。
5. 实现代码
完整实现代码会放到GitHub,这里只拿出部分关键的代码来说说。
1 | protected override void OnPopulateMesh(VertexHelper toFill) |
上面是Image的源代码,可以看出,重写OnPopulateMesh
方法时,实现一个可以替代GenerateSimpleSprite
的方法即可,再接着往下看其中的实现:
1 | void GenerateSimpleSprite(VertexHelper vh, bool lPreserveAspect) |
这个方法很简单,正如上面所言,绘制一个矩形的图片只需要4个顶点,和2个三角形,而这个方法内也正是通过VertextHelper添加了这些,剩下的就是GPU的事了。
其中的Vector4 v
是目标Image绘制区域,Verctor4 uv
则是图片的像素区域,4个值分别代表着区域的4条边:left,bottom,right和top。
所以,如果自定义填满Image的那些区域,则需要修改变量Vector4 v
;同样,如果自定义需要图片中哪些区域的像素,则需要修改Vector4 uv
变量;绘制圆角时,则是自定义添加顶点和三角形。
用CenterCrop举例说明一下,这种模式下,不需要操作Image的区域,因为是完全填满的,只需要计算图片需要选取的区域。
因为是按照原图比例缩放,所以需要先确定是按照宽缩放,还是按照高缩放,这取决于图片的比例和Image比例的大小。如果是以宽为准,也就是让图片的宽完全显示,则先计算图片的宽到Image的宽的缩放值,然后以同样的缩放值去缩放高度,缩放之后的高度无法完全展示,则需要计算截取的部分,然后去偏移图片区域uv
的bottom和top即可。
1 | void GenerateScaleSprite(VertexHelper vh) |
依此类推,按需调整图片区域和Image区域4条边的偏移,即可实现对应的效果。
6. 仓库地址
https://github.com/oynix/ScaleImage
7. 参考
- SimpleRoundedImage-不使用mask实现圆角矩形图片