Mobile Camera Feed in Portrait Mode

Hi everyone,

I am having a major issue with getting the camera feed of mobile devices to display correctly in portrait mode. I have managed to make it work (display in the correct orientation and position) in landscape mode by manipulating the GUI Texture of an object in the scene; performing a scale transformation on the vector and assigning a WebCamTexture to the GUI Texture of the object.

To make things easier to grasp, I would like to know a solution to this for Android, not iOS, as I do not have a developer account, however I understand that a solution would be similar on either platforms.

For reference, I have used this webpage as a rough guide:
http://www.mat-d.com/site/unity-3d-webcamtexture-rotation-problem-with-android-how-to-rotate-the-camera-picture/

As a note, I know it may not seem logical to view the camera feed in portrait mode, but what I’m doing specifically requires the feature to be in portrait.

Thanks :slight_smile:

EDIT: This bit of code does the copying.
using UnityEngine;
using System.Collections;
using System.IO;

public class CameraBlitBehaviour : MonoBehaviour {
	
	WebCamTexture cam_texture;
	int requestedTextureWidth, requestedTextureHeight;
	bool appPaused = false;
	//GameObject camFeedPlane2;
	// Use this for initialization
	void Start () {
		requestedTextureWidth = 400;
		requestedTextureHeight = 400;
		WebCamDevice[] cam_devices = WebCamTexture.devices;
		
		if(Application.platform == RuntimePlatform.IPhonePlayer)
		{
			gameObject.transform.localScale = new Vector3(1f*gameObject.transform.localScale.x, 1f, -1f*gameObject.transform.localScale.z);
			cam_texture = new WebCamTexture(cam_devices[0].name, 640, 480, 30);
		}
		else
		{
			cam_texture = new WebCamTexture(cam_devices[0].name, 480, 640, 30);
		}
		//if(Application.HasUserAuthorization(UserAuthorization.WebCam))
		{
			if(cam_texture != null)
				cam_texture.Play();
		}

		
	}
	void OnGUI()
	{
		if(GUI.Button(new Rect(110,10, 100, 60), new GUIContent("capture")))
		{
			cam_texture.Pause();
			BlitImage();
		}
		int wOffset = cam_texture.width - requestedTextureWidth;
		int hOffset = cam_texture.height - requestedTextureHeight;
		GUI.Label(new Rect(10,500,600,50), Application.persistentDataPath);
		GUI.Label(new Rect(10,550,600,50), "sourceTex"+ cam_texture.width+" "+ cam_texture.height);	
		GUI.Label(new Rect(10,600,600,50), ""+wOffset/2+" "+hOffset/2);
	}
	/// <summary>
	/// Blits the image. Well it's not exactly blitting as Unity doesnt provide a blitting interface. The Graphics.Blit blits the texture
	/// to a render texture which is of not much help. We copy the image to a temporary buffer and then copy it to a Texture2D.
	/// </summary>
	void BlitImage()
	{
		
		int width = cam_texture.width;
		int height = cam_texture.height;
		//assuming the source texture is always greater than the destination texture
		int wOffset = width - requestedTextureWidth;
		int hOffset = height - requestedTextureHeight;
		Debug.Log(width+" : "+height);
		Debug.Log(cam_texture.requestedWidth+" : "+cam_texture.requestedHeight);
	
		BlitImageAndroid(wOffset/2, hOffset/2);
		BlitImageIOS(wOffset/2,hOffset/2);
	}
	/// <summary>
	/// In this function we blit the texture using the calculated texture dimensions. 
	/// </summary>
	/// <param name='widthOffset'>
	/// Width offset.
	/// </param>
	/// <param name='heightOffset'>
	/// Height offset.
	/// </param>
	void BlitImageAndroid(int widthOffset, int heightOffset)
	{
		Debug.Log("starting blitting from"+widthOffset+" :"+(requestedTextureHeight + heightOffset));
		Texture2D bufferTexture = new Texture2D(requestedTextureWidth, requestedTextureHeight, TextureFormat.ARGB32, false);
		
		//This is the android version of blitting
		for(int x = (requestedTextureWidth + widthOffset), destY = 0; x > widthOffset; x--, destY++)
		{
			for(int y = heightOffset, destX=0; y < (requestedTextureHeight+ heightOffset); y++, destX++)
			{
				bufferTexture.SetPixel(destX,destY, cam_texture.GetPixel(x,y));
			}
		}
		
		bufferTexture.Apply();
		
		byte[] pngData = bufferTexture.EncodeToPNG();
		if(File.Exists(Application.persistentDataPath+"/capturedPic.png"))
		{
			File.Delete(Application.persistentDataPath+"/capturedPic.png");
		}
		File.WriteAllBytes(Application.persistentDataPath+"/capturedPic.png",pngData);
		Debug.Log("pic saved to"+Application.persistentDataPath);
	}
	/// <summary>
	/// In this function we flip the pixels around and copy them as we are flipping the texture using localscale, 
	/// hence the destX start from requestedTextureHeight and copies until 0
	/// </summary>
	/// <param name='widthOffset'>
	/// Width offset.
	/// </param>
	/// <param name='heightOffset'>
	/// Height offset.
	/// </param>
	void BlitImageIOS(int widthOffset, int heightOffset)
	{
		Texture2D destTexture = new Texture2D(requestedTextureWidth, requestedTextureHeight, TextureFormat.ARGB32, false);
		for(int x = (requestedTextureWidth + widthOffset), destY = 0; x > widthOffset; x--, destY++)
		{
			for(int y = heightOffset, destX=requestedTextureHeight; y < (requestedTextureHeight+ heightOffset); y++, destX--)
			{
				destTexture.SetPixel(destX,destY, cam_texture.GetPixel(x,y));
			}
		}
				
		destTexture.Apply();
	
		byte[] pngData = destTexture.EncodeToPNG();
		if(File.Exists(Application.persistentDataPath+"/capturedPic2.png"))
		{
			File.Delete(Application.persistentDataPath+"/capturedPic2.png");
		}
		File.WriteAllBytes(Application.persistentDataPath+"/capturedPic2.png",pngData);
		Debug.Log("pic saved to"+Application.persistentDataPath);

	}
	/// <summary>
	/// Blits the image using the GetPixels function.
	/// </summary>
	/// <param name='widthOffset'>
	/// Width offset.
	/// </param>
	/// <param name='heightOffset'>
	/// Height offset.
	/// </param>
	void BlitImageNormal(int widthOffset, int heightOffset)
	{
		Texture2D destTexture = new Texture2D(requestedTextureWidth, requestedTextureHeight, TextureFormat.ARGB32, false);

		Color[] textureData = cam_texture.GetPixels(widthOffset, heightOffset,requestedTextureWidth,requestedTextureHeight);
		
		destTexture.SetPixels(textureData);
		destTexture.Apply();
	
		byte[] pngData = destTexture.EncodeToPNG();
		if(File.Exists(Application.persistentDataPath+"/capturedPic3.png"))
		{
			File.Delete(Application.persistentDataPath+"/capturedPic3.png");
		}
		File.WriteAllBytes(Application.persistentDataPath+"/capturedPic3.png",pngData);
		Debug.Log("pic saved to"+Application.persistentDataPath);
	}
	
	// Update is called once per frame
	void Update () {
		if(!appPaused)
		{
		    if (cam_texture != null && cam_texture.didUpdateThisFrame)
		    {
	
				gameObject.renderer.material.mainTexture = cam_texture;
	
		    }
		}
				
	}
	public void StopCamera()
	{
		cam_texture.Stop();
	}
	void OnApplicationPause(bool pause)
	{
		if(pause)
		{
			appPaused = true;
			cam_texture.Stop();
		}
		else
		{
			appPaused = false;
			if(cam_texture != null)
				cam_texture.Play();
			
		}
	}
}

I am also facing the same problem. My app also needs the camera feed to be displayed in portrait mode. One work around I used was to use a plane that faced the camera with rotation set to (0,270,90). In iOS I would have to flip the plane by modifying the localscale. gameObject.transform.localScale = new Vector3(1f*gameObject.transform.localScale.x, 1f, -1f*gameObject.transform.localScale.z)

Then I am setting the WebCamTexture to the plane’s material texture. I needed a 400x400 image out of the WebCamTexture with dimensions 640x480. The SetPixel and GetPixel start reading the image from bottom left corner of the texture.