Search Unity

Get final, rendered, pixel size of VisualElement ?

Discussion in 'UI Toolkit' started by achimmihca, Dec 30, 2020.

  1. achimmihca

    achimmihca

    Joined:
    Feb 13, 2016
    Posts:
    283
    I want to draw pixel-perfect (lines and pixels) on a VisualElement.
    I did this before in uGUI by drawing on a Texture2D that had the same size in device-pixels as the RectTransform.

    The same approach would also work for VisualElements. I can create and assign a Texture to the VisualElement and draw on this texture.
    Problem is that the result is scaled because I don't know the final device-pixel size. As a result, everything is blurry.

    How do I find the final, rendered size in device-pixels for a VisualElement?

    For example, when I have a VisualElement with width "100px" then these 100 px will be scaled for the device such that it may be more than 100 device-pixels.

    Are there other ways to draw pixels on a VisualElement?
     
    DragonCoder and Predulus like this.
  2. Nexer8

    Nexer8

    Joined:
    Dec 10, 2017
    Posts:
    271
    If you want the elements to scale you should use %. After the element has been created, you can use VisualElement.worldBound.size (or other worldBound properties) to get the width and height. VisualElement.resolvedStyle might also work but I have not tested it yet. To make sure the element has been created you should subscribe to the GeometryChangedEvent or AttatchToPanelEvent.
     
  3. achimmihca

    achimmihca

    Joined:
    Feb 13, 2016
    Posts:
    283
    VisualElement.worldBound.size and VisualElement.resolvedStyle.width is both given in reference resolution (of the PanelSettings of the UIDocument). I want this in device pixels.

    However, I think I could multiply resolvedStyle.width by the factor (device-width / reference-width) to get the actual width in device-pixels.


    I guess the UIElements API is always using the reference resolution?
     
    Last edited: Jan 2, 2021
  4. uMathieu

    uMathieu

    Unity Technologies

    Joined:
    Jun 6, 2017
    Posts:
    398
    You're right, the layout values are always using scaled points units. The actual scale is a mix of the PanelSettings scaling options and the device's dpi values.

    Right now, to get the pixel-size of an element, you can multiply the resolvedStyle.width/height by the scaling ratio.

    This is a bit tricky to get at the moment, but you can fetch the scaling ratio by casting the element's panel to a
    BaseVisualElementPanel, then access its scaledPixelsPerPoint property.
     
    achimmihca likes this.
  5. Cery_

    Cery_

    Joined:
    Aug 17, 2012
    Posts:
    48
    Is there an updated simpler method to get the scaling factor?
    I'm having trouble to access the BaseVisualElementPanel properties since its burried in the UIElements namespace.

    I can think of two methods to determine the scaling factor without it:

    1. Restrict to a scaling mode i.e. scale by width and then calculate the ratio between reference resolution and screen resolution
    2. Generate a background visual element that spans the whole screen get its size and combine with the screen size.
    Both methods are cumbersome or restrictive. Would love a simpler method.
     
    Midiphony-panda likes this.
  6. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    700
    Unfortunately nothing has changed in this area and there's nothing planned around this subject in the near future.
     
  7. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    @Cery_ Here's what I use:

    Code (CSharp):
    1. class PanelHelper
    2. {
    3.     private readonly IPanel _panel;
    4.     private readonly PanelSettings _panelSettings;
    5.     private Vector2 _scalingRatio;
    6.     private int _cacheFrame;
    7.     private float _screenHeight;
    8.     private Vector2 _panelScreenMin;
    9.  
    10.     public PanelHelper(UIDocument document)
    11.     {
    12.         _panel = document.rootVisualElement.panel;
    13.         _panelSettings = document.panelSettings;
    14.     }
    15.  
    16.     public Vector2 screenToPanel(Vector2 v)
    17.     {
    18.         return RuntimePanelUtils.ScreenToPanel(_panel, v);
    19.     }
    20.  
    21.     public Vector2 panelToScreen(Vector2 v)
    22.     {
    23.         if(Time.frameCount != _cacheFrame)
    24.             updateCache();
    25.         Vector2 result = (v - _panelScreenMin) * _scalingRatio;
    26.         result.y = _screenHeight - result.y;
    27.         return result;
    28.     }
    29.  
    30.     public Rect panelToScreen(Rect r)
    31.     {
    32.         Vector2 TL = panelToScreen(new Vector2(r.xMin, r.yMin));
    33.         Vector2 BR = panelToScreen(new Vector2(r.xMax, r.yMax));
    34.         Vector2 min = new(Mathf.Min(a.x, b.x), Matf.Min(a.y, b.y));
    35.         Vector2 size = new(Mathf.Abs(a.x - b.x), Mathf.Abs(a.y - b.y));
    36.         return new Rect(min, size);
    37.     }
    38.  
    39.     public float getScaleWeight() => _panelSettings.scaleMode switch
    40.     {
    41.         PanelScaleMode.ConstantPixelSize => .5f,
    42.         PanelScaleMode.ConstantPhysicalSize => .5f,
    43.         PanelScaleMode.ScaleWithScreenSize => _panelSettings.match,
    44.         _ => throw new ArgumentOutOfRangeException()
    45.     };
    46.  
    47.     public Vector2 getScalingRatio()
    48.     {
    49.         if(Time.frameCount != _cacheFrame)
    50.             updateCache();
    51.         return _scalingRatio;
    52.     }
    53.  
    54.     private void updateCache()
    55.     {
    56.         _cacheFrame = Time.frameCount;
    57.         Vector2 ssize = new(Screen.width, Screen.height);
    58.         Vector2 stpMin = RuntimePanelUtils.ScreenToPanel(_panel, new Vector2(0, 0));
    59.         Vector2 stpMax = RuntimePanelUtils.ScreenToPanel(_panel, ssize);
    60.         Vector2 stpSize = stpMax - stpMin;
    61.         _screenHeight = ssize.y;
    62.         _panelScreenMin = stpMin;
    63.         _scalingRatio = ssize / stpSize;
    64.     }
    65. }
    66.  
    To get the pixel size of a visual element, use it like this:

    Code (CSharp):
    1. Vector2 size = panelHelper.panelToScreen(visualElement.worldBound).size;
    Kinda wish Unity would add something like that to RuntimePanelUtils, but I guess there might be contexts in which it doesn't work or gets more complicated.
     
    Last edited: Oct 14, 2021
  8. v-strelchenko

    v-strelchenko

    Joined:
    Aug 2, 2019
    Posts:
    13
    a simple solution, if you know that your UI Document is full-screen, would be:
    screenToPanelRatio = Screen.width / fullScreenUIDocument.rootVisualElement.resolvedStyle.width;


    Then you can find the size of you panel in pixels by multiplying it by this ratio.

    Also, don't forget that the .resolvedStyle will only give you correct values after the on
    GeometryChangedEvent callback, just like @Nexer8 has mentioned.
     
  9. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    243
    Very happy to find this answer, until I discovered BaseRuntimePanel and RuntimePanel are internals :D

    As @burningmime proposed, I wished it would be supported in the RuntimePanelUtils, at least for the classic overlay use case (I saw the more complex use cases).
     
    Nexer8 likes this.
  10. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    243
    So I did this :

    Code (CSharp):
    1. var screenBoundsInPanelCoords = RuntimePanelUtils.ScreenToPanel(_button.panel,
    2.    new Vector2(Screen.width, Screen.height));
    3.              
    4. Vector2 buttonPosInPanel = _button.worldBound.center;
    5.  
    6. Vector2 buttonPosInScreen = new Vector2(buttonPosInPanel.x * Screen.width / screenBoundsInPanelCoords.x,
    7.    Screen.height - buttonPosInPanel.y * Screen.height / screenBoundsInPanelCoords.y);
    8.  
     
    tang001 and Nexer8 like this.
  11. guillermoi

    guillermoi

    Joined:
    Sep 20, 2012
    Posts:
    27
    For my purposes it was enough to RuntimePanelUtils.ScreenToPane(panel,Vector2.one), and then invert that to get the scaling factor
     
    Midiphony-panda likes this.
  12. st069165

    st069165

    Joined:
    Nov 5, 2020
    Posts:
    5
    What is a and b?
     
  13. burningmime

    burningmime

    Joined:
    Jan 25, 2014
    Posts:
    845
    Sorry that was some old code and I may have edited it after posting it here or something.

    EDIT: OK; yeah, this is the function from my code...

    Code (CSharp):
    1.             public Rect panelToScreen(Rect r)
    2.             {
    3.                 Vector2 TL = panelToScreen(new Vector2(r.xMin, r.yMin));
    4.                 Vector2 BR = panelToScreen(new Vector2(r.xMax, r.yMax));
    5.                 return Utils.absRect(TL, BR);
    6.             }
    Which relies on a separate helper method...

    Code (CSharp):
    1.         public static Rect absRect(Vector2 a, Vector2 b)
    2.         {
    3.             Vector2 min = new(Math.Min(a.x, b.x), Math.Min(a.y, b.y));
    4.             Vector2 size = new(Math.Abs(a.x - b.x), Math.Abs(a.y - b.y));
    5.             return new Rect(min, size);
    6.         }
    But the basic idea is just the same. Get the top-left and bottom-right points in screen space.
     
    Last edited: Apr 2, 2023
    st069165 likes this.