Problems with letterboxing and grabimage

BlitzMax Forums/BlitzMax Programming/Problems with letterboxing and grabimage

christian223(Posted 2009) [#1]
Everything works fine with my game until i try to add letterboxing, some buttons i try to create using the grabimage function somehow appear out of scale and position, and i have no idea what is going on since i do not understand one bit of direct3d or opengl, any assistance would be greatly appreciated.

Heres the code i use to create me button:


		Local scaleH:Float
		Local scaleV:Float
		Local NewImage:TImage = CreateImage(width0, height0)
		Local border:TImage
		'Local link1:T_Objeto
		Local link2:TLink'almacena el texto para poner la forma del boton detras del texto
		Local bordesAlto:Float'el alto de la imagen de los bordes
		
		Print "width0 " + width0 + " height0 " + height0
		
		border = LoadImage("buttonScrollBarVert0-up.png")
		bordesAlto = border.Height
		
		SpriteLoad("buttonScrollBarVert0.png")
		
		scaleH = width0 / Self.My_Imagen.width
		scaleV = (height0 - (bordesAlto * 2)) / Self.My_Imagen.Height
		
		Self.My_Imagen.flags = 1 | 0 | 0 | 1
		
		Print "scaleH " + scaleH + " scaleV " + scaleV
		
		Cls
		SetImageHandle(Self.My_Imagen, 0, 0)
		SetImageHandle(NewImage, 0, 0)
		SetScale(scaleH, scaleV)
				
		DrawImage(Self.My_Imagen, 0, bordesAlto)

		'dibuja los bordes
		SetScale(1, 1)
		SetImageHandle(border, 0, 0)
		DrawImage(border, 0, 0)
		border = LoadImage("buttonScrollBarVert0-dwn.png")
		SetImageHandle(border, 0, 0)
		DrawImage(border, 0, (height0 - bordesAlto))
		
		'captura la imagen		
		GrabImage(NewImage, 0, 0)
		
		MidHandleImage(NewImage)
		Self.SpriteChange(NewImage)
		
		'para poner el texto sobre el boton
		'link1 = T_Objeto.ListaT_Objetos.FindLink(Self)
		link2 = T_Objeto.ListaT_Objetos.FindLink(T_Objeto(Self.MyContentText))
		T_Objeto.ListaT_Objetos.remove(Self)
		T_Objeto.ListaT_Objetos.InsertBeforeLink(Self, link2)
		
		Cls



And the code i use for Letterbox is in this thread, i suspect the problem has to do with the Projection Matrix, but i really could not find out how to fix it:

Letterboxing code

Oh, and if there is a better way to do it, please tell me. Thankyou.


ImaginaryHuman(Posted 2009) [#2]
Your code doesn't show anything about how you are setting up the letterboxing, so it's hard to say, unless your math is funky in your image scaling.

So are you opening a widescreen display and then trying to just pretend that it is a 4:3 ratio by writing black bars to the sides of the backbuffer?


christian223(Posted 2009) [#3]
This is how i set up the letterbox:

Global refreshrate = 70
Global ScreenWidth:Int = 800
Global ScreenHeight:Int = 600
Global depth = 0

Graphics ScreenWidth, ScreenHeight, depth, refreshrate

ProjectionMatrix.SetLetterbox(ScreenWidth, ScreenHeight)


How could this affect the scaling i am having problems with?.


ImaginaryHuman(Posted 2009) [#4]
That still doesn't tell us how you do it cus you haven't shown what the setletterbox method looks like


christian223(Posted 2009) [#5]
Its on the link i posted on the original post, the one that says "letterboxing code", i dont want to copy and paste code that is allready somehwere else.
Thx.


tonyg(Posted 2009) [#6]
I think you would benefit from supplying a short runnable example showing the problem. Without it many people won't have time to help or suggest anything.
Reducing your code is always helpful for diagnosing bugs but, if you can't/won't do that, try using LOTS of debuglog messages.


Otus(Posted 2009) [#7]
A quick glance at the code by sswift in the other thread suggests that you are not setting up letterboxing correctly, but it's difficult to say without all your code.

Your code:
Graphics ScreenWidth, ScreenHeight, depth, refreshrate

ProjectionMatrix.SetLetterbox(ScreenWidth, ScreenHeight)

This uses the same width&height for both the screen and the letterbox, so nothing should be scaled and no borders added, right? What's your code to set the origin?

I'm not sure what you are attempting to do. It would be nice to see the code in English...


christian223(Posted 2009) [#8]
Thanks Otus, the problems is not with the lettebox specifically, since that seems to work fine, the problem is when i use it, somehow screws up some other code that seems to have nothing to do with it.

Thanks for the tip tonyg:
Heres a code i shrinked to show you my problem, it used 3 images wich are explained in the code, it has a function Create_Graphics() wich creates a vertical button, to see the problem just comment and uncomment the indicated lines at the begining of the code.




christian223(Posted 2009) [#9]
If there is no solution to this problem, what other ways are there to obtain the same effect than the letterbox code?.


tonyg(Posted 2009) [#10]
Can't really see what is happening and not keen on breaking the code apart. There is other projection matrix code available or can you use setviewport?
TBH I don't really know what it is you're trying to do.


christian223(Posted 2009) [#11]
I dont want to use viewport, i have heard that it doesn't run well on some video cards.

The code by swifft works perfectly, it does just what i needed, i want to get a perfectly proportioned fullscreen in all kinds of monitors, including widescreen, the problem is that when i use the code it screws up the graphics of some buttons i create using the grabimage function. To see this you just have to copy and paste the code above, run the program, see the nice graphics, and then uncomment/comment 2 lines at the begining of the code as i explained inside of the code, and youll see that the graphics are not shown correctly but have been captured at a different scale than before, wich shouldn't be.
Thanks for the trying to help me, i really appreciate it, and need it :).


ImaginaryHuman(Posted 2009) [#12]
I am not sure if perhaps you are confused about the whole topic of aspect ratios and under exactly which circumstances you need to do something to compensate for it. You said at the top of your code that switching from 800x600 to 1024x768 does something wrong. It shouldn't. That's such a simple change, there is no alteration to aspect ratio at all, all you should need to make your images the same equivalent size as they were on 800x600, is to set the viewport to be coordinate 0 on the left, 0 at the top, 600 on the bottom and 800 on the right border. Then anything you draw at 800,600 will be right in the bottom of your screen instead of an inch or two offset. That doesn't have anything whatsoever to do with aspect ratio's or needing to change them. I think there is a lot of confusion about what the projection matrix is for, what a viewport is and how they relate.

The projection matrix actually stores two things at the same time - the `projection` of space as viewed by a camera, and a scaling operation to convert between world coordinates and window coordinates. The projection matrix basically defines a) the size of the photographic plate within the camera - ie the area which records the actual image - which is measured in window pixels, and b) the coordinates within the game world that fall on the very edges of the view that the camera can see at once. You have to set up both parts of the projection scenario - the world view plus the scaling to window coordinates, otherwise you will not get the results you want. You can't just call glOrtho without also calling glViewport afterwards, for example.

For a 2D game, where you're using an orthographic projection, Blitz usually assumes that you want to work in `pixels` to measure everything. And to make the positioning of objects easier, it ties the pixel coordinates on the screen to the pixel coordinates in the game world. Remember that DX and GL are both 3D api's not 2D and they work in 3D internally even if the results look 2D. You are dealing with a 3D game world, with a projection of space, and a scaling to window coordinates.

So when you say to Blitz `I want you to draw something at 50,50`, it actually means `position an object at coordinate 50,50 in game-world coordinates, and then translate those coordinates using a projection (orthographic means the values won't be changed due to parallel lines with no perspective), and then scale those coordinates to fit into the window viewport (except there will be no scaling because Mark decided to match world coordinates to window coordinates). Yet the system still takes those steps, it just happens not to change the values.

It's not like with old softare renderers where you ask for something to be drawn at 50,50 and it just goes right the `window buffer` and draws something to the pixel at 50,50. Your piece of geometry - the pixel in the corner of your image - is actually at 50,50,0 in 3D. Regardless of what is in the projection matrix or what viewport you set up, the matrix is still going to get processed in order to translate from world coordinates to screen coordinates, and to then translate from screen coordinates to viewport/window coordinates.

So although you might be changing the `projection` with a command like glOrtho, that does NOT set up the viewport/window coordinate relationship. After you've called glOrtho you must call glViewport to tell it how to scale the flattened screen coordinates to window coordinates. Obviously with an orthographic projection you *usually* don't do any scaling, or even any real projecting. With an 800x600 screen and an orthographic projection (which consists of a coordinate system 0,0 through 800,600) and a viewport from 0,0 to 800,600, plotting something at 50,50 actually does end up at the 50th pixel across and down. But there's more going on than meets the eye.

When you use BlitzMax's SetViewport command, you are basically saying to Blitz "set up a projection with no effect from perspective, define that the camera can see a portion of the world ranging from 0,0 through 800,600, and then scale that specific view to fit into the screen." Or at least that's how it works with a fullscreen viewport. When you say to blitz that you want a viewport say 400x300 as part of an 800x600 screen, blitz interprets that to mean `you want to only view the top left quarter of what the camera can normally see. It does not change the coordinate system of the world, or how much of the world the camera sees, it just `crops` it. In OpenGL it uses a Scissor/clip-planes to prevent drawing outside of the viewport area. This is contrary to how OpenGL, for example, defines what a Viewport is.

To GL, a viewport is `how much of your window is going to get covered in pixels using the image the camera is seeing`, but it has no effect on what AREA of the game world the camera is seeing, so if you make your GL viewport smaller it's going to simply show you exactly the same area of the game world, just filling less space on the screen. The viewport is a scaling operation, the projection is to do with how objects get smaller as they are further away in perspective and also how much `field of view` the camera has at a given time.

So let's take a simple scenario - you design your game for 800x600 screens but then you want it to work on a 1024x768 screen. You want your game to fill the whole screen and not leave an area to the right and bottom empty.
What you're really saying is, you don't want the camera to be able to see all the way to coordinates 1024,768, you want the bottom right of its field of view to be 800,600. You want it to be only able to see the portion of the game world that goes from 0,0 to 800,600. Then you tell it that your viewport - your recording resolution of the camera image - is going to fill the whole screen from 0,0 to 1024,768. Then when you draw something, the projection matrix will convert from world coords to screen coords, and then the projection matrix will scale `that view of the world` to fit your viewport. Doesn't matter what size your viewport is, it will only show the region of world space from 0,0 through 800,600. You could say your viewport is 100x10 and it will make that `view` fit into that space. You could also say your viewport is 0,0 to 1024,768 and this will `map` the 1024,768 coords to the world's 800,600 coords. Now when you draw to 799,599, that pixel will be in the bottom right corner of your screen, perhaps occupying the whole of that pixel, but also perhaps occupying a little bit of the pixels to the left and above - due to stretching.

So first and foremost you need to get your system to work where it is simply scaling to resolutions which have the same aspect ratio. Start by trying to go from 800x600 up to 1024x768. Set the projection to 0,0 through 800,600, and then set the viewport from 0,0 through 1024,768. When you get THAT working, THEN you can start thinking about messing with aspect ratio's, cus otherwise you're just going to get yourself all confused.

Now, the issue of different aspect ratios makes things even more complicated. There are some different scenarios in which the aspect ratio changes. First of all, there are a couple of definitions of what an aspect ratio IS. a) The physical width of the viewable screen area in ratio to the physical height, b) the number of pixels that fit into a given physical screen area width in ratio to the number of pixels that fit into a given screen are height, and c) the change in proportion between the width and height of an individual *pixel* which occurs when the physical aspect ratio is different to the number pixel width/height aspect.

Sometimes there is a match between the amount of physical space taken up by a screen mode and the amount of pixels that fit into that space. Let me take a pretend monitor as an example - say the monitor's display area is about 20" wide by 15" heigh. 20/15 is a ratio of 1.333. In other words, for each 1.3 inches horizontally, there is 1 inch vertically. This is a 4:3 ratio. (20/5=4, 15/5=3). Now let's say for ease that my screen resolution filling that entire space is 800x600. So long as it fills that entire space, and so long as the ratio of pixels 800 vs 600 is 1.333 (4:3), there is a correlation between the aspect ratio of the physical screen and the aspect ratio of the resolution.

This is the case with many manufacturers of monitors, and under these conditions it's fairly safe to say aspect=pixelwidth/pixelheight. However, this is not always true. Let's go back to the days of the Amiga. It had a `superhires` mode at 1280x256 in a 4:3 aspect ratio. Even though the physical screen was in a 4:3 proportion, it managed to squeeze 4 times as many pixels horizontally compared to normal resolutions (like 320x256). The screen itself was not larger, but the resolution was disproportionate to the amount of space it consumed. Pixels, therefore, were not square, they were very squished horizontally like little pillars.

Another thing which created this kind of distortion was on many CRT monitors (and others) where the user has the freedom to tweak the video signal using onscreen or manual controls. In order to `get the most out of` their monitor they might have tried to stretch the signal horizontally or vertically. This had the effect of altering the proportions of pixels. The pixelwidth/pixelheight was no longer 4:3, it might be 4.1:3, or 4.2:2.9 for example. Since it is not really possible to `read` the exact physical space consumed by the video beam within the monitor, it is impossible to detect if the user has altered the aspect ratio. How do you compensate for that?

Now, one saving grace is that *most* manufacturers create screen resolutions whose pixel aspect ratio stays the same no matter which mode you choose. Let's say, a widescreen flatpanel monitor, with a native resolution of 1440x900. Let's say the physical area of the viewable screen measures 20" width x 12.5" height. So the physical aspect ratio matches the pixelwidth/pixelheight aspet ratio, both are 1.6. But what size is each individual pixel? 1440/20 gives us 72 pixels per inch horizontally, and 900/12.5 gives us 72 pixels per inch vertically. So they are square pixels. The manufacturer decides to provide an emulation of a lower resolution at 1024x768 but they want it to appear to be a 4:3 pixelwidth ratio. So they decide that this mode will have black bars on the left and right. They will squeeze 768 pixels into the full height of the display but then will only use as much of the width of the display as it needed to show 1024 pixels. The display could potentially show a 1200x768 image, but they're going with common standards. So now the physical aspect ratio has not changed, the aspect ratio of the consumed pixel width/consumed pixel height has changed from 16:10 to 4:3, but the *pixels are still square*.

The shape of pixels stays the same across resolutions so long as there is a correlation between the pixelwidth/pixelheight ratio and the physical-consumed-width/physical-consumed-height ratio. You could change to a 640x480 resolution, similarly the pixels would still be square so long as the amount of screenspace taken up by that mode covers an area which works out to be in a 4:3 ratio.

However, this is where some manufacturer's get nifty. They might decide, okay, I have a widescreen display, why not use the whole space. Well ok, they can do one of two things. Add a 1200x768 mode with square pixels, or add a 1024x768 mode in a 16:10 aspect ratio. The former approach isn't too bad - you can scale your graphics the same amount horizontally as vertically. But the latter is more tricky. There is now discord between the pixel ratio and the physical ratio. Now you have a `stretched` mode. You're going to have to do something to proportionately narrow your graphics to get them back to what seems like the same size as in a 4:3 mode.

Another spanner in the works comes when the user has both a) changed the physical proportions of the display area and b) the pixelwidth/pixelheight ratio didn't match up to your original graphics either. Now you have two issues to compensate for. If you targetted 800x600 you would now have to adjust for a wierd squishy resolution AND a wierd squishy physical reproportioning. I don't think most people try to compensate for physical changes. It's usually fairly safe to just assume that the amount of physical space consumed by a resolution matches in aspect ratio to the ratio of the number of pixels.

If you CAN assume that, then now you're left with only one issue. How to convert between the number of pixels wide/high of your source graphics and the same in your destination screen. Going from 800x600 up to 1024x768 is just a matter of `zooming in a bit`, multiplying the same amount in both x and y. Then you have to consider whether to pretend that you don't have as much space as you do - e.g. in a 1440x900 widescreen resolution you could draw black bars on the sides from 0,0 to 208,900 and then from 1232,0 to 1440,900. You would then be left with a 4:3 aspect play area, the pixels would still be square, and you'd just need to zoom them the same in x and y to make them the right size. This would give you a pretend screen mode of 1024x900. The shape of pixels wouldn't change. OR, you can use the extra play area to give the user more room to move/view the game world.

What if the user plays on an 800x1200 vertical display? Well, now you can decide whether to add bars at the top and bottom or let the user see more game area. But now you just need to again scale the graphics (or projection of/viewing of them) the same in x and y.

You use the projection matrix to say what area of coordinates the camera can see, and you use the viewport to say how that area gets blown up to cover an area of the window. It's like the projection captures the photograph by flattening the 3D perspective, and then the viewport blows up the photograph into a large poster. Making a poster doesn't change the proportions of the photo, just its size. But taking the photo can entail forcing the image to stretch horizontally and/or vertically - ie to change the shape of the camera lens.

So if you want to keep your 800x600 graphics in proportion, no matter what the resolution is, providing that the resolutions all have the same shape pixels (square, ie pixelwidth/pixelheight is always the same result), you just need to set up a simple scaling operation by adjusting the camera lens (projection) to see more or less of the game world. But IF you change the lens, you also have to define the viewport. I found that if I did not do that, I would get the new view/projection but it would try to fit it into the shape of my old viewport.

I don't know if this is going to help you to resolve your issues or whether it just makes it more complicated, but I hope it helps some. I really don't know if there is an issue with your code or with your understanding of what you need to do to get the results you want. But when I looked at your code I started to get the impression you were trying to do stretching where you didn't need to and vice versa due to trying to use the letterbox function when you don't need to, or something.


christian223(Posted 2009) [#13]
Thanks for the lengthy response IH, very informative, and i think i finally understand why i am having the problem im having:

What i am doing is scaling an image so that it fits certain size, for example, a 20x20 image get scaled to fit a 60x20 button, it works very well without the scaling that the viewport does, but when using the projection matrix and viewport i think this is what it happens:

1- i draw the image scaled to fit my 60x20 button
2- The viewport applies an extra scaling effect, thus making it bigger than 60x20
3- i capture the backbuffer (with grabimage) and the resulting image is a 60x20 but its contents look wrong because of what happened in step 2.

So, i guess that i must somehow de-activate the viweport momentarily to use grabimage?, or... do the button in other way, not using grabimage, otherwise it will have an extra scaling effect. But this means that this method of creating images, by using grabimage, CANNOT be used while using this letterbox code.


ImaginaryHuman(Posted 2009) [#14]
Hmm. Well, if you draw to 60x20 and the viewport/projection changes the size to cover, say, 80x30 pixels, then you grab an image portion 60x20, I don't know if it will give you 60x20 pixels or the equivalent of 60x20 scaled up. I think grabimage will use the coordinate system you've set up to decide where the grab area starts and how far it extends to cover 60 horizontal units and 20 vertical units.

What you might have to do before you grabimage is simply convert your image coords/dimensions back to a normal scale, like divide by actualscreenwidth/virtualscreenwidth or something? Then you might be asking for an area, say, 50x16, which might actually grab a 60x20 area?


christian223(Posted 2009) [#15]
Thx IH, i tried what you said and many other things, in the end all i had to do was to use this two functions whenever i want to create an image using grabimage method. First i "deactivate" the projection matrix, then i draw to the backbuffer the contents of the image, and after i use grabimage i reactivate the projection matrix again. This was a big headache for me, thx everybody.


		Function DeActivate_scale()
			RenderState.Push()
			ProjectionMatrix.SetLetterBox(GraphicsWidth() , GraphicsHeight())
		End Function
		
		Function ReActivate_Scale()
			RenderState.Pop()
			ProjectionMatrix.SetLetterBox(widthVirtual, heightVirtual)
		End Function




ImaginaryHuman(Posted 2009) [#16]
Good, glad you got it sorted out.