单独创建一个相机和画布实现截取一部分图,或者长图,传入的obj需含有“RectTransform”便于获取所截区域的大小,即obj的尺寸就是截图的尺寸。
我传入的是Content对象,组件有ContentSizeFitter,因为适应宽高刷新不及时,如果不调用延时的话,可能会报错“Reading pixels out of bounds of the current active render texture”,如果没有该组件可以不用延时。
在设置相机画布尺寸后延时DelayCaptureTexture渲染,为了适配好宽高,不然渲染出的是很小的图,是因为画布默认的宽高是窗口的大小,截出的有效区很小。
相机清除时_camera.clearFlags = CameraClearFlags.Depth在真机上截图效果花屏,应改为CameraClearFlags.SolidColor。
private Canvas _canvas;
private Camera _camera;
private RenderTexture _renderTexture;
private RectTransform _rectTransformObj;
private GameObject _objClone;
private Texture2D _texture2D;
public void DoScreenShot(GameObject obj)
{
CreateCanvas();
DoCloneGameObject(obj);
}
//创建画布 画布的尺寸随相机视口大小
private void CreateCanvas()
{
if (_canvas == null)
{
_canvas = new GameObject("ScreenShotCanvas").AddComponent<Canvas>();
_canvas.renderMode = RenderMode.ScreenSpaceCamera;
CreateCamera();
_canvas.worldCamera = _camera;
_canvas.gameObject.GetOrAddComponent<CanvasScaler>();
_canvas.gameObject.GetOrAddComponent<GraphicRaycaster>();
}
}
//创建相机
private void CreateCamera()
{
_camera = new GameObject("ScreenShotCamera").AddComponent<Camera>();
_camera.transform.localPosition = new Vector3(30, 0, -10);
_camera.transform.localScale = Vector3.one;
//_camera.clearFlags = CameraClearFlags.Depth;
_camera.clearFlags = CameraClearFlags.SolidColor;
_camera.backgroundColor = new Color32(0, 0, 0, 0);
_camera.cullingMask = -1;
_camera.orthographic = true;
_camera.orthographicSize = 1.5f;
_camera.depth = -2;
}
//克隆需要截图的对象
private void DoCloneGameObject(GameObject obj)
{
_objClone = GameObject.Instantiate(obj) as GameObject;
_rectTransformObj = _objClone.GetComponent<RectTransform>();
_objClone.transform.SetParent(_canvas.transform);
_rectTransformObj.pivot = new Vector2(0.5f, 0.5f);
_rectTransformObj.anchorMax = new Vector2(0.5f, 0.5f);
_rectTransformObj.anchorMin = new Vector2(0.5f, 0.5f);
_rectTransformObj.sizeDelta = obj.GetComponent<RectTransform>().rect.size;
_objClone.transform.localPosition = Vector3.zero;
_objClone.transform.localScale = Vector3.one;
StartCoroutine(DelaySetSize());
}
//延时设置_renderTexture的尺寸,相机的尺寸随_renderTexture的尺寸
private IEnumerator DelaySetRenderTextureSize()
{
yield return new WaitForEndOfFrame();
_renderTexture = new RenderTexture((int)_rectTransformObj.rect.width, (int)_rectTransformObj.rect.height, 24,
RenderTextureFormat.ARGB32);
_camera.targetTexture = _renderTexture;
StartCoroutine(DelayCaptureTexture());
}
private IEnumerator DelayCaptureTexture()
{
yield return new WaitForEndOfFrame();
_camera.RenderDontRestore();
_renderTexture.filterMode = FilterMode.Bilinear;
_camera.Render();
_texture2D = new Texture2D((int)_rectTransformObj.rect.width, (int)_rectTransformObj.rect.height,
TextureFormat.ARGB32, false);
_texture2D.ReadPixels(new Rect(0, 0, _rectTransformObj.rect.width, _rectTransformObj.rect.height), 0, 0);
_texture2D.Apply();
ScreenShotImage();
_camera.targetTexture = null;
GameObject.Destroy(_objClone);
}
//生成图片并保存
private void ScreenShotImage()
{
byte[] bytes = _texture2D.EncodeToPNG();
_texture2D.Compress(true);
_texture2D.Apply();
string fileDir = Application.dataPath + "/ScreenShot";
if (!Directory.Exists(fileDir))
{
Directory.CreateDirectory(fileDir);
}
string filePath = fileDir + "/ScreenShotImage.png";
File.WriteAllBytes(filePath, bytes);
Debug.Log("截取了一张图片:"+ filePath);
}
上面使用的方法是基于画布的Canvas Scaler组件的缩放模式(UI Scale Mode)为Constant Pixel Size画布尺寸同像素大小及Camera的Target Texture。这种方法有个问题的,当传入的obj是关于文字排版时,克隆出的对象和主相机中的对象排版不一,尤其是文字下有下划线时更明显,其他情况下都是OK的。
后面使用的截图方法缩放模式是Scale With Screen Size。
截图读取像素从左下角开始,克隆对象轴点和锚点设置为0,代码段如下:
_rectTransformObj.pivot = Vector2.zero;
_rectTransformObj.anchorMax = Vector2.zero;
_rectTransformObj.anchorMin = Vector2.zero;
_rectTransformObj.sizeDelta = _cloneObjSize;
_rectTransformObj.anchoredPosition3D = Vector3.zero;
_objClone.transform.localScale = Vector3.one;
_objClone.transform.localEulerAngles = Vector3.zero;
private Texture2D GetScreenTexture(Camera _camera,RectTransform _rectTransformObj)
{
RenderTexture rt = RenderTexture.GetTemporary((int)(_canvasSize.x), (int)(_canvasSize.y));
_camera.targetTexture = rt;
_camera.Render();
RenderTexture.active = rt;
Texture2D _texture2D;
_texture2D = new Texture2D((int)(_rectTransformObj.rect.width), (int)(_rectTransformObj.rect.height/0.95f), TextureFormat.ARGB32, false);
_texture2D.ReadPixels(new Rect(0, 0f, (int)(_rectTransformObj.rect.width), (int)(_rectTransformObj.rect.height/0.95f)), 0, 0);
_texture2D.Apply();
byte[] bytes = _texture2D.EncodeToPNG();
_camera.targetTexture = null;
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
string fileDir = Application.dataPath + "/ScreenShot";
if (!Directory.Exists(fileDir))
{
Directory.CreateDirectory(fileDir);
}
string filePath = fileDir + "/ScreenShotImage.png";
File.WriteAllBytes(filePath, bytes);
Debug.Log("截取了一张图片:"+ filePath);
return _texture2D;
}
上面这种是截取canvas内指定的区域的图片,如果obj尺寸大于canvas的宽或高则等比缩放到canvas内。这种方法有个问题是缩小比例越大,截图放大后就失真越多越模糊。
private void CaptureTexture(int surplusWidth, int surplusHeight, int addX = 0, int addY = 0)
{
Texture2Ds texture2Ds = new Texture2Ds();
texture2Ds._X = addX;
texture2Ds._Y = addY;
if (surplusWidth <= (int)(_canvasSize.x))
{
if (surplusHeight <= (int)(_canvasSize.y))
{
texture2Ds._Texture2D = DoCaptureTexture(surplusWidth, surplusHeight);
_listTexture2D.Add(texture2Ds);
}
else
{
texture2Ds._Texture2D = DoCaptureTexture(surplusWidth, (int)(_canvasSize.y));
_listTexture2D.Add(texture2Ds);
_rectTransformObj.anchoredPosition3D = new Vector3(_rectTransformObj.anchoredPosition3D.x,
_rectTransformObj.anchoredPosition3D.y - (int)(_canvasSize.y), 0);
CaptureTexture(surplusWidth, surplusHeight - (int)(_canvasSize.y), addX, addY + (int)(_canvasSize.y));
}
}
else if (surplusWidth > (int)(_canvasSize.x))
{
if (surplusHeight <= (int)(_canvasSize.y))
{
texture2Ds._Texture2D = DoCaptureTexture((int)(_canvasSize.x), surplusHeight);
_listTexture2D.Add(texture2Ds);
_rectTransformObj.anchoredPosition3D = new Vector3(_rectTransformObj.anchoredPosition3D.x-(int)(_canvasSize.x),
_rectTransformObj.anchoredPosition3D.y, 0);
CaptureTexture(surplusWidth-(int)(_canvasSize.x), surplusHeight, addX+(int)(_canvasSize.x), addY );
}
else
{
texture2Ds._Texture2D = DoCaptureTexture((int)(_canvasSize.x), (int)(_canvasSize.y));
_listTexture2D.Add(texture2Ds);
_rectTransformObj.anchoredPosition3D = new Vector3(_rectTransformObj.anchoredPosition3D.x-(int)(_canvasSize.x),
_rectTransformObj.anchoredPosition3D.y - (int)(_canvasSize.y), 0);
CaptureTexture(surplusWidth-(int)(_canvasSize.x), surplusHeight - (int)(_canvasSize.y), addX+(int)(_canvasSize.x), addY + (int)(_canvasSize.y));
}
}
}
private Texture2D DoCaptureTexture(int width,int height)
{
RenderTexture rt = RenderTexture.GetTemporary((int)_canvasSize.x,(int)_canvasSize.y);
_camera.targetTexture = rt;
_camera.Render();
RenderTexture.active = rt;
Texture2D texture2D = new Texture2D(width, height, TextureFormat.ARGB32, false);
texture2D.ReadPixels(new Rect(0, 0f, width, height), 0, 0);
texture2D.Apply();
_camera.targetTexture = null;
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
return texture2D;
}
//拼图
private void MergeTexture2D()
{
_texture2D = new Texture2D((int)_cloneObjSize.x, (int)_cloneObjSize.y);
int w = 0;
int h = 0;
for (int i = 0; i < _listTexture2D.Count; i++)
{
//取图
Color32[] color = _listTexture2D[i]._Texture2D.GetPixels32(0);
//赋给新图
if (i > 0)
{
if (_listTexture2D[i]._X > _listTexture2D[i - 1]._X)
_texture2D.SetPixels32(w += _listTexture2D[i - 1]._Texture2D.width, h,
_listTexture2D[i]._Texture2D.width, _listTexture2D[i]._Texture2D.height, color); //宽度
else
_texture2D.SetPixels32(w, h += _listTexture2D[i - 1]._Texture2D.height,
_listTexture2D[i]._Texture2D.width, _listTexture2D[i]._Texture2D.height, color); //高度
}
else
{
_texture2D.SetPixels32(w, h, _listTexture2D[i]._Texture2D.width, _listTexture2D[i]._Texture2D.height, color);
}
}
//应用
_texture2D.Apply();
byte[] bytes = _texture2D.EncodeToPNG();
string fileDir = Application.dataPath + "/ScreenShot";
if (!Directory.Exists(fileDir))
{
Directory.CreateDirectory(fileDir);
}
string filePath = fileDir + "/ScreenShotImage.png";
File.WriteAllBytes(filePath, bytes);
Debug.Log("截取了一张图片:"+ filePath);
}
调用方法CaptureTexture((int)_cloneObjSize.x, (int)_cloneObjSize.y);传入克隆对象的宽高,传入值必须是整型,因为像素是整型,画布尺寸也需是整型。在调用前先动态获取下_canvasSize的尺寸,如果在创建画布后就获取它的尺寸,还来不及适应,获取的值就不对,裁剪时尺寸会对不上,从而裁剪不完全。
上面这种方法是不用缩放obj的,即使obj的尺寸远远大于画布尺寸,思路是先裁剪再拼图,这种就不会失真了。