如何在 UI 界面中實現穩定截圖以及截長圖的方案

2021-02-21 Unity程序猿的學習日常

如何在 Unity 中截圖

在 Unity 中要實現截圖的功能,我大體搜索了下,列舉如下幾種:

截全屏(使用 ScreenCapture 的API)
void CaptureScreen()   {      ScreenCapture.CaptureScreenshot("Screenshot.png"); }


根據截圖區域截圖(使用 Texture2D 的 API)
Texture2D CaptureScreenshot2(Rect rect)   {      // 先創建一個的空紋理,大小可根據實現需要來設置      Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24,false);  
// 讀取屏幕像素信息並存儲為紋理數據, screenShot.ReadPixels(rect, 0, 0); screenShot.Apply();
// 然後將這些紋理數據,成一個png圖片文件 byte[] bytes = screenShot.EncodeToPNG(); string filename = Application.dataPath + "/Screenshot.png"; System.IO.File.WriteAllBytes(filename, bytes); Debug.Log(string.Format("截屏了一張圖片: {0}", filename));
// 最後,我返回這個Texture2d對象,這樣我們直接,所這個截圖圖示在遊戲中,當然這個根據自己的需求的。 return screenShot; }


根據相機渲染內容截圖(使用 RenderTexture API)
Texture2D CaptureCamera(Camera camera, Rect rect)   {          RenderTexture rt = new RenderTexture((int)rect.width, (int)rect.height, 0);          camera.targetTexture = rt;      camera.Render();                                  
RenderTexture.active = rt; Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24,false); screenShot.ReadPixels(rect, 0, 0); screenShot.Apply();
camera.targetTexture = null; RenderTexture.active = null; GameObject.Destroy(rt); byte[] bytes = screenShot.EncodeToPNG(); string filename = Application.dataPath + "/Screenshot.png"; System.IO.File.WriteAllBytes(filename, bytes); Debug.Log(string.Format("截屏了一張照片: {0}", filename));
return screenShot; }


以上 3 種方案應該都可以實現截圖功能,但是實際使用下來或多或少都還有些問題,具體哪些問題可以嘗試去寫寫代碼。

UI 截圖方案

這裡介紹一種 UI 截圖的方案,其實也是上面截圖方案的一種而已,之所以單獨拿出來說,也是在使用過程中出現過一些問題,然後自己總結得到的一種通用功能接口。

UI 截圖用到的方案就是上面的第二種,通過給定具體的區域進行屏幕截圖,那這種方案實現下來會有怎樣的問題呢。其實答案也簡單,截圖嘛,最怕的不就是截圖截錯了,或者根本截不到圖。在 Unity 的 UGUI 中為啥會出現這些情況呢,這原因可能在於 UGUI 本身的設計,帶錨點和不用錨點的 UI 截圖會出現不一樣的情況,更何況不同解析度下的 UI 展示的效果也是不同的。

好了,話不多說,直接上代碼:

IEnumerator CaptureScreenshot(RectTransform rectTransform){    yield return new WaitForEndOfFrame();    int width = Mathf.Abs((int)rectTransform.rect.size.x);    int height = Mathf.Abs((int)rectTransform.rect.size.y);
Texture2D tex = new Texture2D(width, height, TextureFormat.RGB24, false); int x = (int)((mainCam.WorldToScreenPoint(rectTransform.position).x) - (width * rectTransform.pivot.x)); int y = (int)((mainCam.WorldToScreenPoint(rectTransform.position).y) - (height * rectTransform.pivot.y));
tex.ReadPixels(new Rect(x, y, width, height), 0, 0); //執行讀取操作 tex.Apply(); byte[] bytes = tex.EncodeToPNG(); //保存 System.IO.File.WriteAllBytes(Application.dataPath + "/截圖.png", bytes);}


代碼中需要注意的有幾點:

首先就是獲取傳入 UI 組件的 RectTransform 得到組件的寬高,這裡採取的方法可能在某些組件上不起作用,這個後面再說;然後就是構建我們需要的 Texture2D 對象,設置好寬高,這裡需要了解 x 和 y 的值在截圖方法中起到什麼樣的作用。我們採用區域截圖的原理進行截圖,目的是構建一個 Texture2D 對象,目前寬高設置好了,但是截圖區域還沒說,截圖區域默認屏幕左下角為 (0,0) ,那麼 x 和 y 對應的就是這個起始截圖位置了。上面代碼針對 UI 組件進行了 x 和 y 值的獲取,需要將 UI 組件的世界坐標轉換成屏幕坐標,而且還要需要考慮 UI 組件的 pivot 點的位置,這樣才能精確計算到當前 UI 組件需要截取的區域是哪塊。目前我使用上述方案截取 UI 的圖片,沒啥問題了,但可能我測試不多,有問題再改吧~截長圖的實現過程

這裡截長圖的方案也是基於上面 UI 截圖來的,在 Unity 中搭建的界面,涉及長圖的話其實應該是使用了 ScrollView 組件,本方案也是基於 UGUI 中的 ScrollView 組件來的。

基本原理:截圖的方案有了,但是截圖必須是能在屏幕上看到的內容才能截取,比如一頁顯示不完的 ScrollView 內容 ,是不能夠直接傳入 UI 組件的信息進行截圖的,所以這裡的想法是拖動 ScollView 一幀一幀來截圖,然後收集截取的 Texture2D 片段,最後拼接截取的片段變成長圖。

話不多說,還是直接上代碼:

using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.UI;using FitMode = UnityEngine.UI.ContentSizeFitter.FitMode;
public class CaptureLongPic : MonoBehaviour{ public Button captureBtn; private Camera mainCam; public RectTransform targetTrans;
private ScrollRect scrollRect; // 計算 需要截取多少張圖 private int texLength = 0; // 收集到的 截圖片段 private List<Texture2D> texList; // 需要截取的區域信息 private RectTransform capturesRectTrans; // 是否可以分成整頁整頁這樣 private bool isAll = false; // 一頁的高度 private float pageHeight; // 所有頁面的高度(長圖的高度) private float allPageHeight;
void Start() { scrollRect = targetTrans.GetComponent<ScrollRect>(); captureBtn.onClick.AddListener(() => { Capture(); });
mainCam = Camera.main;
texList = new List<Texture2D>();
float height1 = scrollRect.transform.Find("Viewport").GetComponent<RectTransform>().rect.size.y; // float height2 = scrollRect.transform.Find("Viewport/Content").GetComponent<RectTransform>().rect.size.y; // 這樣獲取的是 0
// 組件 有 ContentSizeFitter 則需要這樣獲取組件高度 LayoutRebuilder.ForceRebuildLayoutImmediate(scrollRect.transform.Find("Viewport/Content").GetComponent<RectTransform>()); float height2 = HandleSelfFittingAlongAxis(1, scrollRect.transform.Find("Viewport/Content").GetComponent<RectTransform>(), scrollRect.transform.Find("Viewport/Content").GetComponent<ContentSizeFitter>());

// 說明滑動內容不夠一頁 if (height1 >= height2) { Debug.Log("內容不足1頁"); texLength = 1; // 只需要截取 content 中的內容即可 capturesRectTrans = scrollRect.transform.Find("Viewport/Content").GetComponent<RectTransform>(); isAll = false; } else { // 如果 滑動內容長度是一頁的整數倍 if ((height2 % height1) == 0) { texLength = (int)(height2 / height1); isAll = true; } else { texLength = (int)(height2 / height1) + 1; isAll = false; } capturesRectTrans = scrollRect.transform.Find("Viewport").GetComponent<RectTransform>(); }
pageHeight = height1; allPageHeight = height2;
}
// public Vector2 GetContentSizeFitterPreferredSize(this RectTransform rect, ContentSizeFitter contentSizeFitter) // { // LayoutRebuilder.ForceRebuildLayoutImmediate(rect); // return new Vector2(HandleSelfFittingAlongAxis(0, rect, contentSizeFitter), HandleSelfFittingAlongAxis(1, rect, contentSizeFitter)); // }
private float HandleSelfFittingAlongAxis(int axis, RectTransform rect, ContentSizeFitter contentSizeFitter) { FitMode fitting = (axis == 0 ? contentSizeFitter.horizontalFit : contentSizeFitter.verticalFit); if (fitting == FitMode.MinSize) { return LayoutUtility.GetMinSize(rect, axis); } else { return LayoutUtility.GetPreferredSize(rect, axis); } }
void Capture() { StartCoroutine(CaptureLonPic()); }
IEnumerator CaptureLonPic() { //上移 scrollview 繼續截圖 // 判斷當前長圖頁面是否是整數倍 if (isAll) { for (int i = 0; i < texLength; i++) { yield return new WaitForEndOfFrame();
StartCoroutine(CaptureScreenshot(capturesRectTrans));
scrollRect.verticalNormalizedPosition = 1 - (1 * i / texLength); } } else { float offset = pageHeight / (allPageHeight - pageHeight); for (int i = 0; i < texLength; i++) { yield return new WaitForEndOfFrame(); if (i == texLength - 1) { StartCoroutine(CaptureScreenshot(capturesRectTrans, (int)(allPageHeight - pageHeight * (texLength - 1)))); scrollRect.verticalNormalizedPosition = 0; } else { StartCoroutine(CaptureScreenshot(capturesRectTrans)); scrollRect.verticalNormalizedPosition = 1 - (offset * i); } } } yield return new WaitForSeconds(1f); // 拼接長圖 MergeImage(texList); }
/// <summary> /// Captures the screenshot2. /// </summary> /// <returns>The screenshot2.</returns> /// <param name="rect">Rect.截圖的區域,左下角為o點</param> IEnumerator CaptureScreenshot(RectTransform rectTransform, int h = 1280) { // 必須是 Camera 渲染模式 yield return new WaitForEndOfFrame(); int width = Mathf.Abs((int)rectTransform.rect.size.x); int height = Mathf.Abs((int)rectTransform.rect.size.y);
Texture2D tex; int x = (int)((mainCam.WorldToScreenPoint(rectTransform.position).x) - (width * rectTransform.pivot.x)); int y = (int)((mainCam.WorldToScreenPoint(rectTransform.position).y) - (height * rectTransform.pivot.y));
// 默認 解析度 720 * 1280 此處可以設置為動態獲取當前需要設置的高度 if (h != 1280) { tex = new Texture2D(width, h, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(x, y, width, h), 0, 0); } else { tex = new Texture2D(width, height, TextureFormat.RGB24, false); tex.ReadPixels(new Rect(x, y, width, height), 0, 0); } //執行讀取操作 tex.Apply(); texList.Add(tex);// 收集 Texture2D }
/// <summary> /// 多張Texture2D合成一張Texture2D /// </summary> /// <param name="tex"></param> /// <returns></returns> public Texture2D MergeImage(List<Texture2D> tex) { if (tex.Count == 0) return null; //定義新圖的寬高 int width = 0, height = 0;
for (int i = 0; i < tex.Count; i++) { //新圖的寬度 width = tex[i].width; height += tex[i].height; }
//初始Texture2D Texture2D texture2D = new Texture2D(width, height);
int x = 0, y = 0; for (int i = tex.Count - 1; i >= 0; i--) { //取圖 Color32[] color = tex[i].GetPixels32(0); //賦給新圖 if (i < tex.Count - 1) { texture2D.SetPixels32(x, y += tex[i + 1].height, tex[i].width, tex[i].height, color); } else { texture2D.SetPixels32(x, y, tex[i].width, tex[i].height, color); } }
//應用 texture2D.Apply();
byte[] bytes = texture2D.EncodeToPNG(); //保存 System.IO.File.WriteAllBytes(Application.dataPath + "/截圖.png", bytes); return texture2D;    }}

代碼注意:

ScrollView 組件中 Content 一般會添加 ContentSizeFitter ,一旦添加了這個的 UI 組件時不能像上述截圖方案中那樣獲取組件寬高信息,具體可以參考上面代碼的方式獲取寬高。

截長圖還是需要考慮截圖內容的長度問題,不足一頁的和超過一頁的情況,同時得到一頁高度和總高度,為後面移動 ScrollView 做準備,還有就是基於目前的情況得到能夠截取多少個 Texture2D。

代碼的截圖代碼大體和上述的方案差不多,不過不同的是添加了尾頁不足一頁長度的特殊處理。

最後就是拼接圖片,遍歷 Texture2D 的時候採取的從後向前遍歷,因為發現這樣合成的圖片才是正常的;SetPixels32 方法可以理解為 給長圖 Texture2D 的框框添加內容,參數就是具體的位置信息而已。

綜上差不多了~

相關焦點

  • 一分鐘教你Android、iOS如何實現自動化截長圖功能,超實用!
    點擊👆小卡片,回復 「合集」 獲取系統性的學習筆記和測試開發技能圖譜背景
  • 電腦輕鬆截長圖,截圖有它就夠了
    如今,大部分手機都能實現「截長圖」功能,可是電腦怎麼截長圖呢?今天這款軟體就能輕鬆幫你實現電腦「截長圖」,最不可思議的還可以錄製視頻,在截圖上添加圖片,文字,模糊、聚光燈效果,劃橫線等,功能甚是強大,它就是FSCapture。
  • iPhone/iPad 如何截長圖 ?
    例如,如何在 iOS 上實現長截圖,就讓很多人撓破了頭,每當需要截圖長長的聊天記錄的時候,就非常羨慕能夠輕鬆長截圖的安卓系統了。除了 Safari 瀏覽器自帶的整頁截圖功能(截圖還只能導出為 PDF),iOS 至今仍無系統內置的長截圖功能。
  • 百度iOS截長圖App
    雖然這款App在iPad上也可以運行,但實際上體驗並不完美,一來App界面沒有對iPad屏幕適配,會導致截圖現實不完全;二來對於沒有適配iPad的iPhone App,它沒法進行截圖,因此想要獲得最好的體驗,還是使用iPhone比較好。  iOS系統是不支持滾動截長圖的,百度iOS滾動截長圖App是如何做到這一點的呢?其實它和其他很多截長圖App一樣,都是通過屏幕錄像來實現的。
  • iPhone 也能隨意截長圖了!
    呔咯只想說,作為一個老果粉,這麼長時間還不會用 iPhone 截長圖,你不行啊!看在你們強烈要求的份上,今天給大家操作一下。升級 iOS 13 以後,有一個很頂的功能,就是用 Safari 瀏覽器一鍵截長圖。
  • 推薦一款滾動截長圖APP,全免費!超好用~
    目前這些APP主要由兩種方式實現長截圖功能,一是拼接截圖(用戶需要手動截多張圖片,然後通過應用自動拼接實現長截圖效果);二是錄屏滾動截圖(先用錄屏功能,錄製一段視頻,然後在滾動屏幕的時候長截圖)。相比之下,錄屏滾動截長圖無疑是更像原生截長圖功能,但這個功能往往是需要付費才能擁有。
  • 電腦截長圖就用它!最好用的錄屏+截圖軟體推薦
    【軟體介紹】FastStone Capture 是一款出色的屏幕捕捉(截圖)軟體,
  • 電腦截圖怎麼快捷,想截長圖怎麼辦
    電腦上怎麼截圖以Win10為例,系統自帶截圖工具,在開始菜單、附件裡可以找到截圖工具,打開後,默認新建截圖文件,圖示十字標,框選要截圖的內容截圖後,可以在菜單處選擇保存,或者其他操作。電腦怎麼快捷截圖直接按下這個鍵就是截全屏,來到wps文檔中ctrl+v粘貼,如圖,看到整個屏幕都被我截下來了。在圖上點擊滑鼠右鍵選擇另存為圖片可把圖片單獨保存出來。軟體附加截圖功能,像QQ,360瀏覽器等,均可截圖,可以添加文字、指示框、箭頭等,可以截圖後直接保存在剪貼板,然後在聊天窗口或者wps文檔直接粘貼即可。也可直接保存出來圖片,方便快捷。利用好快捷鍵可以在軟體最小化的情況下任意截圖。
  • 一款異常強大的截圖軟體,可截長圖、GIF圖、拼接圖片、延時截圖、自動添加水印等等
    這對於平時工作很少用到截圖功能的朋友來說,基本是夠用了。如果你經常截圖,且對截圖要求較多的朋友,相信今天介紹的這款軟體應該不會讓你失望。這款軟體叫——FastStone Capture。貌似只有Window版。軟體的功能十分強大,單單截圖就有N種姿勢,例如最常用的選框截圖,選擇部分或者全屏;還有類似於微信截圖一樣的自動選框,這些都只是最基本的截圖。
  • 蘋果手機怎麼截長圖?iPhone網頁長截屏、APP滾動截長圖教程
    最近有朋友問小編蘋果手機怎麼截長圖?
  • 好用的滾動截長圖工具大放送 Win&MAC
    ,難的是如何截長圖!1.FastStone Capture --Windows操作說明(1)無需安裝,下載文件包打開即可使用(文末附下載地址)第一步:打開需要截圖的界面第三步:截圖完成後會自動將截圖保存到操作面板中,並且可以在操作面板中對圖片進行各項編輯等
  • 更新:iPhone也能截長圖~超方便!
    有語音視頻通話支持文字表情互動,支持「個籤」互動新姿勢,消息左劃快捷回復,以及支持長截圖功能。想要更新的小夥伴只需要到App Store---軟體更新就可以進行全球更新了。最新iOS版騰訊QQ v8.2.6新功能更新一覽:-語音視頻通話支持文字互動,一起high聊更隨心;-網頁支持截長圖,快捷保存分享,觀感/體驗更舒適; -「個籤」互動新姿勢,點讚評論,超多熱點話題等你暢聊;-消息左劃快捷回復,互動更便捷。
  • 網頁內容太長怎麼截圖?不用安裝插件,在電腦上截長圖的2種最簡單方法
    QQ沒落,微信盛行時,我們選擇用快捷鍵Alt + A截圖。 但無論是QQ截圖,還是微信截圖,皆存在一個致命的缺點,就是不能一次性截長圖。 當我們要截取一個網頁,特別是內容很多很長時,選擇QQ截圖,或者微信截圖,都無法一次性完成操作。如果試圖縮小網頁大小,這時截取的圖片又會出現字體和圖像變形,也是違背了截長圖的初衷的。 因此,也有不少人選擇分幾次截取網頁,然後拼合在一起。
  • Windows和Mac OS必備的截長圖(滾動截圖)工具
    日常生活或工作中,我們經常會遇到截圖,但是僅限於截圖窗口部分或者其他不規則截圖,有時候還會面臨沒網沒第三方截圖工具。
  • 用 iPhone,這3個截長圖的方法你一定要知道
    今天教大家3個在 iPhone 上截長圖的方法 方法一無縫長圖拼接在iPhone上實現無縫長圖拼接,還是需要藉助第三方App來實現,除了之前介紹過收費1塊錢的 進行無縫長圖拼接之前,首先還是要用 iPhone 自帶的截圖方法,截好所有要拼接的圖比如下面我截的這幾張,我故意讓3張截圖互有重疊部分,這是為了待會讓軟體更好地識別
  • 原來蘋果手機有這麼多種截圖方式?之前還以為蘋果不能截長圖
    不知道大家平時用蘋果手機都是怎麼截圖的呢?相信就是用了好幾年iPhone的果粉也不一定就了解蘋果的所有功能吧?你知道在蘋果手機裡有多少種截圖方法嗎?你知道蘋果手機怎麼截長圖嗎?都還不知道?那就來看看吧!
  • 一款超級實用的截長圖工具
    你一定需要     如今,電腦已經成為我們生活和工作中必不可少的一個生產力工具。不管工作還是生活中,我們都需要一個簡單而又實用的功能——那就是截圖功能。     根據不同的生活場景往往會用到許多不同的截圖功能,市面上也有許多不同的軟體來滿足我們的需求,之前給大家推薦了一個截圖貼圖非常強大的軟體——Snipaste     但是很多小夥伴和我反映上面那個軟體截長圖非常的不方便,而且很多小夥伴都需要一個強大截長圖軟體。
  • 電腦端如何截長圖?方法很多種,我選我常用
    正常需要截圖一小塊,我會使用QQ截圖,快捷方式:Ctrl+Alt+A如果我的QQ沒有登錄或者設置不能使用快捷方式
  • iPhone 長截圖新方法,功能免費~
    ,截長圖非常方便。滾動截圖的就是通過 iPhone 的錄屏功能來實現長截圖的方法,而在目前的 APP Store 上很多類似的滾動截圖 APP ,但很多都需要收費,而今天給的大家分享的是免費應用。今天分享的是有這款應用是有百度推出的免費滾動截圖應用--「滾動截長圖」,大家可以直接到 App Store 上直接搜索下載。
  • iPhone手機如何長截圖?
    我是小幫,用iPhone的小夥伴們都知道,蘋果手機一直都沒有截長圖的功能,而安卓手機大部分都已經系統自帶截長圖了。