I'm building a web based app that takes a screenshot of a play area and then posts it to a web server to be called up in a image gallery for other to view. Currently, when running in the editor I can take the screenshot and save it locally but that won't work once it's deployed. I don't know how to take that screenshot and save it to a texture (rather than to disk) to then upload to my server. How do I do this? I'm new at this and especially new at Render Texture functionality. Can someone help me sort this out?
I have found this Snippet on a forum here. But not tested by myself on WebPlayer.
using UnityEngine;
using System.Collections;
public class Main : MonoBehaviour
{
private string _data = string.Empty;
public Texture2D bg;
void OnGUI()
{
if (GUI.Button(new Rect(Screen.width*0.5f-32,32,64,32),"Save"))
StartCoroutine(ScreeAndSave());
}
IEnumerator ScreeAndSave()
{
yield return new WaitForEndOfFrame();
var newTexture = ScreenShoot(Camera.main, bg.width, bg.height);
LerpTexture(bg, ref newTexture);
_data = System.Convert.ToBase64String(newTexture.EncodeToPNG());
Application.ExternalEval("document.location.href='data:octet-stream;base64," + _data + "'");
}
private static Texture2D ScreenShoot(Camera srcCamera, int width, int height)
{
var renderTexture = new RenderTexture(width, height, 0);
var targetTexture = new Texture2D(width, height, TextureFormat.RGB24, false);
srcCamera.targetTexture = renderTexture;
srcCamera.Render();
RenderTexture.active = renderTexture;
targetTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
targetTexture.Apply();
srcCamera.targetTexture = null;
RenderTexture.active = null;
srcCamera.ResetAspect();
return targetTexture;
}
private static void LerpTexture(Texture2D alphaTexture, ref Texture2D texture)
{
var bgColors = alphaTexture.GetPixels();
var tarCols = texture.GetPixels();
for (var i = 0; i < tarCols.Length; i++)
tarCols[i] = bgColors[i].a > 0.99f ? bgColors[i] : Color.Lerp(tarCols[i], bgColors[i], bgColors[i].a);
texture.SetPixels(tarCols);
texture.Apply();
}
}
Reference Link
Related
using UnityEngine;
using System.Collections;
public class HiResScreenshots : MonoBehaviour
{
public Camera camera;
public int resWidth = 2550;
public int resHeight = 3300;
private bool takeHiResShot = false;
public static string ScreenShotName(int width, int height)
{
return string.Format("{0}/screenshots/screen_{1}x{2}_{3}.png",
Application.dataPath,
width, height,
System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
}
public void TakeHiResShot()
{
takeHiResShot = true;
}
void LateUpdate()
{
takeHiResShot |= Input.GetKeyDown("k");
if (takeHiResShot)
{
RenderTexture rt = new RenderTexture(resWidth, resHeight, 24);
camera.targetTexture = rt;
Texture2D screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);
camera.Render();
RenderTexture.active = rt;
screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
camera.targetTexture = null;
RenderTexture.active = null; // JC: added to avoid errors
Destroy(rt);
byte[] bytes = screenShot.EncodeToPNG();
string filename = ScreenShotName(resWidth, resHeight);
System.IO.File.WriteAllBytes(filename, bytes);
Debug.Log(string.Format("Took screenshot to: {0}", filename));
takeHiResShot = false;
}
}
}
I'm getting exception DirectoryNotFoundException and when pressing once the k key it's taking screenshots non stop and I want it to take one screenshot each time when pressing the k key.
I'm getting exception DirectoryNotFoundException
You can use Path.GetDirectoryName in order to get the full directory path but without the filename from the filename.
And then you can simply use Directory.CreateDirectory
Creates all directories and subdirectories in the specified path unless they already exist.
So.you don't even have to check whether it exists. (Of course you still need the permission to do so)
when pressing once the k key it's taking screenshots non stop and I want it to take one screenshot each time when pressing the k key.
This is a follow up error. Since you get an exception you never reach the line
takeHiResShot = false;
In general rather use a Coroutine here like e.g.
public void TakeHiResShot()
{
StartCoroutine (ScreenshotRoutine ());
}
void Update()
{
if(Input.GetKeyDown("k"))
{
StartCoroutine (ScreenshotRoutine ());
}
}
private IEnumerator ScreenshotRoutine ()
{
yield return new WaitForEndOfFrame();
var rt = new RenderTexture(resWidth, resHeight, 24);
camera.targetTexture = rt;
var screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);
camera.Render();
RenderTexture.active = rt;
screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
camera.targetTexture = null;
RenderTexture.active = null; // JC: added to avoid errors
Destroy(rt);
var bytes = screenShot.EncodeToPNG();
var filename = ScreenShotName(resWidth, resHeight);
var directory = Path.GetDirectoryName(filename);
Directory.CreateDirectory(directory);
System.IO.File.WriteAllBytes(filename, bytes);
Debug.Log($"Took screenshot to: /"{filename}/"", this);
}
This makes it imposible to land in an endless loop in case there was an error
I made this very simple script which should be attached to inactive camera Gameobject with renderTexure.
If the camera Gameobject is active, this suppose to record only one frame to the renderTexure and then save to a path as a .png. After that the camera should be disabled.
public static string path;
void Update()
{
StartCoroutine(SSOT());
}
public static void SaveRTToFileToSharing()
{
RenderTexture rt = Resources.Load<RenderTexture>(#"Render Texure/ScreenShot") as RenderTexture;
RenderTexture.active = rt;
Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
//RenderTexture.active = null;
tex.Apply();
byte[] bytes;
bytes = tex.EncodeToPNG();
path = Application.persistentDataPath + "Test Shot.png";
System.IO.File.WriteAllBytes(path, bytes);
Destroy(tex);
}
IEnumerator SSOT()
{
yield return new WaitForEndOfFrame();
SaveRTToFileToSharing();
gameObject.SetActive(false);
}
The script works as I intended but i'm not sure if it really record only one frame or more. What would you change if you gonna change anything ?
It should be fine since you deactivate the object in the same frame you enabled it.
Anyway just to be sure I would instead of using Update which is called every frame simply move that StartCoroutine call to the OnEnable which is called everytime the GameObject gets activated.
private void OnEnable()
{
StartCoroutine(SSOT());
}
To make it a bit more performant there are a few things you could do only once and reuse them - depends ofcourse on your priorities: If your priority is little memory usage and performance doesn't matter for the screenshot then you are fine ofcourse. Otherwise I would probably do it like
private RenderTexture rt;
private Texture2D tex;
private string path;
private void Awake ()
{
// Do this only once and keep it around while the game is running
rt = (RenderTexture) Resources.Load<RenderTexture>(#"Render Texure/ScreenShot");
tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
path = Application.persistentDataPath + "Test Shot.png";
}
private void SaveRTToFileToSharing()
{
RenderTexture.active = rt;
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
//RenderTexture.active = null;
tex.Apply();
byte[] bytes;
bytes = tex.EncodeToPNG();
// This happens async so your game can continue meanwhile
using (var fileStream = File.Open(path, FileMode.OpenOrCreate, FileAccess.Write))
{
fileStream.WriteAsync(bytes, 0, bytes.Length);
}
}
This is in relation to this post Save AcquireCameraImageBytes() from Unity ARCore to storage as an image
I tried the steps mentioned by
#JordanRobinson I am having a similar issue of seeing just a gray square. I keep re-reading his update, and I am not clear how step 2 (creating a texture reader) ties to step 3. I added the update function to call Frame.CameraImage.AcquireCameraImageBytes. I think missing something.
I feel I am close as it is saving an image (just a gray nothing image :-) Any help you can offer will be greatly appreciated
Here is my code
private Texture2D m_TextureRender;
private TextureReader m_CachedTextureReader;
void Start ()
{
m_CachedTextureReader = GetComponent<TextureReader>();
m_CachedTextureReader.OnImageAvailableCallback += OnImageAvailable;
QuitOnConnectionErrors ();
}
void Update () {
Screen.sleepTimeout = SleepTimeout.NeverSleep;
using (var image = Frame.CameraImage.AcquireCameraImageBytes())
{
if (!image.IsAvailable)
{
return;
}
OnImageAvailable(TextureReaderApi.ImageFormatType.ImageFormatColor,
image.Width, image.Height, image.Y, 0);
}
}
private void OnImageAvailable(TextureReaderApi.ImageFormatType format, int width, int height, System.IntPtr pixelBuffer, int bufferSize)
{
if (format != TextureReaderApi.ImageFormatType.ImageFormatColor)
{
Debug.Log("No edge detected due to incorrect image format.");
return;
}
if (m_TextureRender == null || m_EdgeDetectionResultImage == null || m_TextureRender.width != width || m_TextureRender.height != height)
{
m_TextureRender = new Texture2D(width, height, TextureFormat.RGBA32, false, false);
m_EdgeDetectionResultImage = new byte[width * height * 4];
m_TextureRender.width = width;
m_TextureRender.height = height;
}
System.Runtime.InteropServices.Marshal.Copy(pixelBuffer, m_EdgeDetectionResultImage, 0, bufferSize);
// Update the rendering texture with the sampled image.
m_TextureRender.LoadRawTextureData(m_EdgeDetectionResultImage);
m_TextureRender.Apply();
var encodedJpg = m_TextureRender.EncodeToJPG();
var path = Application.persistentDataPath;
File.WriteAllBytes(path + "/test2.jpg", encodedJpg);
}
I made a simple app for android devices that uses mobile's camera to take a photo and save it in internal storage folder /mnt/sdcard/DCIM/Camerizeman/
Photos are saved correctly but the problem that i am faceing is that i can't see the photos from mobile's gallery. I can see them correctly if i use a file manager or reboot me device. i am searchig 10 days now and the problem is that i have to refresh the gallery after saving the image.
I didn't found any working solution.
my code is bellow:
RenderTexture renderTex;
Texture2D screenshot;
Texture2D LoadScreenshot;
int width = Screen.width; // for Taking Picture
int height = Screen.height; // for Taking Picture
string fileName;
string myScreenshotLocation;
string screenShotName = "MyImage_AR_" + System.DateTime.Now.ToString("yyyy-MM-dd-HHmmss") + ".png";
public void Snapshot ()
{
StartCoroutine (CaptureScreen ());
}
public IEnumerator CaptureScreen ()
{
yield return null; // Wait till the last possible moment before screen rendering to hide the UI
//GameObject.FindGameObjectWithTag("Snapshoot").SetActive(false);
yield return new WaitForEndOfFrame (); // Wait for screen rendering to complete
if (Screen.orientation == ScreenOrientation.Portrait || Screen.orientation == ScreenOrientation.PortraitUpsideDown) {
mainCamera = Camera.main.GetComponent<Camera> (); // for Taking Picture
renderTex = new RenderTexture (width, height, 24);
mainCamera.targetTexture = renderTex;
RenderTexture.active = renderTex;
mainCamera.Render ();
screenshot = new Texture2D (width, height, TextureFormat.RGB24, false);
screenshot.ReadPixels (new Rect (0, 0, width, height), 0, 0);
screenshot.Apply (); //false
RenderTexture.active = null;
mainCamera.targetTexture = null;
}
if (Screen.orientation == ScreenOrientation.LandscapeLeft || Screen.orientation == ScreenOrientation.LandscapeRight) {
mainCamera = Camera.main.GetComponent<Camera> (); // for Taking Picture
renderTex = new RenderTexture (height, width, 24);
mainCamera.targetTexture = renderTex;
RenderTexture.active = renderTex;
mainCamera.Render ();
screenshot = new Texture2D (height, width, TextureFormat.RGB24, false);
screenshot.ReadPixels (new Rect (0, 0, height, width), 0, 0);
screenshot.Apply (); //false
RenderTexture.active = null;
mainCamera.targetTexture = null;
}
myScreenshotLocation = myFolderLocation + screenShotName;
File.WriteAllBytes (myFolderLocation + screenShotName, screenshot.EncodeToPNG ());
}
Please help!
The second working solution is:
using (AndroidJavaClass jcUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (AndroidJavaObject joActivity = jcUnityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
using (AndroidJavaObject joContext = joActivity.Call<AndroidJavaObject>("getApplicationContext"))
using (AndroidJavaClass jcMediaScannerConnection = new AndroidJavaClass("android.media.MediaScannerConnection"))
using (AndroidJavaClass jcEnvironment = new AndroidJavaClass("android.os.Environment"))
using (AndroidJavaObject joExDir = jcEnvironment.CallStatic<AndroidJavaObject>("getExternalStorageDirectory"))
{
jcMediaScannerConnection.CallStatic("scanFile", joContext, new string[] { YOURFULL IMAGE PATH}, null, null);
}
Use MediaScannerConnection. You need the image "scanned into" the gallery.
Native Java Code goes something like:
MediaScannerConnection.scanFile(unityPlayerActivity, new String[]{externalImagePath}, null, null);
May Create plugin - Unity Android plugin tutorial (1/3) Fundamentals -, or use AndroidJavaClass.CallStatic to invoke a MediaScannerConnection.scanFile.
So I've created this class based off of the Texture2D.EncodeToPNG code example on Unity's website. I'm not getting any errors when I execute it, but I'm also not seeing a new file created. What am I doing wrong here?
public class CreateJPG : MonoBehaviour
{
public int width = 1050;
public int height = 700;
string fileName;
string filePath;
// Texture2D tex;
public void GrabJPG () {
SaveJPG();
Debug.Log("GrabJPG Executing");
}
IEnumerator SaveJPG()
{
// We should only read the screen buffer after rendering is complete
yield return new WaitForEndOfFrame();
// Create a texture the size of the screen, RGB24 format
Texture2D tex = new Texture2D(width, height, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(0,0,width,height),0,0);
tex.Apply();
// Encode texture into JPG
byte[] bytes = tex.EncodeToJPG(60);
Object.Destroy(tex);
// Get filePrefix from GameSetup array index
GameObject init = GameObject.FindGameObjectWithTag("Initializer");
GameSetup gameSetup = init.GetComponent<GameSetup>();
string prefix = gameSetup.filePrefix;
string subDir = gameSetup.subDir;
string dtString = System.DateTime.Now.ToString("MM-dd-yyyy_HHmmssfff");
fileName = prefix+dtString+".jpg";
filePath = "/Users/kenmarold/Screenshots/"+subDir+"/";
Debug.Log("SaveJPG Executing");
File.WriteAllBytes(filePath+fileName, bytes);
Debug.Log("Your file was saved at " + filePath+subDir+prefix+fileName);
if(width > 0 && height > 0)
{
}
}
}
You didn't start your coroutine, you need to call StartCodoutine in GrabJPG:
StartCoroutine(SaveJPG());
https://docs.unity3d.com/Manual/Coroutines.html
https://unity3d.com/learn/tutorials/modules/intermediate/scripting/coroutines
P. S. By the way, you can use Application.CaptureScreenshot