AutoScale - AutoFit alternative


Tweet graphics monkey-x code-archives
(Posted 2 months ago) RonTek

Back when I first wrote this over a year ago, AutoFit didn't have all of the features that I wanted to use in my app, or it didn't apply the features properly. I decided to upload this after making some changes to the code recently to more efficiently support devices and targets where the dimensions can be resized at runtime. Today, I'm presuming that AutoFit can do most of the things AutoScale set out to do, but the features I had in mind when creating AutoScale were the following:

  1. Aspect ratio preserving (and ignoring) resize ability
  2. Sliding aspect-ratio preserved canvases to the center
  3. Touch-based delta input values, in addition to mouse-based
  4. a DeviceX() and DeviceY() function that returned values relative to DeviceWidth() and DeviceHeight(), in percentages, even when the rest of the screen is already scaled
  5. Some way to apply letterboxing, when necessary.

The rest of the differences with AutoFit are mostly stylistic.

Author: Nobuyuki

2D AutoScale - AutoFit alternative Monkey-X Blitz3D

How to Use
InitAutoScale(width, height) should be called OnCreate, and ScaleScreen() should be called at the beginning of OnRender. ScreenWidth and ScreenHeight are the virtual screen dimensions. All dimensions in your app can be hardcoded relative to positions in your virtual screen, and will be scaled accordingly, but for mouse clicks and touches to register in the correct location, always poll dTouch or dMouse functions instead of the default ones. You can also use SetScaledScissor to perform a scissor operation in the virtual screen space.

No scissoring is done to provide any letterbox effect in AutoScale by default -- This may be a desired effect for some users. However, you can add your own letterbox effect with the new RenderLetterBox() function. Call it after all of your other rendering operations, supply it an instance of a LetterBoxer, and it automagically fills in the correct area.

There are 3 built-in types which extend LetterBoxer that are included: Color bars, stretched "book-end" images, and a tiled background. You can also make your own completely custom LetterBoxer -- the render routine gives you the correct draw rectangle and side, and SlideAxis will tell you how your letterbox should be oriented. Updates will be coming soon to make BookEndLetterBox be SlideAxis-aware.

'Copyright 2011-2014 Nobuyuki (nobu[AT]subsoap.com). 
'No warranties expressed or implied.

Import mojo
Private
    Global lastDeviceWidth:Float, lastDeviceHeight:Float

Public
    Global PreserveAspectRatio:Bool = True 'Aspect ratio preserving
    Global ScreenWidth:Float = 800 'Native size of your app
    Global ScreenHeight:Float = 480
    Global ScaleFactor:Float  'Amount to scale window by preserving aspect ratio;  calculated at runtime

    Global SlideAxis:Bool '0:  X-Axis, 1:  Y-Axis
    Global SlideAmount:Int  'amount to slide the window to center the screen

    Global ScaleX:Float, ScaleY:Float  'Individual scale amounts for sloppy stretch to window

Function initAutoScale:Void(w:Float = 800, h:Float = 480, preserveAspectRatio:Bool = True)
    ScreenWidth = w; ScreenHeight = h; PreserveAspectRatio = preserveAspectRatio
    lastDeviceWidth = DeviceWidth(); lastDeviceHeight = DeviceHeight()

    ScaleX = DeviceWidth() / ScreenWidth
    ScaleY = DeviceHeight() / ScreenHeight

    'shorter distance determines scale factor
    If ScaleX < ScaleY then ScaleFactor = ScaleX else ScaleFactor = ScaleY

    'calculate slide distance based on derived size
    Local DiffX:Int= DeviceWidth() - ScreenWidth * ScaleFactor
    Local DiffY:Int= DeviceHeight() - ScreenHeight * ScaleFactor

    If ScaleX < ScaleY then 
        SlideAxis = True 'Set axis to Y
        SlideAmount = DiffY / 2
    else 
        SlideAxis = False 'Set axis to X
        SlideAmount = DiffX / 2
    End if   

    'Scale the slide amount by the scale factor to get the real slide amount after scaling
    SlideAmount *= (1/ScaleFactor)
End Function

Function CheckScreenDimensions:Void()
    If lastDeviceWidth <> DeviceWidth() Or lastDeviceHeight <> DeviceHeight() Then 
        initAutoScale(ScreenWidth, ScreenHeight, PreserveAspectRatio)

        'Print "Screen dimensions changed. " + lastDeviceWidth + "=" + DeviceWidth() + ", " +
        '   lastDeviceHeight + "=" + DeviceHeight()
    End If
End Function

'Summary: Scales the screen.  It is recommended that you call this at beginning of OnRender.
Function ScaleScreen:Void(checkForResize:Bool = True)
    If checkForResize = True Then CheckScreenDimensions()

    'SetMatrix (1,0,0,1,0,0) 'Identity Matrix
    If PreserveAspectRatio = True Then
        Scale(ScaleFactor, ScaleFactor)
        If SlideAxis = False Then Translate(SlideAmount, 0) Else Translate(0, SlideAmount)
    Else
        Scale(ScaleX, ScaleY)
    End If
End Function

'Derived mouse positions
Function dMouseX:Int()
    If PreserveAspectRatio
        If SlideAxis = False Then Return MouseX() / ScaleFactor - SlideAmount Else Return MouseX() / ScaleFactor
    Else
        Return MouseX() / ScaleX
    End If
End Function

Function dMouseY:Int()
    If PreserveAspectRatio
        If SlideAxis = True Then Return MouseY() / ScaleFactor - SlideAmount Else Return MouseY() / ScaleFactor
    Else
        Return MouseY() / ScaleY
    End If
End Function

'Derived multitouch positions
Function dTouchX:Int(index:Int=0)
    If PreserveAspectRatio
        If SlideAxis = False Then Return TouchX(index) / ScaleFactor - SlideAmount Else Return TouchX(index) / ScaleFactor
    Else
        Return TouchX(index) / ScaleX
    End If
End Function

Function dTouchY:Int(index:Int=0)
    If PreserveAspectRatio
        If SlideAxis = True Then Return TouchY(index) / ScaleFactor - SlideAmount Else Return TouchY(index) / ScaleFactor
    Else
        Return TouchY(index) / ScaleY
    End If
End Function



'Derived device screen positions.  Returns position based on percent from 0-1.
Function DeviceX:Float(percent:Float=1)
    If ScaleX = 1 Then Return DeviceWidth() * percent 
    If SlideAxis = False Then Return (DeviceWidth() / ScaleFactor) * percent - SlideAmount Else Return (DeviceWidth()/ScaleFactor) * percent 
End Function

Function DeviceY:Float(percent:Float=1)
    If ScaleY = 1 Then Return DeviceHeight() * percent 
    If SlideAxis = True Then Return (DeviceHeight() / ScaleFactor) * percent - SlideAmount Else Return (DeviceHeight()/ScaleFactor) * percent 
End Function

'Derived Scissor
Function SetScaledScissor:Void(x:Float, y:Float, width:Float, height:Float)
    Local sx:Float, sy:Float

    If PreserveAspectRatio = True Then
        If SlideAxis = False Then sx = (x + SlideAmount) * ScaleFactor Else sx = x * ScaleFactor
        If SlideAxis = True Then sy = (y + SlideAmount) * ScaleFactor Else sy = y * ScaleFactor

        SetScissor(sx, sy, ScaleFactor * width, ScaleFactor * height)
    Else
        sx = x * ScaleX
        sy = y * ScaleY
        SetScissor(sx, sy, ScaleX * width, ScaleY * height)
    End If
End Function


'Summary:  Renders a letterbox over the top of the surface.
Function RenderLetterBox:Void(lb:LetterBoxer)
    If DeviceWidth() = ScreenWidth And DeviceHeight() = ScreenHeight Then Return
    Local x:Float, y:Float, w:Float, h:Float

    If SlideAxis = False Then 'X-Axis
        w = SlideAmount * ScaleFactor  'Letterbox size
        h = DeviceHeight()

        x = DeviceWidth() -w  'SideB origin
    Else 'Y-Axis
        w = DeviceWidth()
        h = SlideAmount * ScaleFactor

        y = DeviceHeight() -h  'SideB origin
    End If
    SetMatrix(1, 0, 0, 1, 0, 0) 'Identity matrix.

    lb.Render(0, 0, w, h, False)
    lb.Render(x, y, Ceil(w), Ceil(h), True)  'Side B
End Function


'Summary:  Base LetterBoxer class. Extend this with your own to make custom letterboxes.
Class LetterBoxer Abstract
    Method Render:Void(x:Float, y:Float, w:Float, h:Float, SideB:Bool = False) Abstract
End Class

'Summary:  The most basic type of letterbox;  colored bars.
Class ColoredLetterBox Extends LetterBoxer
    Field r:Int, g:Int, b:Int
    Method New(r:Int, g:Int, b:Int)
        Self.r = r; Self.g = g; Self.b = b
    End Method

    Method Render:Void(x:Float, y:Float, w:Float, h:Float, SideB:Bool)
        SetColor(r, g, b)
        DrawRect(x, y, w, h)
        SetColor(255, 255, 255)         
    End Method
End Class

'Summary:  Displays an image on either side of the letterbox.
Class BookEndLetterBox Extends LetterBoxer
    Field img:Image, img2:Image
    Method New(A:Image, B:Image)
        img = A; img2 = B
    End Method

    Method Render:Void(x:Float, y:Float, w:Float, h:Float, SideB:Bool)
        If SideB
            DrawImage(img2, x, y, 0, w / img2.Width, h / img2.Height)
        Else
            DrawImage(img, x, y, 0, w / img.Width, h / img.Height)
        End If
    End Method  
End Class

'Summary:  Displays a tiled image on both sides of the letterbox.
Class TiledLetterBox Extends LetterBoxer
    Field img:Image
    Method New(img:Image)
        Self.img = img
    End Method

    Method Render:Void(x:Float, y:Float, w:Float, h:Float, SideB:Bool)
        Local s:Float[] = GetScissor()
        SetScissor(x, y, w, h)

        For Local yy:Int = 0 To Ceil(h / img.Height)
            For Local xx:Int = 0 To Ceil(w / img.Width)
                DrawImage(img, x + xx * img.Width, y + yy * img.Height)
            Next
        Next
        SetScissor(s[0], s[1], s[2], s[3])
    End Method
End Class

Reply To Topic (minimum 10 characters)

Please log in to reply