SFML community forums

Help => Window => Topic started by: JoshuaBehrens on July 14, 2014, 02:16:22 pm

Title: [Solved] Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on July 14, 2014, 02:16:22 pm
Hey guys,

I am stuck with certain problem: I manage stuff inside of sth you can call a scene and I want to keep the aspect ratio of the scene when the window is resized. I want to do this by changing the viewport. I am used to the fact that the viewport is given in pixels as sf::View seems to use percentage values of the window size I scale my calculated viewport down with these lines:

// down scale to % units ; windoww/h are from the resize event
viewport.width  = viewport.width  / windoww;
viewport.height = viewport.height / windowh;
// center
viewport.left = ( 1.0 - viewport.width  ) * 0.5;
viewport.top  = ( 1.0 - viewport.height ) * 0.5;

But it seems that my calculations are always wrong :/ and I guess I am stuck in my head so I can't see the problem. I tried this way to get the right upscaled/cropped size:
 // in = ( scenew, sceneh ); clip = ( windoww, windowh )
sf::Vector2f scaleToFit( const sf::Vector2f& in, const sf::Vector2f& clip )
{
        sf::Vector2f ret( in );
        if ( ( clip.y * in.x ) / in.y >= clip.x )
        {
                ret.y = ( clip.x * in.y ) / in.x;
                ret.x = clip.x;
        }
        else if ( ( clip.x * in.y ) / in.x >= clip.y )
        {
                ret.x = ( clip.y * in.x ) / in.y;
                ret.y = clip.y;
        }
        else
                ret = clip;
        return ret;
}
 
All I get is strange resizing. The scene keeps in the center but if I extend the width of the window (there should be black panels on the left and right) the black panels get very big so the scene does not keep its ratio and I don't know why. I hope I can get some help here.

Just for your information: This is what all happens on the resize event but I don't expect an error there:
sf::View v( sf::FloatRect( 0, 0, scenew, sceneh ) );
sf::FloatRect viewport( sf::Vector2f( 0, 0 ), scaleToFit( sf::Vector2f( screenw, screenh ), sf::Vector2f( windoww, windowh ) ) );
viewport.width  = viewport.width  / basicSettings.windoww;
viewport.height = viewport.height / basicSettings.windowh;
viewport.left = ( 1.0 - viewport.width  ) * 0.5;
viewport.top  = ( 1.0 - viewport.height ) * 0.5;
v.setViewport( viewport );
window.setView( v );
 
Title: Re: Change viewport to keep aspect ratio of scene
Post by: Hapax on July 14, 2014, 08:26:52 pm
You aim seems to be keeping the view constantly letterboxed. Does the view really have to be resized with the window? Can't you just adjust the aspect of it and keep one of the sides always matching the original view?

Anyway, it looks like
sf::FloatRect viewport( sf::Vector2f( 0, 0 ), scaleToFit( sf::Vector2f( screenw, screenh ), sf::Vector2f( windoww, windowh ) ) );
should be
sf::FloatRect viewport( sf::Vector2f( 0, 0 ), scaleToFit( sf::Vector2f( scenew, sceneh ), sf::Vector2f( windoww, windowh ) ) );
(scenew & sceneh instead of screenw & screenh)
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on July 15, 2014, 06:11:20 pm
Yeah that is just a typo. As I compared it to a scene I renamed the variables in the code (except these two). So this is not a real "error".
Letterboxed ... that was the word ... Well at the right window ratio there are no letterboxes(obvious) but yes I want the letterboxes.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: Hapax on July 15, 2014, 10:34:31 pm
In this line:
viewport.width  = viewport.width  / windoww;
you are dividing the current ratio by the size of the window in pixels and then assigning that as the new ratio.

I think it should rather be:
viewport.width = currentViewportWidthInPixels / windoww;

Obviously, the same sort of thing would also apply to the height line.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on July 16, 2014, 12:30:51 am
As scenew, sceneh, windoww and windowh contain values in pixel unit scaleToFit returns unit pixel. So viewport.width/.height contain pixel values.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: Hapax on July 16, 2014, 12:56:30 am
Ah, I see; that makes sense.
However, the .setViewPort requires ratios, not pixels.
Also, your viewport.left and viewport.top are based on viewport.width and viewport.height being ratios.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on July 16, 2014, 05:42:20 am
That is why I scale the size values by dividing by their maximum pixel units (window size). The position is just position in the center.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: Hapax on July 18, 2014, 03:28:06 pm
My point is that you're scaling using one ratio and one pixel length to get a ratio. They should be both ratios or both pixel lengths.

e.g.
viewport.width = 0.5
windoww = 600

then
viewport.width = 0.5 / 600 = 0.000833
0.000833 is probably not the width you wanted.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on July 30, 2014, 04:49:46 pm
I got your point, but the units are fine(and they were the right units all the time). I was testing an other way, but here is the same issue that it is scaling weird. I looks like this generates no panels when the window is like a square and scales wrong if it is rectangular.
        // not edited
        sf::View v( sf::FloatRect( sf::Vector2f( basicSettings.screenx, basicSettings.screeny ), \
                                                           sf::Vector2f( basicSettings.screenw, basicSettings.screenh ) ) );

        sf::Vector2f S( basicSettings.screenw, basicSettings.screenh );
        sf::Vector2f W( basicSettings.windoww, basicSettings.windowh );

        float scalor( std::min( W.x / S.x, W.y / S.y ) ); // squared window preference?!
        sf::Vector2f scaled( S );
        scaled *= scalor;
        printf( "scaled: %f %f\n", scaled.x, scaled.y );

        sf::FloatRect viewport( sf::Vector2f( 0, 0 ), scaled );
        viewport.width  = viewport.width  / W.x;
        viewport.height = viewport.height / W.y;
        viewport.left = ( 1.0 - viewport.width  ) * 0.5;
        viewport.top  = ( 1.0 - viewport.height ) * 0.5;
        v.setViewport( viewport ); // in the end still not the same ratio as the scene
        basicSettings.window.setView( v );

Why did anyone chose to set the viewport in percent units? Why not in pixels as the glViewport does? It was easier with pixel values to check the problem :/ as you always had to deal with pixels when changing the viewport.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: Hapax on July 30, 2014, 07:32:03 pm
Why did anyone chose to set the viewport in percent units? Why not in pixels as the glViewport does? It was easier with pixel values to check the problem :/ as you always had to deal with pixels when changing the viewport.
There are no percentages; they're ratios. This question is answered in the tutorial (http://www.sfml-dev.org/tutorials/2.1/graphics-view.php#defining-how-the-view-is-viewed):
Quote
the viewport is not defined in pixels, but rather as a ratio of the window size. This is much more convenient: this way you don't have to track resize events to update the size of the viewport. It is also more intuitive: you will most likely need to define your viewport as a fraction of your window, not as a fixed-size rectangle.

I got your point, but the units are fine(and they were the right units all the time).
I can't see a possible way that the units could have been fine. Either you were using a ratio where a pixel size should have been used or you were using a pixel size where a ratio should have been used.

I must admit that I had a tiny bit of trouble with keeping aspect ratio of view constant when window's aspect ratio changed, but it turned out to be not that difficult (I'm slow :P); keep at it!  ;)
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on July 31, 2014, 08:16:39 am
Well percentages and ratios both are usually used with values between 0..1 and as I am not a native English speaker I might tend to use wrong words.

Ok, I got the reason why ratios are used. Why do you stick to the thought that the units were/are wrong and not the values? Check the code, I convert from pixel to ratio with this:
    viewport.width  = viewport.width  / W.x;
    viewport.height = viewport.height / W.y;
And all before this is pixel and all after this is in ratio units.

Can't you write something more helpful? If you already had the same problem why can't you give a hint, where I should rethink my code or give (pseudo-)code.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: maly on July 31, 2014, 12:30:02 pm
Something like this?
float screenwidth = windowsize.x / static_cast<float>(gamesize.x);
float screenheight = windowsize.y / static_cast<float>(gamesize.y);

sf::FloatRect viewport;
viewport.width = 1.f;
viewport.height = 1.f;

if(screenwidth > screenheight)
{
        viewport.width = screenheight / screenwidth;
        viewport.left = (1.f - viewport.width) / 2.f;
}
else if(screenwidth < screenheight)
{
        viewport.height = screenwidth / screenheight;
        viewport.top = (1.f - viewport.height) / 2.f;
}

sf::View v( sf::FloatRect( 0, 0, gamesize.x , gamesize.y ) );
v.setViewport(viewport);
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on July 31, 2014, 07:55:22 pm
I tried yours maly and it ends up the same as my implementation.
        float screenwidth =  static_cast<float>(basicSettings.windoww) / static_cast<float>(basicSettings.screenw);
        float screenheight = static_cast<float>(basicSettings.windowh) / static_cast<float>(basicSettings.screenh);

        sf::FloatRect viewport;
        viewport.width = 1.f;
        viewport.height = 1.f;

        if( screenwidth > screenheight )
        {
                viewport.width = screenheight / screenwidth;
                viewport.left = (1.f - viewport.width) / 2.f;
        }
        else if(screenwidth < screenheight)
        {
                viewport.height = screenwidth / screenheight;
                viewport.top = (1.f - viewport.height) / 2.f;
        }

        sf::View v( sf::FloatRect( sf::Vector2f( basicSettings.screenx, basicSettings.screeny ), \
                                                           sf::Vector2f( basicSettings.screenw, basicSettings.screenh ) ) );
        v.setViewport(viewport);

        basicSettings.window.setView( v );
I guess I make some screenshots:
(click to show/hide)
Title: Re: Change viewport to keep aspect ratio of scene
Post by: Gobbles on August 01, 2014, 02:15:41 am
just put something together rather quick, wonder if it's the same kind of thing your trying to do.
void Game::MaintainAspectRatio()
{
        //first we check our new aspect width to see if it changed
        float newAspectWidth = window.getSize().x;
        float newAspectHeight = window.getSize().y;
        if(newAspectWidth != currentAspectWidth)
        {
                //width changed, maintain the aspect ratio and adjust the height
                currentAspectWidth = newAspectWidth;
                currentAspectHeight = currentAspectWidth / aspectRatio;
        }
        else if(newAspectHeight != currentAspectHeight)
        {
                //height changed, maintain aspect ratio and change the width
                currentAspectHeight = newAspectHeight;
                currentAspectWidth = currentAspectHeight * aspectRatio;
        }
        std::cout << "width: " << currentAspectWidth << " height: " << currentAspectHeight;
        window.setSize(sf::Vector2u(currentAspectWidth, currentAspectHeight));
}

Where aspectRatio is always 4:3.

Edit: I should also mention that this gets called whenever theres a Resize Event.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on August 01, 2014, 05:26:28 am
I get your dirty trick behind it as you limit the window size. I don't really like it as I cannot freely do with the window what I want (as a player).
I tried your code with currentAspectWidth and currentAspectHeight as floats. After resizing once the window shrunk:
(click to show/hide)
I also tried to change the floats to ints(just currentAspect***) and the same phenomenom:
(click to show/hide)
Title: Re: Change viewport to keep aspect ratio of scene
Post by: Gobbles on August 01, 2014, 05:44:30 am
I must've misread your question, but what this does for me is as I alter one dimension of the screen, the other dimension adjusts to maintain a 4:3 aspect Ratio. Coupled with a sf::Style::Resize in the window, this allows me to drag the window to whatever size I'm most comfortable with. What exactly are you looking for?
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on August 01, 2014, 07:14:00 am
I want to always see the whole scene so if my scene has an aspect ratio of 1:1 and the width is greater than the height of the window I need black panels on the upper and lower part of the window (vice versa for height > width). And I try to achieve this with using glViewport as used with the viewport from sf::View.
Title: Re: Change viewport to keep aspect ratio of scene
Post by: maly on August 01, 2014, 08:42:49 am
when you resize the window always set a new view.

basicSettings.screenx and basicSettings.screeny doing strange offsets.
sf::View v( sf::FloatRect( sf::Vector2f( basicSettings.screenx, basicSettings.screeny ), \
                               sf::Vector2f( basicSettings.screenw, basicSettings.screenh ) ) );
try
sf::View v( sf::FloatRect( sf::Vector2f( 0, 0 ), \
                               sf::Vector2f( basicSettings.screenw, basicSettings.screenh ) ) );

example
#include <SFML/Graphics.hpp>

sf::View calcView(const sf::Vector2u &windowsize, const sf::Vector2u &designedsize)
{
    sf::FloatRect viewport(0.f, 0.f, 1.f, 1.f);

    float screenwidth = windowsize.x / static_cast<float>(designedsize.x);
    float screenheight = windowsize.y / static_cast<float>(designedsize.y);

    if(screenwidth > screenheight)
    {
        viewport.width = screenheight / screenwidth;
        viewport.left = (1.f - viewport.width) / 2.f;
    }
    else if(screenwidth < screenheight)
    {
        viewport.height = screenwidth / screenheight;
        viewport.top = (1.f - viewport.height) / 2.f;
    }

    sf::View view( sf::FloatRect( 0, 0, designedsize.x , designedsize.y ) );
    view.setViewport(viewport);

    return view;
}

int main()
{
    const sf::Vector2u designedsize(320,200);

    sf::RenderWindow app(sf::VideoMode(800, 400), "SFML window");
    app.setView(calcView(app.getSize(), designedsize));

    while (app.isOpen())
    {
        sf::Event event;
        while (app.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                app.close();
            if (event.type == sf::Event::Resized)
                app.setView(calcView(sf::Vector2u(event.size.width, event.size.height), designedsize));
        }

        app.clear();
        sf::CircleShape circle(5);
        for(int y = 0; y < designedsize.y/10; ++y)
        {
            for(int x = 0; x < designedsize.x/10; ++x)
            {
                circle.setPosition(x * 10, y * 10);
                circle.setFillColor( (x + y) & 1 ? sf::Color::Blue:sf::Color::Green);
                app.draw(circle);
            }
        }
        app.display();
    }
    return 0;
}
Title: Re: Change viewport to keep aspect ratio of scene
Post by: kimci86 on August 01, 2014, 10:45:01 am
Here is how I would do it.
I hope it helps.

sf::View calcView(const sf::Vector2f& windowSize, float minRatio, float maxRatio)
{
    sf::Vector2f viewSize = windowSize;

    // clip ratio
    float ratio = viewSize.x / viewSize.y;
    if(ratio < minRatio) // too high
        viewSize.y = viewSize.x / minRatio;
    else if(ratio > maxRatio) // too wide
        viewSize.x = viewSize.y * maxRatio;

    sf::View view(sf::FloatRect(sf::Vector2f(), viewSize));

    sf::FloatRect viewport((windowSize - viewSize) / 2.f, viewSize);
    viewport.left /= windowSize.x;
    viewport.top /= windowSize.y;
    viewport.width /= windowSize.x;
    viewport.height /= windowSize.y;
    view.setViewport(viewport);

    return view;
}
Title: Re: Change viewport to keep aspect ratio of scene
Post by: Hapax on August 01, 2014, 11:23:26 pm
Well percentages and ratios both are usually used with values between 0..1 and as I am not a native English speaker I might tend to use wrong words.
For your information, percentage is usually in the range of 0 to 100.

I convert from pixel to ratio with this:
    viewport.width  = viewport.width  / W.x;
    viewport.height = viewport.height / W.y;
And all before this is pixel and all after this is in ratio units.
You store pixel width and ratio width in the same variable - viewport.width? I see that now but it's not very clear or logical.

Can't you write something more helpful? If you already had the same problem why can't you give a hint, where I should rethink my code or give (pseudo-)code.
A lot of code has been given by others since this but I'm still not 100 percent sure what your aim is.
I know that you want letterboxing when the window is no longer the same ratio.
I don't think you've specified whether or not you want the actual letterboxed display to stay the original size or stretch to fit the window. If stretched, the view can stay almost the same (which was why I mentioned it in my first post). If not, the view is the identical to the window and the viewport is the only thing that changes.

If you still haven't solved it from others' code, and you've explained what your aim is, let me know and I'll look into it for you (it's been a while since I worked with this so I don't have the answer at hand).
Title: Re: Change viewport to keep aspect ratio of scene
Post by: JoshuaBehrens on August 02, 2014, 01:19:22 am
@Hapax: Well then I know why we miscommunicate as you see percentage in a range of 0..100(which is absolutely right) and I just called it percentage as it should not be larger than 100% = 1.0.
I can understand your misinterpretation of the doubled usage of the floatrects properties width/height on the one hand in pixels and on the one hand in ratios.
The code from the others works, but I found an other error(continue reading).

@mely: I just tested your example and it worked perfectly. I put it into my application and it was working. Then I changed the code from relying on window.getSize( ) on my variables windoww and windowh. Then it was not working and stretching wrong. Then I found a typo in my resize-event where I asign the new width and height to windoww but windowh is not affected. So I guess even my code should have worked without the typo (not tested).

Thank you for your patience and pointing me (unintentionally) to an error (I should have been aware of).