Unity3D, build PNG from Panel of a Unity.UI?

本小妞迷上赌 提交于 2019-12-04 01:01:36

问题


Think about any Unity.UI Canvas you might have.

Imagine a typical Panel on that Canvas. Say it happens to contain some images, maybe some Text and so on.

It would be very handy if you could turn that panel (only the panel) in to a screenshot: a Texture2D or PNG.

The only thing I can think of is just using ReadPixels and figuring out the area of the Panel in question on the screen (and actually that is pretty tricky); and that only works if the panel happens to be square and not rotated at an angle.

You'd think, there should be a way to render one Panel, or at least, one whole canvas? I can find nothing.

In the example, make the pink Panel a PNG image. Ouch.

(Obviously, if anyone has a solution that does merely "the whole Canvas", rather than one Panel, of course even that is admirable.)


回答1:


The code below can take picture of a Canvas. Canvas must be attached the Object you are passing into it. The only function to call is void takeScreenShot(Canvas canvasPanel, SCREENSHOT_TYPE screenShotType = SCREENSHOT_TYPE.IMAGE_AND_TEXT, bool createNewInstance = true)

SCREENSHOT_TYPE.IMAGE_AND_TEXT parameter will take picture of images and texts.

SCREENSHOT_TYPE.IMAGE_ONLY parameter will only take picture of images. Every texts on the screen will be excluded. You can use this for security reason to remove texts and just show graphics only.

SCREENSHOT_TYPE.TEXT_ONLY parameter will take picture of Texts only.

How to use. Create a GameObject, attach the CanvasScreenShot script to it. Subscribe to CanvasScreenShot.OnPictureTaken(byte[] pngArray);, then call screenShot.takeScreenShot(canvasToSreenShot, SCREENSHOT_TYPE.IMAGE_AND_TEXT, false);

Complete Code:

Your test.cs script:

public class test : MonoBehaviour
{
    public Canvas canvasToSreenShot;

    // Use this for initialization
    void Start()
    {
        //Subscribe
        CanvasScreenShot.OnPictureTaken += receivePNGScreenShot;
        CanvasScreenShot screenShot = GameObject.Find("GameObject").GetComponent<CanvasScreenShot>();

        //take ScreenShot(Image and Text)
        //screenShot.takeScreenShot(canvasToSreenShot, SCREENSHOT_TYPE.IMAGE_AND_TEXT, false);
        //take ScreenShot(Image only)
        screenShot.takeScreenShot(canvasToSreenShot, SCREENSHOT_TYPE.IMAGE_ONLY, false);
        //take ScreenShot(Text only)
        // screenShot.takeScreenShot(canvasToSreenShot, SCREENSHOT_TYPE.TEXT_ONLY, false);

    }

    public void OnEnable()
    {
        //Un-Subscribe
        CanvasScreenShot.OnPictureTaken -= receivePNGScreenShot;
    }

    void receivePNGScreenShot(byte[] pngArray)
    {
        Debug.Log("Picture taken");

        //Do Something With the Image (Save)
        string path = Application.persistentDataPath + "/CanvasScreenShot.png";
        System.IO.File.WriteAllBytes(path, pngArray);
        Debug.Log(path);
    }

}

The CanvasScreenShot.cs script:

public class CanvasScreenShot : MonoBehaviour
{
    /*
 CanvasScreenShot by programmer.
 http://stackoverflow.com/questions/36555521/unity3d-build-png-from-panel-of-a-unity-ui#36555521
 http://stackoverflow.com/users/3785314/programmer
 */

    //Events
    public delegate void takePictureHandler(byte[] pngArray);
    public static event takePictureHandler OnPictureTaken;

    private GameObject duplicatedTargetUI;
    private Image[] allImages;
    private Text[] allTexts;

    //Store all other canvas that will be disabled and re-anabled after screenShot
    private Canvas[] allOtherCanvas;

    //takes Screenshot
    public void takeScreenShot(Canvas canvasPanel, SCREENSHOT_TYPE screenShotType = SCREENSHOT_TYPE.IMAGE_AND_TEXT, bool createNewInstance = true)
    {
        StartCoroutine(_takeScreenShot(canvasPanel, screenShotType, createNewInstance));
    }

    private IEnumerator _takeScreenShot(Canvas canvasPanel, SCREENSHOT_TYPE screenShotType = SCREENSHOT_TYPE.IMAGE_AND_TEXT, bool createNewInstance = true)
    {
        //Get Visible Canvas In the Scene
        allOtherCanvas = getAllCanvasInScene(false);

        //Hide all the other Visible Canvas except the one that is passed in as parameter(Canvas we want to take Picture of)
        showCanvasExcept(allOtherCanvas, canvasPanel, false);
        //Reset the position so that both UI will be in the-same place if we make the duplicate a child
        resetPosAndRot(gameObject);

        //Check if we should operate on the original image or make a duplicate of it
        if (createNewInstance)
        {
            //Duplicate the Canvas we want to take Picture of
            duplicatedTargetUI = duplicateUI(canvasPanel.gameObject, "ScreenShotUI");
            //Make this game object the parent of the Canvas
            duplicatedTargetUI.transform.SetParent(gameObject.transform);

            //Hide the orginal Canvas we want to take Picture of
            showCanvas(canvasPanel, false);
        }
        else
        {
            //No duplicate. Use original GameObject
            //Make this game object the parent of the Canvas
            canvasPanel.transform.SetParent(gameObject.transform);
        }

        RenderMode defaultRenderMode;

        //Change the duplicated Canvas to RenderMode to overlay
        Canvas duplicatedCanvas = null;
        if (createNewInstance)
        {
            duplicatedCanvas = duplicatedTargetUI.GetComponent<Canvas>();
            defaultRenderMode = duplicatedCanvas.renderMode;
            duplicatedCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
        }
        else
        {
            defaultRenderMode = canvasPanel.renderMode;
            canvasPanel.renderMode = RenderMode.ScreenSpaceOverlay;
        }


        if (screenShotType == SCREENSHOT_TYPE.IMAGE_AND_TEXT)
        {
            //No Action Needed
        }
        else if (screenShotType == SCREENSHOT_TYPE.IMAGE_ONLY)
        {
            if (createNewInstance)
            {
                //Get all images on the duplicated visible Canvas
                allTexts = getAllTextsFromCanvas(duplicatedTargetUI, false);
                //Hide those images
                showTexts(allTexts, false);
            }
            else
            {
                //Get all images on the duplicated visible Canvas
                allTexts = getAllTextsFromCanvas(canvasPanel.gameObject, false);
                //Hide those images
                showTexts(allTexts, false);
            }
        }
        else if (screenShotType == SCREENSHOT_TYPE.TEXT_ONLY)
        {
            if (createNewInstance)
            {
                //Get all images on the duplicated visible Canvas
                allImages = getAllImagesFromCanvas(duplicatedTargetUI, false);
                //Hide those images
                showImages(allImages, false);
            }
            else
            {
                //Get all images on the duplicated visible Canvas
                allImages = getAllImagesFromCanvas(canvasPanel.gameObject, false);
                //Hide those images
                showImages(allImages, false);
            }
        }

        //////////////////////////////////////Finally Take ScreenShot///////////////////////////////
        yield return new WaitForEndOfFrame();
        Texture2D screenImage = new Texture2D(Screen.width, Screen.height);
        //Get Image from screen
        screenImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
        screenImage.Apply();

        //Convert to png
        byte[] pngBytes = screenImage.EncodeToPNG();

        /*FOR TESTING/DEBUGGING PURPOSES ONLY. COMMENT THIS
        string path = Application.persistentDataPath + "/CanvasScreenShot.png";
        System.IO.File.WriteAllBytes(path, pngBytes);
        Debug.Log(path);*/

        //Notify functions that are subscribed to this event that picture is taken then pass in image bytes as png
        if (OnPictureTaken != null)
        {
            OnPictureTaken(pngBytes);
        }


        ///////////////////////////////////RE-ENABLE OBJECTS

        //Change the duplicated Canvas RenderMode back to default Value
        if (createNewInstance)
        {
            duplicatedCanvas.renderMode = defaultRenderMode;
        }
        else
        {
            canvasPanel.renderMode = defaultRenderMode;
        }
        //Un-Hide all the other Visible Canvas except the one that is passed in as parameter(Canvas we want to take Picture of)
        showCanvas(allOtherCanvas, true);
        if (screenShotType == SCREENSHOT_TYPE.IMAGE_AND_TEXT)
        {
            //No Action Needed
        }
        else if (screenShotType == SCREENSHOT_TYPE.IMAGE_ONLY)
        {
            //Un-Hide those images
            showTexts(allTexts, true);
        }
        else if (screenShotType == SCREENSHOT_TYPE.TEXT_ONLY)
        {
            //Un-Hide those images
            showImages(allImages, true);
        }

        //Un-hide the orginal Canvas we want to take Picture of
        showCanvas(canvasPanel, true);

        if (createNewInstance)
        {
            //Destroy the duplicated GameObject
            Destroy(duplicatedTargetUI, 1f);
        }
        else
        {
            //Remove the Canvas as parent 
            canvasPanel.transform.SetParent(null);
        }
    }

    private GameObject duplicateUI(GameObject parentUICanvasOrPanel, string newOBjectName)
    {
        GameObject tempObj = Instantiate(parentUICanvasOrPanel);
        tempObj.name = newOBjectName;
        return tempObj;
    }


    private Image[] getAllImagesFromCanvas(GameObject canvasParentGameObject, bool findDisabledCanvas = false)
    {
        Image[] tempImg = canvasParentGameObject.GetComponentsInChildren<Image>(findDisabledCanvas);
        if (findDisabledCanvas)
        {
            return tempImg;
        }
        else
        {
            System.Collections.Generic.List<Image> canvasList = new System.Collections.Generic.List<Image>();
            for (int i = 0; i < tempImg.Length; i++)
            {
                if (tempImg[i].enabled)
                {
                    canvasList.Add(tempImg[i]);
                }
            }
            return canvasList.ToArray();
        }
    }

    private Text[] getAllTextsFromCanvas(GameObject canvasParentGameObject, bool findDisabledCanvas = false)
    {
        Text[] tempImg = canvasParentGameObject.GetComponentsInChildren<Text>(findDisabledCanvas);
        if (findDisabledCanvas)
        {
            return tempImg;
        }
        else
        {
            System.Collections.Generic.List<Text> canvasList = new System.Collections.Generic.List<Text>();
            for (int i = 0; i < tempImg.Length; i++)
            {
                if (tempImg[i].enabled)
                {
                    canvasList.Add(tempImg[i]);
                }
            }
            return canvasList.ToArray();
        }
    }

    private Canvas[] getAllCanvasFromCanvas(Canvas canvasParentGameObject, bool findDisabledCanvas = false)
    {
        Canvas[] tempImg = canvasParentGameObject.GetComponentsInChildren<Canvas>(findDisabledCanvas);
        if (findDisabledCanvas)
        {
            return tempImg;
        }
        else
        {
            System.Collections.Generic.List<Canvas> canvasList = new System.Collections.Generic.List<Canvas>();
            for (int i = 0; i < tempImg.Length; i++)
            {
                if (tempImg[i].enabled)
                {
                    canvasList.Add(tempImg[i]);
                }
            }
            return canvasList.ToArray();
        }
    }

    //Find Canvas.
    private Canvas[] getAllCanvasInScene(bool findDisabledCanvas = false)
    {
        Canvas[] tempCanvas = GameObject.FindObjectsOfType<Canvas>();
        if (findDisabledCanvas)
        {
            return tempCanvas;
        }
        else
        {
            System.Collections.Generic.List<Canvas> canvasList = new System.Collections.Generic.List<Canvas>();
            for (int i = 0; i < tempCanvas.Length; i++)
            {
                if (tempCanvas[i].enabled)
                {
                    canvasList.Add(tempCanvas[i]);
                }
            }
            return canvasList.ToArray();
        }
    }

    //Disable/Enable Images
    private void showImages(Image[] imagesToDisable, bool enableImage = true)
    {
        for (int i = 0; i < imagesToDisable.Length; i++)
        {
            imagesToDisable[i].enabled = enableImage;
        }
    }

    //Disable/Enable Texts
    private void showTexts(Text[] imagesToDisable, bool enableTexts = true)
    {
        for (int i = 0; i < imagesToDisable.Length; i++)
        {
            imagesToDisable[i].enabled = enableTexts;
        }
    }


    //Disable/Enable Canvas
    private void showCanvas(Canvas[] canvasToDisable, bool enableCanvas = true)
    {
        for (int i = 0; i < canvasToDisable.Length; i++)
        {
            canvasToDisable[i].enabled = enableCanvas;
        }
    }


    //Disable/Enable one canvas
    private void showCanvas(Canvas canvasToDisable, bool enableCanvas = true)
    {
        canvasToDisable.enabled = enableCanvas;
    }

    //Disable/Enable Canvas Except
    private void showCanvasExcept(Canvas[] canvasToDisable, Canvas ignoreCanvas, bool enableCanvas = true)
    {
        for (int i = 0; i < canvasToDisable.Length; i++)
        {
            if (!(canvasToDisable[i] == ignoreCanvas))
            {
                canvasToDisable[i].enabled = enableCanvas;
            }
        }
    }

    //Disable/Enable Canvas Except
    private void showCanvasExcept(Canvas[] canvasToDisable, Canvas[] ignoreCanvas, bool enableCanvas = true)
    {
        for (int i = 0; i < canvasToDisable.Length; i++)
        {
            for (int j = 0; j < ignoreCanvas.Length; j++)
            {
                if (!(canvasToDisable[i] == ignoreCanvas[j]))
                {
                    canvasToDisable[i].enabled = enableCanvas;
                }
            }
        }
    }

    //Reset Position
    private void resetPosAndRot(GameObject posToReset)
    {
        posToReset.transform.position = Vector3.zero;
        posToReset.transform.rotation = Quaternion.Euler(Vector3.zero);
    }

}

public enum SCREENSHOT_TYPE
{
    IMAGE_AND_TEXT, IMAGE_ONLY, TEXT_ONLY
}



回答2:


This is just a quick stab at it since I can't find any official easy way to do it.

I have no idea how performance intensive this would be since I haven't tried it.

Add a camera, pointed at the UI panel to get a straight on shot. Render the camera to a texture, with only that panel being rendered on the camera, destroy the camera.

Then encode the texture to a png.

  1. This could be horribly expensive.
  2. It would also render the rest of the screen unless you size the camera to the panel, and even then if the panel isn't a square or rectangle you will get part of the game/skybox/background color in the shot.
  3. It could just plain not work



回答3:


I haven't looked into it deeply, but I assume it is impossible to render a Panel without rendering its parent Canvas. I'm also assuming that you don't want to do this every frame, but only on specific occasions.

In that context, here's what I would try:

  • Have a second Canvas with RenderMode "Screen Space - Camera". This allows you to specify the camera used to render this Canvas.
  • Have a dedicated Camera to render your second Canvas.
  • Give the Camera a script which handles OnPreCull and OnPostRender
  • OnPreCull, attach the target Panel to your secondary Canvas. OnPostRender, reattach it to where it was before (I'm so... so sorry)
  • Let the secondary Camera render onto a RenderTexture
  • ReadPixels, Apply, EncodeToPNG, voila

Of course there are lots of details unspecified, like sizes and positions and whatever. But having dedicated Canvas and Camera should allow all of this to be figured out and setup correctly without messing stuff up.



来源:https://stackoverflow.com/questions/36555521/unity3d-build-png-from-panel-of-a-unity-ui

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!