Unity Texture2D.ReadPixels reads differently on Android - unity3d

We're trying to capture an area of the screen for our players to share. In the editor, the capture works fine, it captures the exact area. On Android however, the area seems to vary on the device itself. On one device it captures a much larger area and on another it's a smaller area. But, the final image size is always the same. This makes me think that a scaling or translation isn't correct when capturing the screen. However, visually everything scales correctly.
I'm using Unity 2020.1.10
I assign the panel I want a screenshot of to recT.
public class ElementScreenshot : MonoBehaviour
{
public RectTransform rectT; // Assign the UI element which you wanna capture
//public Image img;
int width; // width of the object to capture
int height; // height of the object to capture
// Use this for initialization
void Start()
{
width = System.Convert.ToInt32(rectT.rect.width);
height = System.Convert.ToInt32(rectT.rect.height);
}
public IEnumerator takeScreenShot(string filePath)
{
yield return new WaitForEndOfFrame(); // it must be a coroutine
Vector2 temp = rectT.transform.position;
var startX = temp.x - width / 2;
var startY = temp.y - height / 2;
var tex = new Texture2D(width, height, TextureFormat.RGB24, false);
tex.ReadPixels(new Rect(startX, startY, width, height), 0, 0);
tex.Apply();
// Encode texture into PNG
var bytes = tex.EncodeToPNG();
Destroy(tex);
File.WriteAllBytes(filePath, bytes);
}
public string Capture()
{
var filePath = Path.Combine(Application.temporaryCachePath, "shared_image.png");
StartCoroutine(takeScreenShot(filePath)); // screenshot of a particular UI Element.
return filePath;
}
}

The issue was with using rect. The following change fixed the issue.
public IEnumerator takeScreenShot(string filePath)
{
yield return new WaitForEndOfFrame(); // it must be a coroutine
var r = RectTransformToScreenSpace(rectT);
var tex = new Texture2D((int)r.width, (int)r.height, TextureFormat.RGB24, false);
tex.ReadPixels(r, 0, 0);
tex.Apply();
// Encode texture into PNG
var bytes = tex.EncodeToPNG();
Destroy(tex);
File.WriteAllBytes(filePath, bytes);
}
public static Rect RectTransformToScreenSpace(RectTransform transform)
{
Vector2 size = Vector2.Scale(transform.rect.size, transform.lossyScale);
return new Rect((Vector2)transform.position - (size * 0.5f), size);
}

Related

Unity 2d Camera Scale to Screen Size

I am looking to scale a orthographic camera in unity 2d to screen size.
When you edit your game you orient the camera in a way you want it to be, view all your objects etc.
But then when you maximise it your camera zooms out showing maybe other things you don't want to show
Images for example
Edit Mode View
Maximised
Thank you if you can answer my question
Best Regards
Dawid
Attach the following component to your camera. Make sure to set the target aspect ratio in the inspector (16/9 for example).
using UnityEngine;
[ExecuteAlways]
[RequireComponent(typeof(Camera))]
public class ViewportScaler : MonoBehaviour
{
private Camera _camera;
[Tooltip("Set the target aspect ratio.")]
[SerializeField] private float _targetAspectRatio;
private void Awake()
{
_camera = GetComponent<Camera>();
if (Application.isPlaying)
ScaleViewport();
}
private void Update()
{
#if UNITY_EDITOR
if (_camera)
ScaleViewport();
#endif
}
private void ScaleViewport()
{
// determine the game window's current aspect ratio
var windowaspect = Screen.width / (float) Screen.height;
// current viewport height should be scaled by this amount
var scaleheight = windowaspect / _targetAspectRatio;
// if scaled height is less than current height, add letterbox
if (scaleheight < 1)
{
var rect = _camera.rect;
rect.width = 1;
rect.height = scaleheight;
rect.x = 0;
rect.y = (1 - scaleheight) / 2;
_camera.rect = rect;
}
else // add pillarbox
{
var scalewidth = 1 / scaleheight;
var rect = _camera.rect;
rect.width = scalewidth;
rect.height = 1;
rect.x = (1 - scalewidth) / 2;
rect.y = 0;
_camera.rect = rect;
}
}
}
I'd like to expand on #sean-carey 's very useful answer.
I modified his code in order to add a min and max aspect ratio, so the viewport will be full screen in between those two aspects, and will pillarbox when < min and letterbox when > max
using UnityEngine;
[ExecuteAlways]
[RequireComponent(typeof(Camera))]
public class ViewportController : MonoBehaviour
{
private Camera _camera;
[Tooltip("Set the target aspect ratio.")]
[SerializeField] private float _minAspectRatio;
[SerializeField] private float _maxAspectRatio;
private void Awake()
{
_camera = GetComponent<Camera>();
if (Application.isPlaying)
ScaleViewport();
}
private void Update()
{
#if UNITY_EDITOR
if (_camera)
ScaleViewport();
#endif
}
private void ScaleViewport()
{
// determine the game window's current aspect ratio
var windowaspect = Screen.width / (float) Screen.height;
// current viewport height should be scaled by this amount
var letterboxRatio = windowaspect / _minAspectRatio;
var pillarboxRatio = _maxAspectRatio / windowaspect;
// if max height is less than current height, add letterbox
if (letterboxRatio < 1)
{
SetRect(1, letterboxRatio, 0, (1-letterboxRatio)/2);
}
// if min height is more than current height, add pillarbox
else if (pillarboxRatio < 1)
{
SetRect(pillarboxRatio, 1, (1-pillarboxRatio)/2, 0);
}
// full screen
else
{
SetRect(1, 1, 0, 0);
}
}
private void SetRect(float w, float h, float x, float y)
{
var rect = _camera.rect;
rect.width = w;
rect.height = h;
rect.x = x;
rect.y = y;
_camera.rect = rect;
}
}

Calling my background Sprite to fill screen in Unity

I know that there are many questions like this on the net but I haven't found a solution to my problem yet...
I'm trying to make a background image that fills the screen. In my game, there is a canvas and inside this Canvas there is a gameObject which has attached the sprite component.
Many solutions includes this code:
void Awake()
{
SpriteRenderer sr = GetComponent<SpriteRenderer>();
if (sr == null) return;
transform.localScale = new Vector3(1, 1, 1);
float width = sr.sprite.bounds.size.x;
float height = sr.sprite.bounds.size.y;
double variable = Camera.main.orthographicSize * 4.0;
float worldScreenHeight = (float)variable;
float worldScreenWidth = worldScreenHeight / Screen.height * Screen.width;
transform.localScale = new Vector2(worldScreenWidth / width, worldScreenHeight / height);
}
But I tried it and it makes the gameobject really small.
Any ideas?
At first, select your Canvas game object. in Canvas Scaler component set UI Scale Mode to Constant Pixel Size.
Note that, when you set UI Scale Mode to Scale With Screen Size, your image changes by screen width or height.
Also, Note that your image should be in the center of its parent. Set the transform position and rotation of your image to this:
Now Change your code to this:
void Awake()
{
SpriteRenderer sr = GetComponent<SpriteRenderer>();
if (sr == null) return;
transform.localScale = new Vector3(1, 1, 1);
float width = sr.sprite.bounds.size.x;
float height = sr.sprite.bounds.size.y;
transform.localScale = new Vector2(Screen.width / width, Screen.height / height);
}
Hope it helps you.

Indentation messing up Nested Property Drawers

I have this Class called ToggledFloat. It wraps a float but also adds a bool telling if it is enabled or not.
[System.Serializable]
public class ToggledFloat
{
public float value;
public bool enabled = false;
}
[System.Serializable]
public class ClassWIthNestedProp
{
public ToggledFloat aTogFloat;
}
public class Test : MonoBehaviour
{
public ClassWIthNestedProp eCA;
public ToggledFloat tF;
}
I can easily make a Custom Property Drawer for this, and it looks right, when the editor draws this property at indentation level "0". However when I look at ToggledFloat properties nested inside another property they look wrong.
My propertyDrawer looks like this:
[CustomPropertyDrawer(typeof(ToggledFloat))]
public class ToggledPropertyEditor : PropertyDrawer
{
public override float GetPropertyHeight (SerializedProperty property, GUIContent label)
{
var propVal = property.FindPropertyRelative("value");
float contentUnfoldedHeight = EditorGUI.GetPropertyHeight (propVal, label);
return contentUnfoldedHeight;
}
// Draw the property inside the given rect
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
int iL = EditorGUI.indentLevel;
//EditorGUI.indentLevel = 0; //- Set this to "0" to make thing work but non-indented
SerializedProperty valueProp = property.FindPropertyRelative("value");
SerializedProperty enabledProp = property.FindPropertyRelative("enabled");
//Label left of checkbox
float labelWidth = GUI.skin.label.CalcSize(label).x;
Rect labelRect = new Rect(0/*position.x*/, position.y, labelWidth, 16);
EditorGUI.LabelField(labelRect, label, GUIContent.none);
EditorGUI.DrawRect(labelRect, new Color(0,0,1,.1f));
//Checkbox
Rect enableRect = new Rect();
enableRect.xMin = labelRect.xMax;
enableRect.yMin = labelRect.yMin;
enableRect.width = 16;
enableRect.height = 16;
EditorGUI.PropertyField(enableRect, enabledProp, GUIContent.none);
EditorGUI.DrawRect(enableRect, new Color(0,1,0,.1f));
//Value
Rect valueRect = new Rect();
valueRect.xMin = enableRect.xMax;
valueRect.yMin = enableRect.yMin;
valueRect.xMax = position.xMax;
valueRect.yMax = position.yMax;
EditorGUI.DrawRect(valueRect, new Color(1,0,0,.1f));
bool enabled = GUI.enabled;
GUI.enabled = enabledProp.boolValue;
EditorGUI.PropertyField(valueRect, valueProp, new GUIContent(""), true);
GUI.enabled = enabled;
EditorGUI.indentLevel = iL;
}
}
When the inspector draws an instance of class Test, it looks like this:
The Colored rects are only there for allowing me to debug where those rects actually are. The weird thing is that the text-labels are offset from the coloured rects, even though they get the same rect as argument. This is of course only a problem if I want coloured rects in my inspector - but the real problem is that it seems that this offset problem causes nested checkboxes to not work. I cannot click checkboxes on a nested property.
If I then explicitly set EditorGUI.IndenLevel = 0, then the coloured rects and the labels coincide and the toggle buttons work properly - but I then loose the automatic indentation, that I would really like to use.
Can someone tell me what I am overlooking
The DrawRect method doesn't consider the indent level. You have to adjust rect yourself to draw indented rectangle. Fortunately, There is a function to get the intented rect.
From UnityCsReference EditorGUI
static void DrawIndentedRect(Rect rect, Color color)
{
EditorGUI.DrawRect(EditorGUI.IndentedRect(rect), color);
}
Captured Inspector IndentedRect
However it doesn't work properly.
From the implementation of IndentedRect, it reduces the width of rect.
static void DrawIndentedRect(Rect rect, Color color)
{
// Indent per level is 15
rect.x += EditorGUI.indentLevel * 15;
EditorGUI.DrawRect(rect, color);
}
or
static void DrawIndentedRect(Rect rect, Color color)
{
var indentedRect = EditorGUI.IndentedRect(rect);
indentedRect.width = rect.width;
EditorGUI.DrawRect(indentedRect, color);
}
Captured Inspector DrawIndentedRect

How to make the quadrangles can do drag and drop in Unity Editor Window?

How to create an interactive GUI object in Unity - Editor Window?
I can draw the static quadrangle like code below. But I want the effect like the reference video that starts from 0:22 to 0:30.
public class EditorCanvas {
public static void DrawQuad(int x, int y, int width, int height, Color color) {
Rect rect = new Rect(x, y, width, height);
Texture2D texture = new Texture2D(1, 1);
texture.SetPixel(0, 0, color);
texture.Apply();
GUI.skin.box.normal.background = texture;
GUI.Box(rect, GUIContent.none);
}
}
public class MyWindow : EditorWindow {
void OnGUI() {
EditorCanvas.DrawQuad(100, 75, 50, 50, Color.black);
}
}
You can declare a rect which contains the current position of your box. In this example the position is initialized to 0,0 for a size of 100,100.
Then for each time you move the mouse while clicking (EventType.MouseDrag) you add the mouse movement since the last event (Event.delta) to the box position.
In order to get a smoothly drag & drop you have to tell to unity that you have an event so he can repaint. (Event.use)
Rect _boxPos = new Rect(0, 0, 100, 100);
void OnGUI()
{
if (Event.current.type == EventType.MouseDrag &&
_boxPos.Contains(Event.current.mousePosition))
{
_boxPos.position += Event.current.delta;
}
GUI.Box(_boxPos, "test");
if (Event.current.isMouse)
Event.current.Use();
}
So now you can easily adapt your DrawQuad method.

Take picture from Gallery And Crop that picture in the selected circle portion In Unity 3d?

I am working on a project. I want to take a picture from gallery of android device.
1. How can i take a picture from gallery of android device?
2. How can i Crop the picture selected circle portion in unity3d?
You can use below code for cropping image. I only pass Texture2D and set centre and radius automatically according to texture; but you can pass them manually if you want to.
Texture2D RoundCrop (Texture2D sourceTexture) {
int width = sourceTexture.width;
int height = sourceTexture.height;
float radius = (width < height) ? (width/2f) : (height/2f);
float centerX = width/2f;
float centerY = height/2f;
Vector2 centerVector = new Vector2(centerX, centerY);
// pixels are laid out left to right, bottom to top (i.e. row after row)
Color[] colorArray = sourceTexture.GetPixels(0, 0, width, height);
Color[] croppedColorArray = new Color[width*height];
for (int row = 0; row < height; row++) {
for (int column = 0; column < width; column++) {
int colorIndex = (row * width) + column;
float pointDistance = Vector2.Distance(new Vector2(column, row), centerVector);
if (pointDistance < radius) {
croppedColorArray[colorIndex] = colorArray[colorIndex];
}
else {
croppedColorArray[colorIndex] = Color.clear;
}
}
}
Texture2D croppedTexture = new Texture2D(width, height);
croppedTexture.SetPixels(croppedColorArray);
croppedTexture.Apply();
return croppedTexture;
}