Scaling UI Image to Screen Size

Hi Guys,

I’ve been trying for ages to figure this out, but I’m stuck.

I’m trying to scale a UI image to the size of the screen (Perspective Camera), while keeping it’s aspect ratio (I’ve hit the maintain aspect ration tick box). I have a script, which works for my orthographic camera:

if (image)
        {
            float worldScreenHeight = (float)(cam.orthographicSize * 2.0);
            float worldScreenWidth = worldScreenHeight / Screen.height * Screen.width;
            float newLocalScale = (worldScreenWidth / image.sprite.bounds.size.x);
            transform.localScale = new Vector3(newLocalScale, newLocalScale, 1);
        }

and that works totally fine for orthographic. However when I try to use this on a perspective camera, it does not work. I’ve tried playing with camera.pixelHeight, camera.pixelWidth, Screen.Height, Screen.Width, camera.rect.height/width/x/y/z/size. I’ve figured out that Unity UI’s are distanced exactly 1 Unity unit from the camera, however I have no idea how to scale the object to that camera’s perspective. I’ve tried using all the layout tags. The closest one to working, was the aspect ratio fitter, however it just keeps an aspect ratio while forgetting the objects size I want to keep snap to.

Basically I want the image to stretch, but maintain aspect ratio, to the size of the screen. On whichever camera axis is larger, compared to the images aspect ratio. So it scales to snap and fit either to the current camera’s screen height or width, depending on whether the aspect ratio of the image’s width is higher than the camera’s aspect ratio width (This is on mobile, so the screen size will vary a lot).


- So the black is the screen size, and the red is the image. The image is scaling to the largest axis of the camera, while maintaining it’s aspect ratio.

Any help much appreciated guys, been stuck on this for ages!

At a first glance I would suggest you to create a iso camera with your script attached, then forward the output of the camera to a RawImage on a UI canvas that fills all the screen… does it sound right?

Hi beppim, thanks for replying. Would you be able to elaborate for me? Currently I have a camera, which has a canvas attached to it, and the canvas is Screen Space - Camera. When you say forward the output, what output do you mean? When you say a raw image, I’m guessing you mean using the image’s size, like I have here:

float newLocalScale = (worldScreenWidth / image.sprite.bounds.size.x);

That does sound right, it’s HOW I would do that which is the headache.

You have to create a Canvas first of all, that fits your entire screen. Set it to Screen Space Overlay, for example.
Then create a object in your canvas of type UI → Raw Image.
That object is like a “cinema screen” and you can set a camera to redirect its output there, instead of to the main monitor.
After creating the Raw Image, create a new Camera, of the type you need, don’t set it as MainCamera and configure it like you want.
In the Camera parameters set “Clear Flags” to Solid Colour if you want.
the set “Target Texture” to your Raw Image. That camera will redirect its input to the image.
I hope I was clear enough :slight_smile:
In this way, if I understood well what you need, there’s really no need to make calculations on the screen size or whatever, as soon as the output of the camera is a canvas that can fit the screen, and the camera itself could be moved and configured for example to stay always at the same distance from your object.

1 Like

Haha awesome! Yeah that works beautifully actually. All that number crunching for nothing lol, thanks :slight_smile:

Right I’ve been trying to get this working all day. It seems that putting it as a RenderTexture allows it to set to the size correctly… However the RenderTexture cannot be resized at runtime, which means there may be a problem with using it. Asides that, but it seems to make all my assets choppy. Even though I’ve set it to the correct screen size.

    void Start()
    {
        SetCurrentResolution();
    }

    public void SetCurrentResolution()
    {
        if (!bInitialised)
        {
            renderTexture = GetComponent<RawImage>().texture as RenderTexture;

            renderTexture.height = Screen.height;
            renderTexture.width = Screen.width;
        }

        RectTransform[] imgs = transform.parent.transform.GetComponentsInChildren<RectTransform>();

        for (int i = 0; i < imgs.Length; i++)
        {
            imgs[i].localScale = GetComponent<RectTransform>().localScale;
        }
    }

Any ideas?

Right after about 20 hours work I’ve found that render Texture doesn’t work on Android… :frowning:

Any ideas on the issue I posted guys?

Thanks!

For orthographic camera’s I have something that works perfectly:

    public bool SetInitialSize()
    {
        SpriteRenderer sr = GetComponent<SpriteRenderer>();

        if (sr)
        {
            float worldScreenHeight = (float)(cam.orthographicSize * 2.0);
            float worldScreenWidth = worldScreenHeight / Screen.height * Screen.width;
            float camAspectRatio = cam.aspect;

            float newLocalScale;

            if (camAspectRatio >= 1.45f)
            {
                newLocalScale = (worldScreenWidth / sr.sprite.bounds.size.x);
            }
            else
            {
                newLocalScale = (worldScreenHeight / sr.sprite.bounds.size.y);
            }

            transform.localScale = new Vector3(newLocalScale, newLocalScale, 1);
            SetScaleClamp(transform.localScale);

            Vector3 position;
            position.x = cam.transform.position.x;// -(sr.bounds.size.x * 0.5f); <-- Originally added as map's center point was bottom left. Now center.
            position.y = cam.transform.position.y;// -(sr.bounds.size.y * 0.5f); <-- Originally added as map's center point was bottom left. Now center.
            position.z = transform.position.z;
            transform.position = position;
        }
        else
        {
            Debug.Log("MapHandler::SetInitialSize() - SpriteRenderer Component not found!");
            return false;
        }
        return true;
    }

But for the perspective camera it scews out of sync, depending on the screen resolution. To me that suggests there is some maths there I can use to counter this and make it fit right, but I’ll be damned if I actually know the answer.

Can anyone help please?

I’m unsure if what you are trying to do is with the entire game or not. I have been doing a rework of our work software (software application, not game) in Unity and was on a long streak of camera issues. Basically, the software was designed for a 1000x600 window, and everything shrinks/stretches when the window resizes. This is so we can program for one “window size”, and any resizes won’t affect the look of our application, everything will stay within the same window, only shrunk/stretched.

Well, to recreate that in Unity took a while, as I didn’t understand the camera system very well. I also didn’t want to just use ortho cameras as we had some 3d rendered stuff previously. The previous system used OpenTK and C# and I didn’t do the graphics/camera system in it either.

So, I ended up with this script, attached to my camera (called MainCamera), and it works just like our previous setup. Everything shrinks/scales correctly according to the initial width/height I set.

using UnityEngine;
using System.Collections;

public class CameraManager : MonoBehaviour {
   
   public int startingWidth = 1000;
   public int startingHeight = 600;
   public float zNear = 0.3f;
   public float zFar = 1000f;

   private float _aspectRatio;
   private float _scaleWidth;
   private float _scaleHeight;
   private bool _updateView = false;
   private Camera _mainCamera = null;
   
   // Use this for initialization
   void Start () {
     _mainCamera = (Camera)(GameObject.Find ("MainCamera").GetComponent(typeof(Camera)));
     _aspectRatio = (float)startingWidth / (float)startingHeight;
   }
   
   // Update is called once per frame
   void Update () {
  if (Screen.width != startingWidth)
  {
  float internalScaleWidth = (float)Screen.width / startingWidth;
       if(internalScaleWidth != _scaleWidth)
       {
  _scaleWidth = (float)Screen.width / startingWidth;
         _updateView = true;
       }
     }
  if (Screen.height != startingHeight)
  {
  float internalScaleHeight = (float)Screen.height / startingHeight;
       if(internalScaleHeight != _scaleHeight)
       {
  _scaleHeight = (float)Screen.height / startingHeight;
         _updateView = true;
       }
     }
     
     if (_updateView) {
       _mainCamera.projectionMatrix = Matrix4x4.Perspective ((float)60, _aspectRatio, zNear, zFar);
       _updateView = false;
     }
     
   }
}
1 Like

Hiya detlion,

The code would suit, but I don’t want to override the game’s FoV using projectionMatrix. I use that a lot for a ‘zoom’.

You know believe it or not it’s only for one UI image, which just won’t do what I want it to do lol. When you tick ‘preserve aspect ratio’ in UI 4.6+, if the image doesn’t fit the rectTransform exactly it creates transparent space around the outside of the image in the rectTransform. So you can’t use the rectTransform to fill the image exactly to the screen size (with overlap on either width or height, like in my image at the top).

It’ll be some form of calculation like you have, but that one I can’t do. And it’ll be for that one UI image I want to fit the screen size without breaking it’s aspect ratio…

I’m just going to have to keep being a bull in a china shop until something clicks I think lol.

This is my UI helper code. Although this is designed for NGUI so it might not be exactly what you need, but maybe you can pull out something useful. Im sure I tried this script with Unity GUI and it worked

using UnityEngine;
using System.Collections;

public class UIHelper : MonoBehaviour {

    static float ratio = 0;

    public static float GetRatio()
    {
        if (ratio == 0)
            ratio = Screen.width / 1920f;

        return ratio;
    }

    public static int Ratio(int value)
    {
        return (int)(value * GetRatio());
    }

    public static void SetRatio(UITexture uiTexture)
    {
        uiTexture.cachedTransform.localPosition = uiTexture.cachedTransform.localPosition * GetRatio();
        uiTexture.SetDimensions((int)(uiTexture.width * ratio), (int)(uiTexture.height * GetRatio()));
    }

    public static void SetRatio(UISprite uiSprite)
    {
        uiSprite.cachedTransform.localPosition = uiSprite.cachedTransform.localPosition * GetRatio();
        uiSprite.SetDimensions((int)(uiSprite.width * ratio), (int)(uiSprite.height * GetRatio()));
    }

    public static void SetRatio(UILabel uiLabel)
    {
        uiLabel.cachedTransform.localPosition = uiLabel.cachedTransform.localPosition * GetRatio();
        uiLabel.SetDimensions((int)(uiLabel.width * ratio), (int)(uiLabel.height * GetRatio()));
        uiLabel.fontSize = (int)(uiLabel.fontSize * GetRatio());
    }

    public static void SetColor(UILabel uiLabel)
    {
        uiLabel.color = ColorSystem.GetSystemColor();

    }

    public static void SetColor(UITexture uiTexture)
    {
            uiTexture.color = ColorSystem.GetSystemColor();
    }

    public static void SetColor(UITexture uiTexture, Color color)
    {
             uiTexture.color = color;
    }

    public static void SetColor(UIInput uiInput)
    {
        uiInput.activeTextColor = ColorSystem.GetSystemColor();
        uiInput.selectionColor = ColorSystem.GetSystemColor();
        uiInput.caretColor = ColorSystem.GetSystemColor();
        uiInput.selectionColor = ColorSystem.GetSystemColor() * 0.75f;
    }

    public static void SetColor(UIButton uiButton)
    {
        SetColor(uiButton, ColorSystem.GetSystemColor());
    }

    public static void SetColor(UIButton uiButton, Color color)
    {
        uiButton.defaultColor = color;
        uiButton.disabledColor = color * 0.75f;
        uiButton.hover = color;
        uiButton.pressed = color;

       // uiButton.t
        uiButton.UpdateColor(true);
    }

    public static void SetColor(UISprite uiSprite)
    {
        uiSprite.color = ColorSystem.GetSystemColor();
    }

    public static void SlideTexture(UITexture uiTexture, float x, float y )
    {

        Vector3 loc = uiTexture.cachedTransform.localPosition;
        loc.x += x;
        loc.y += y;
        uiTexture.cachedTransform.localPosition = loc;
    }

    public static void SetTextureX(UITexture uiTexture, float x)
    {
        {
            Vector3 loc = uiTexture.transform.localPosition;
            loc.x = x;
            uiTexture.transform.localPosition = loc;
        }
        //uiTexture.SetRect(x, loc.y, uiTexture.width, uiTexture.height);
    }

}
1 Like

Checked a different project that I think uses new GUI system. Might be what you actually need.

This and above code assumes you have designed your screen in 1080p. It scales it down by width ratio which works for most devices. Some Ipads have a squarer resolution though, so you might need to adapt for that depending on needs.

    public float xScale;
    public float yScale;
    void Start()
    {
        xScale = Screen.width / 1920f;
        yScale = Screen.height / 1080f;

        foreach (var r in GetComponentsInChildren<RectTransform>())
        {
            r.anchoredPosition *= xScale;
            r.sizeDelta *= xScale;
        }
      
    }
1 Like

It looks like I’ll need to sift through these and see what I can use… I’ll have a play and see what I can do with them, thanks guys :slight_smile:

I never tried to to id by code, but currently I’m working on a project that uses a RawImage and works perfectly on Android… I have two cameras with a very close range that stay outside of the range of the main camera. They send everything to RawImage and the same RawImage is used within a Canvas. If I remember well recently RawImage was renamed to RenderTexture (?).
I lost the point exactly of where you are now, but what about creating a Canvas with a RawImage, use the stretch feature, store it as prefab and Implement it when you need it?
Well, sorry if, instead, this note is outdated.

1 Like

No it’s handy to know about beppin, thanks.

I’ve fixed the problem another way now, but I’ll probably be needing render texture for other things, so it’s good to know it’s not off the cards again.

Just Add Image Component For Canvas and Drag Your Image to it. That’s All :slight_smile:

1 Like

Thanks a lot man!

I was stumped on this and I couldn’t believe such a seemingly simple thing wasn’t also easy to do. Granted I’ve only tried this on my Android device, but I’m hoping it’ll scale with different Android screens as well as iOS screens.

Best solution is put image in game as sprite instead of ui like normal ingame background and this will do all that without any script