Search Unity

How to save manually save a PNG of a camera view

Discussion in 'Scripting' started by wileyjerkins, Nov 26, 2017.

Thread Status:
Not open for further replies.
  1. wileyjerkins

    wileyjerkins

    Joined:
    Oct 13, 2017
    Posts:
    77
    Someone may find this useful. I looked for something similar and ran into all kinds of problems, so thought I would share my eventual solution.

    If you attach this script to any camera that has a valid target texture in its settings, it will save a PNG of that snapshot to your hard drive (note the folder name in my path) ... which you can later use for whatever purpose. I have this script attached to a camera that only renders the game background, and I use the images later to create randomized backgrounds.

    This demo script uses the F9 key to capture the image, but you can make it automatically capture at random times (start of each level or some other time when there's no action) if you wish.

    Code (CSharp):
    1. using System.IO;
    2. using UnityEngine;
    3.  
    4. public class SR_RenderCamera : MonoBehaviour {
    5.  
    6.     public int FileCounter = 0;
    7.  
    8.     private void LateUpdate()
    9.     {
    10.         if (Input.GetKeyDown(KeyCode.F9))
    11.         {
    12.             CamCapture();  
    13.         }
    14.     }
    15.  
    16.     void CamCapture()
    17.     {
    18.         Camera Cam = GetComponent<Camera>();
    19.  
    20.         RenderTexture currentRT = RenderTexture.active;
    21.         RenderTexture.active = Cam.targetTexture;
    22.  
    23.         Cam.Render();
    24.  
    25.         Texture2D Image = new Texture2D(Cam.targetTexture.width, Cam.targetTexture.height);
    26.         Image.ReadPixels(new Rect(0, 0, Cam.targetTexture.width, Cam.targetTexture.height), 0, 0);
    27.         Image.Apply();
    28.         RenderTexture.active = currentRT;
    29.  
    30.         var Bytes = Image.EncodeToPNG();
    31.         Destroy(Image);
    32.  
    33.         File.WriteAllBytes(Application.dataPath + "/Backgrounds/" + FileCounter + ".png", Bytes);
    34.         FileCounter++;
    35.     }
    36.    
    37. }
     
    JonasLuz, JGroxz, SteveSimX and 17 others like this.
  2. unitylepi

    unitylepi

    Joined:
    May 21, 2018
    Posts:
    34
    Nice, thanks!
     
  3. Deleted User

    Deleted User

    Guest

    ElnuDev likes this.
  4. ElnuDev

    ElnuDev

    Joined:
    Sep 24, 2017
    Posts:
    298
    Thanks so much for this script! I can confirm it works in Unity 2019.3.0f6. I've made some performance, ease of use, and convention improvements to the script:

    Code (CSharp):
    1. using System.IO;
    2. using UnityEngine;
    3.  
    4. public class CameraCapture : MonoBehaviour
    5. {
    6.     public int fileCounter;
    7.     public KeyCode screenshotKey;
    8.     private Camera Camera
    9.     {
    10.         get
    11.         {
    12.             if (!_camera)
    13.             {
    14.                 _camera = Camera.main;
    15.             }
    16.             return _camera;
    17.         }
    18.     }
    19.     private Camera _camera;
    20.  
    21.     private void LateUpdate()
    22.     {
    23.         if (Input.GetKeyDown(screenshotKey))
    24.         {
    25.             Capture();
    26.         }
    27.     }
    28.  
    29.     public void Capture()
    30.     {
    31.         RenderTexture activeRenderTexture = RenderTexture.active;
    32.         RenderTexture.active = Camera.targetTexture;
    33.  
    34.         Camera.Render();
    35.  
    36.         Texture2D image = new Texture2D(Camera.targetTexture.width, Camera.targetTexture.height);
    37.         image.ReadPixels(new Rect(0, 0, Camera.targetTexture.width, Camera.targetTexture.height), 0, 0);
    38.         image.Apply();
    39.         RenderTexture.active = activeRenderTexture;
    40.  
    41.         byte[] bytes = image.EncodeToPNG();
    42.         Destroy(image);
    43.  
    44.         File.WriteAllBytes(Application.dataPath + "/Backgrounds/" + fileCounter + ".png", bytes);
    45.         fileCounter++;
    46.     }
    47. }
    Let me know if you have any problems with it! Again, big thanks to @wileyjerkins for making the original script! :D
     
  5. wileyjerkins

    wileyjerkins

    Joined:
    Oct 13, 2017
    Posts:
    77
    I am stealing these improvements! Good job!
     
    ElnuDev likes this.
  6. ElnuDev

    ElnuDev

    Joined:
    Sep 24, 2017
    Posts:
    298
    Thanks! Keep in mind that like the original, it unfortunately doesn't have any way to render gizmos since it is still using a render texture. Any ideas regarding taking a screenshot with gizmos?
     
  7. wileyjerkins

    wileyjerkins

    Joined:
    Oct 13, 2017
    Posts:
    77
    That is a complicated thing. Essentially you have to create your own gizmos, but fortunately someone else has done it for you. https://github.com/popcron/gizmos
     
    ElnuDev likes this.
  8. ElnuDev

    ElnuDev

    Joined:
    Sep 24, 2017
    Posts:
    298
    Neat! Can't wait to try it out. Too bad there isn't any setting to make vanilla gizmos render in the camera/builds, though. :(
     
  9. joepavitt

    joepavitt

    Joined:
    Oct 1, 2018
    Posts:
    2
    Thank you so much for writing these scripts. I do however hit an odd issue with the color of the output PNG.

    The output PNG is considerably darker than the actual camera/game feed in Unity. Any ideas?

    This is the script I've used, as per the examples included here. I created a completely new Scene, added a RenderTexture to the `Main Camera`, hit `Play` and pressed the relevant key to trigger it. The attached images are the output png (darker) and a screengrab of my Unity Editor's `Game` window.

    Code (CSharp):
    1.  
    2. using System.IO;
    3. using UnityEngine;
    4.  
    5. public class CameraCapture : MonoBehaviour
    6. {
    7.     public KeyCode screenshotKey;
    8.     private Camera _camera;
    9.  
    10.     void Start () {
    11.       _camera = GetComponent<Camera>();
    12.     }
    13.  
    14.     private void LateUpdate()
    15.     {
    16.         if (Input.GetKeyDown(screenshotKey))
    17.         {
    18.             Capture();
    19.         }
    20.     }
    21.  
    22.     public void Capture()
    23.     {
    24.         RenderTexture activeRenderTexture = RenderTexture.active;
    25.         Debug.Log(_camera);
    26.         RenderTexture.active = _camera.targetTexture;
    27.  
    28.         _camera.Render();
    29.  
    30.         Texture2D image = new Texture2D(_camera.targetTexture.width, _camera.targetTexture.height);
    31.         image.ReadPixels(new Rect(0, 0, _camera.targetTexture.width, _camera.targetTexture.height), 0, 0);
    32.         image.Apply();
    33.         RenderTexture.active = activeRenderTexture;
    34.  
    35.         byte[] bytes = image.EncodeToPNG();
    36.         Destroy(image);
    37.  
    38.         Debug.Log(bytes);
    39.  
    40.         File.WriteAllBytes(Path.Combine(Application.persistentDataPath, "output.png"), bytes);
    41.     }
    42. }
    43.  
    Screenshot 2020-04-17 at 18.20.16.png Screenshot 2020-04-17 at 18.20.23.png

    Thanks in advance for any help.
     
  10. joepavitt

    joepavitt

    Joined:
    Oct 1, 2018
    Posts:
    2
    Just to post a fix to my own problem. My project (not sure how) had switched to `Linear` Color Space. It's in the `Project Settings -> Player -> Other Settings` in case anyone else hits this issue, this should be set to `Gamma`
     
  11. Deleted User

    Deleted User

    Guest

    Your project didn't switch to linear by itself, you switched it. You need linear to use Post Processing properly.
     
  12. StanleyCreative

    StanleyCreative

    Joined:
    Oct 20, 2015
    Posts:
    3
    So is there a way to use Linear AND fix the "darkened"-issue?
     
    lyxxx likes this.
  13. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    andrew_ES and ferverence like this.
  14. ViacheslavI

    ViacheslavI

    Joined:
    Dec 20, 2019
    Posts:
    22
    It doesn't work on Unity 2019.4.7f1. There is NullReferenceException on the line "Texture2D image = new Texture2D(Camera.targetTexture.width, Camera.targetTexture.height);" (Camera.targetTexture is null)
     
    Ishidres, lyxxx and executer like this.
  15. lyxxx

    lyxxx

    Joined:
    Sep 13, 2020
    Posts:
    1
    That happened to me too. Then I realized you need to render the camera into a valid texture for it to work. Read the original post carefully again and you will see it mentioned this
     
    Ghosthawknight likes this.
  16. sivaprakashk

    sivaprakashk

    Joined:
    Aug 22, 2019
    Posts:
    1


    What is the use of fileCounter here ?
     
  17. Ghosthawknight

    Ghosthawknight

    Joined:
    May 14, 2019
    Posts:
    1
    Hey Dude it's totally normal to have this issue because you need to create a Render Texture using [Assets > Create > Render Texture] and assign it to Target Texture in your Camera component. It's from here where it take your Screenshot Size ---Texture2D(Camera.targetTexture.width, Camera.targetTexture.height)---.
    For more details about RenderTexture:
    https://docs.unity3d.com/Manual/class-RenderTexture.html
     
    BE_Foge likes this.
  18. JudahMantell

    JudahMantell

    Joined:
    Feb 28, 2017
    Posts:
    476
    I second this, I'm having the same issue. I need output images to have the proper coloring, but also use post-processing!
     
    Ishidres likes this.
  19. Full_Tilt

    Full_Tilt

    Joined:
    Apr 25, 2018
    Posts:
    18
    Looks like `fileCounter` is used to name the captured files, saved under the "Backgrounds" folder. I think is just so you can press the capture function key (or call the API) lots of times and it just increments a counter to name the captured file and avoid re-saving over top of others. So, this will work alright for each time you run the app, but between starts it will reset to zero and begin writing back over old files.
     
  20. starfoxy

    starfoxy

    Joined:
    Apr 24, 2016
    Posts:
    184
    Hey this is pretty great! If the default hotkey is F9, there was a conflict as that key was already in use for unity 2019.4 (for screen recording) which was doubly confusing. I remapped it to F10 and am able to take a screenshot.
     
  21. SniperED007

    SniperED007

    Joined:
    Sep 29, 2013
    Posts:
    345
    Sadly it looks like Image.EncodeToPNG() does not support Linear color space.
     
  22. SniperED007

    SniperED007

    Joined:
    Sep 29, 2013
    Posts:
    345
    Actually it does work, think you just have to make sure the RenderTexture and Texture2D are in the same format:

    This worked for me (as in it remained in Linear color space so that it wasn't dark)

    Code (CSharp):
    1. RenderTexture tempRT = new RenderTexture(800, 800, 24, RenderTextureFormat.ARGB32)
    2.     {
    3.       antiAliasing = 4
    4.     };
    5.  
    6.     FishThubnailCamera.targetTexture = tempRT;
    7.  
    8.     RenderTexture.active = tempRT;
    9.     FishThubnailCamera.Render();
    10.  
    11.     Texture2D image = new Texture2D(800, 800, TextureFormat.ARGB32, false, true);
    12.     image.ReadPixels(new Rect(0, 0, image.width, image.height), 0, 0);
    13.     image.Apply();
    14.    
    15.     RenderTexture.active = null;
    16.    
    17.     tex = image;
    18.  
    19.     byte[] bytes = image.EncodeToPNG();
    20.  
    21.     File.WriteAllBytes(ImageLocation(), bytes);
     
    Domboo and TigerHix like this.
  23. Stacklucker

    Stacklucker

    Joined:
    Jan 23, 2015
    Posts:
    82
    What Color Format did you set you Render Texture to in order for this to work and not show up dark again?
     
  24. SniperED007

    SniperED007

    Joined:
    Sep 29, 2013
    Posts:
    345
    same as my code above. RenderTextureFormat.ARGB32
     
  25. MindGem

    MindGem

    Joined:
    May 11, 2017
    Posts:
    84

    Hey! I'm bad at this. Am I suppose to attach something to the cameras "Target Texture"?
    It gives me a "NullReferenceException error" and points to this line
    1. Texture2D image = new Texture2D(Camera.targetTexture.width, Camera.targetTexture.height);
     
  26. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,742
    Yes, yes, you are.

    PLEASE don't reply to multi-year old threads to report your own NullReference.

    Null reference DOES NOT require a forum post EVER. Here's why:

    The answer is always the same... ALWAYS. It is the single most common error ever.

    Don't waste your life spinning around and round on this error. Instead, learn how to fix it fast... it's EASY!!

    Some notes on how to fix a NullReferenceException error in Unity3D
    - also known as: Unassigned Reference Exception
    - also known as: Missing Reference Exception
    - also known as: Object reference not set to an instance of an object

    http://plbm.com/?p=221

    The basic steps outlined above are:
    - Identify what is null
    - Identify why it is null
    - Fix that.

    Expect to see this error a LOT. It's easily the most common thing to do when working. Learn how to fix it rapidly. It's easy. See the above link for more tips.

    You need to figure out HOW that variable is supposed to get its initial value. There are many ways in Unity. In order of likelihood, it might be ONE of the following:

    - drag it in using the inspector
    - code inside this script initializes it
    - some OTHER external code initializes it
    - ? something else?

    This is the kind of mindset and thinking process you need to bring to this problem:

    https://forum.unity.com/threads/why-do-my-music-ignore-the-sliders.993849/#post-6453695

    Step by step, break it down, find the problem.

    Here is a clean analogy of the actual underlying problem of a null reference exception:

    https://forum.unity.com/threads/nul...n-instance-of-an-object.1108865/#post-7137032
     
Thread Status:
Not open for further replies.