项目源码:后期发布
索引
- 图层
- TextureLayer
- 可见性
- 激活性
- 可编辑性
- 绘画区域、绘画板
- 绘画区域锚点
- 导入图像
图层
在PS
中,图层
的概念贯穿始终(了解PS图层),他可以称作PS
最基础也是最强大的特性之一。
那么,在TextureShop
中,我们的第一个任务也即是设计并完成图层
的功能。
TextureLayer
首先,定义TextureLayer
类,此即为图层类,一个TextureLayer
类对象代表一个图层实例:
/// <summary>
/// 图层
/// </summary>
public sealed class TextureLayer
{
}
可见性
基于图层的可见性
(也即是人眼可见),我们需要每一个图层都是能够独立渲染的,所以图层需要携带一个渲染器,在此我决定使用RawImage
来渲染,同时Texture2D
对象作为渲染源,Material
对象作为渲染材质:
/// <summary>
/// 图层
/// </summary>
public sealed class TextureLayer
{
private Material _material;
private RawImage _target;
private Texture2D _texture;
}
这三者应是图层的内部逻辑对象,不需对外,也不能对外公开,所以他们为private
的。
激活性
基于图层的可激活、可隐藏性
(也即是可激活、可隐藏图层),我们加入一个属性:
/// <summary>
/// 图层
/// </summary>
public sealed class TextureLayer
{
private Material _material;
private RawImage _target;
private Texture2D _texture;
/// <summary>
/// 是否激活图层
/// </summary>
public bool IsShow
{
get
{
return _target.enabled;
}
set
{
_target.enabled = value;
}
}
}
可编辑性
基于图层的可编辑性
(也即是可编辑颜色、可编辑区域等),我们会发现如下的问题:
当我们的图层尺寸为512x512
时,显示一张大于512x512
尺寸的图像,显示效果如下:
超出512x512
范围的图像内容(黑色区域
)将被裁切!
而实际上,这部分内容不仅要保留,还需要在某些情况下显示出来,比如向右拖动
图层时,左侧被黑色区域
挡住的内容将显现出来,而中间的原显示内容将移动到右侧黑色区域
中不可见。
由此,我们再次提出2个概念。
1.绘画区域,也即是可见的
512x512
区域被称作绘画区域
,只有此区域是可见、可编辑、可导出
的。
2.绘画板,也即是黑色区域
被称作绘画板
,默认2048*2048
,存储了整张图像的所有颜色数据。
为此,定义一个用于表示画板的类Plate
:
/// <summary>
/// 绘画板
/// </summary>
public sealed class Plate
{
private Color[,] _colors;
/// <summary>
/// 宽度
/// </summary>
public int Width { get; private set; }
/// <summary>
/// 高度
/// </summary>
public int Height { get; private set; }
}
二维数组_colors
存储了整个画板中的颜色数据。
绘画区域、绘画板
绘画区域、绘画板
都将单独用一个Plate(画板)
表示,他们是独立存在的:
/// <summary>
/// 图层
/// </summary>
public sealed class TextureLayer
{
//绘画板
private Plate _plate;
//绘画区域
private Plate _region;
}
绘画区域、绘画板
的关系大概就是如下这样:
绘画区域锚点
此时,我们的图层
在渲染图像时,会将绘画区域
中的内容填充到渲染源(Texture2D)
,再绘制到屏幕上。
而绘画区域
中的内容,又是根据绘画区域
所在的位置和尺寸,到整个绘画板
中去提取颜色数据。
那么,如果给绘画区域
一个锚点,则移动图层
的功能,便可以直接实现为移动绘画区域锚点
:
/// <summary>
/// 图层
/// </summary>
public sealed class TextureLayer
{
/// <summary>
/// 绘画区域在绘画板中的偏移值(锚点)
/// </summary>
public Vector2Int Offset { get; private set; }
}
锚点
为(0,0)
,则代表左下角,锚点
为(1,1)
,则代表右上角。
导入图像
图层的绘画板
作为其唯一存储颜色数据的目标,我们要实现导入一张外部图片,自动生成一个图层
的功能,只需要读取外部图片,并填充到绘画板(_plate)
中即可:
/// <summary>
/// 绘画板
/// </summary>
public sealed class Plate
{
private Color[,] _colors;
/// <summary>
/// 宽度
/// </summary>
public int Width { get; private set; }
/// <summary>
/// 高度
/// </summary>
public int Height { get; private set; }
/// <summary>
/// 绘画板
/// </summary>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="texture">导入的图像</param>
public Plate(int width, int height, Texture2D texture = null)
{
_colors = new Color[width, height];
if (texture != null)
{
if (texture.isReadable)
{
//计算图片尺寸与绘画板尺寸的关系,使得图片始终于绘画板中居中
int offsetX = (width - texture.width) / 2;
if (offsetX < 0) offsetX = 0;
int offsetY = (height - texture.height) / 2;
if (offsetY < 0) offsetY = 0;
for (int x = 0; x < texture.width; x++)
{
for (int y = 0; y < texture.height; y++)
{
int pixelX = x + offsetX;
int pixelY = y + offsetY;
if (pixelX < width && pixelY < height)
{
_colors[pixelX, pixelY] = texture.GetPixel(x, y);
}
}
}
}
else
{
Debug.LogError($"图像 {texture.name} 必须设置为可读写的,请在检视器面板勾选 Read/Write Enabled!");
}
}
Width = width;
Height = height;
}
}
同理的,在图层(TextureLayer)
的构造方法中完成这一切:
/// <summary>
/// 图层
/// </summary>
public sealed class TextureLayer
{
private RectTransform _rectTransform;
private Material _material;
private RawImage _target;
private Texture2D _texture;
private Plate _plate;
private Plate _region;
/// <summary>
/// 图层名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 是否显示图层
/// </summary>
public bool IsShow
{
get
{
return _target.enabled;
}
set
{
_target.enabled = value;
}
}
/// <summary>
/// 绘画区域在画板中的偏移值
/// </summary>
public Vector2Int Offset { get; private set; }
/// <summary>
/// 图层
/// </summary>
/// <param name="name">图层名称</param>
/// <param name="paintAreaWidth">绘画区域宽度</param>
/// <param name="paintAreaHeight">绘画区域高度</param>
/// <param name="plateWidth">绘画板宽度</param>
/// <param name="plateHeight">绘画板高度</param>
/// <param name="parent">图层所属父级</param>
/// <param name="texture">导入的图像</param>
/// <param name="offset">偏移值</param>
public TextureLayer(string name, int paintAreaWidth, int paintAreaHeight, int plateWidth, int plateHeight, RectTransform parent, Texture2D texture, Vector2Int offset = default)
{
//创建图层的渲染对象
_rectTransform = Utility.CreateRectTransform(name, parent, true);
//创建材质
_material = new Material(Utility.TextureLayerShader);
_material.hideFlags = HideFlags.HideAndDontSave;
//创建渲染器
_target = _rectTransform.gameObject.AddComponent<RawImage>();
_target.raycastTarget = true;
_target.material = _material;
//创建渲染源
_texture = new Texture2D(paintAreaWidth, paintAreaHeight, TextureFormat.RGBA32, false);
_texture.SetPixels(new Color[paintAreaWidth * paintAreaHeight]);
_texture.wrapMode = TextureWrapMode.Clamp;
_texture.name = Name;
//创建绘画板、绘画区域
_plate = new Plate(plateWidth, plateHeight, true, texture);
_region = new Plate(paintAreaWidth, paintAreaHeight, false);
//设置渲染源,此处没有采用直接设置为RawImage.texture的方式,因为在渲染Shader中,有一些我们自己的处理
_material.SetTexture("_PaintTex", _texture);
//设置初始偏移值(锚点),使得图像居中
Offset = (offset != default) ? offset : new Vector2Int((_plate.Width - _region.Width) / 2, (_plate.Height - _region.Height) / 2);
Name = name;
}
}
到这里,由于篇幅问题,图层(上)篇的内容暂时告一段落。