Texture2D.ReadPixels( ) is rotated on iPhone

Hi,

I have created a Texture2D tex = new Texture2D( … ) and then used
tex.ReadPixels( … ) to create a screenshot of the screen. Then I displayed the result texture on a plane.
In the Editor works GREAT!

On the iPhone the result texture is rotated 90 degrees instead of being like the screen in landscape. It’s like it’s storing the result texture in portrait mode, although the screenshot is made in Landscape mode. I’ve set the Orientation to Landscape Left in my iPhone Build Settings so I’m sure it’s not related to this.
I’m using Unity 3.3.

In the editor it works ok but on the iPhone or iPad the result texture is rotated.

Anyone know why this could happen? Is it a bug in ReadPixels on iOS? Am I missing something?

I have 3 days hitting my head on the wall and I can’t seem to find out what’s causing this. :frowning:

It looks like this is a bug (even if it is an efficiency consideration for the iPhone, then the editor should either have consistent behaviour or the inconsistency should be documented). I’ve filed a bug report for this issue, case number 404624 if you want to follow it up in the future.

For the time being, I think the workaround is to allocate the texture so that it has the correct dimensions for a portrait image and then rotate it yourself from code. This is basically just a matter of creating another Texture2D with the same dimensions, but in landscape, and copying the pixel data over:-

function PortraitToLandscape(origTex: Texture2D) {
	var origPix = origTex.GetPixels();
	var newPix = new Color[origPix.Length];
	
	for (x = 0; x < origTex.width; x++) {
		for (y = 0; y < origTex.height; y++) {
			var newX = y;
			var newY = origTex.width - x - 1;
			newPix[newY * origTex.height + newX] = origPix[y * origTex.width + x];
		}
	}
	
	var newTex = new Texture2D(origTex.height, origTex.width);
	newTex.SetPixels(newPix);
	newTex.Apply();
	
	return newTex;
}

Thanks Andeeeeee! You’re awesome MAN! Your solution is great! I’m gonna post a second
solution that worked in the end for me.

I’ve also done some digging around and I’ve found something else. If I use a RenderTexture in which the screen is rendered using the main camera the result RenderTexture is NOT rotated and is ok, BUT…BUUUT this works ok ONLY for OpenGL ES 2.0. On devices
with OpenGL ES 1.1 the issue happens in both cases: when using ReadPixels( ) to read from the screen, or when using RenderTexture and a Camera to render to it.

Here is a script that I cooked-up that fixes the issue for Opengl ES 2.0:

ScreenShotController.cs

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]
public class ScreenShotController : MonoBehaviour {
	// The result texture of a previously made screen-shot.
	public static Texture2D result = null;	
	
	void Awake() {
		// deactivate this gameobject to avoid rendering the entire screen every frame with a second camera
		gameObject.active = false;
	}
	
	/**
	 * This coroutine does the screen shot with the currently set camera and stores the result in the static member of this component: @result
	 * You will have to call this coroutine like this: yield return StartCoroutine(ScreenShotController.WaitForScreenShot()). 
	 * That means that the call will wait one frame for the screenshot to be made so the result will be available in the next frame. By calling
	 * this coroutine with "yield" you can wait for it to finish and make sure that you have the screen-shot ready after it returns from the "yield".
	 */ 
	public IEnumerator WaitForScreenShot() {
		// prepare temporary RenderTexture for this temporary camera to render in it (we don't need a depth buffer for it or an alpha channel)
		RenderTexture screenShotResult = RenderTexture.GetTemporary(Screen.width, Screen.height, 0, RenderTextureFormat.RGB565);
		this.camera.targetTexture = screenShotResult;

		// activate this gameobject to allow it's camera component to render one frame
		gameObject.active = true;
		
		// wait for one frame to be rendered
		yield return new WaitForEndOfFrame();
		
		// activate the temporary RenderTexture
		RenderTexture.active = screenShotResult;
		
		// Convert the render texture to a Texture2D 
		result = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
		result.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
		result.Apply();
		
		// deactivate the temporary RenderTexture
		RenderTexture.active = null;
		
		// release the temporary RenderTexture
		RenderTexture.ReleaseTemporary(screenShotResult);
		
		// deactivate this temp camera to avoid rendering the entire screen every frame
		gameObject.active = false;
	}
	
	
	/**
	 * Destroy this component and it's parent gameobject. Any previous result screenshot made will remain referenced by the static member @result.
	 * You will have to destroy the result manually when you finish with it.
	 */
	public void DestroyMe() {
		Destroy(this.gameObject);
	}
	
	/**
	 * Helper function to create a temporary clone of a specified camera that will be used to make a screenshot of the screen
	 * @param srcCamera reference to the camera that will be temporarily be cloned and used to capture a screen-shot
	 * @return Returns a reference to this controller that will be able to do the screen shot by calling the coroutine @WaitForScreenShot().
	 */
	public static ScreenShotController GetScreenShotController(Camera srcCamera) {
		GameObject newTempCam = (Instantiate(srcCamera.gameObject) as GameObject);
		newTempCam.tag = "";
		
		// make sure that this temp camera has no audio listener active
		AudioListener audioListener = newTempCam.GetComponent<AudioListener>();
		if (audioListener != null)
			audioListener.enabled = false;
		    
		return newTempCam.AddComponent<ScreenShotController>();
	}
		
}

And here is a usage sample of the above script:

/**
 * This sample script is placed on the destination renderer that will render the screen-shot texture.
 */
public class RenderToTextureTest : MonoBehaviour {		
	
	// Use this for initialization
	IEnumerator Start () {
		// wait a little so you can see the small green destination area where the screen-shot will be displayed
		yield return new WaitForSeconds(2.5f);
		
		// In this sample script I want to render the screenshot on the renderer of this component
		ScreenShotController scrShotController = ScreenShotController.GetScreenShotController(Camera.main);
		
		// Wait for the screenshot to be made
		yield return StartCoroutine(scrShotController.WaitForScreenShot());
		
		Debug.Log("Screenshot DONE!");
		
		// Set the result texture on this renderer
		renderer.material.mainTexture = ScreenShotController.result;
		
		// You can destroy the screenshot controller after you're done, or you can hold on to it's reference to use it again later. In this
		// sample I destroy it using the built in method of ScreenShotController.
		scrShotController.DestroyMe();
	}
}

The downside is that in the ScreenShotController at one time there are 2 textures created for the screen. But this code can be modified to use only the RenderTexture directly and not copy the result into a Texture2D.

Hope it helps someone out there.

1 Like

This issue is fixed for the next release.

Andrius Kuznecovas
QA Engineer

Thanks for the info, guys!

I’m using andeee’s rotate script in my iPad app… but it’s quite a memory burden for an iPad 1!

Here’s a function to flip your image upside down (in case they’re in PortraitUpsideDown mode). It assumes that you’re using a member variable to store your image (and that you’ve filled it with data) since that’s what I had to do to keep memory usage under control.

	var screenImage  :  Texture2D;

	function flipImage ()
	{
		var srcPixels  =  screenImage.GetPixels();
		var outPixels  =  new Color[srcPixels.Length];
	
		var currentIndex  =  0;

		var startX  =  screenImage.width  - 1;
		var startY  =  screenImage.height - 1;

		for ( var y = startY; y >= 0; y -- ) {
			for ( var x = startX; x >= 0; x -- ) {
				outPixels[currentIndex ++] = srcPixels[y * screenImage.width + x];
			}
		}
		
		screenImage.SetPixels(outPixels);

		return;
	}