2D image scaling in irrlicht? Anyone got quality results?

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
revoluted

2D image scaling in irrlicht? Anyone got quality results?

Post by revoluted »

I am trying to write a flexible (bitmap) gui class for game usage. I have it working fine, until I change resolution. I can keep track of ofsets, get correct positions (relative) but can not map the texture correctly onto the rescaled quad without artifacts (prob because irr uses shorts instead of floats and not real 0-1.0 mapping or something??

anyway, is there a way, even adding in some adapted code to draw2Dimage (which I use) to allow for perfect scaling up and down. I know emils 2D class does good scaling but it goes directly to using vertices and more low lever stuff, is there a way to keep it higher level (abstracted?) maybe by implementing another overloaded draw2dImage that takes relative floats instead of absolute pixel co-ordinates.

Also I notice when the windowed mode is set to be resizable that irrlicht scales up and down perfectly! Looking at it's internal methods for onResize, it adjust a rect and keeps track of it for guis etc. So if it works ok like that why doesn't it work when you switch from 800x600 to 640x480 for example?

Unless somehow DX is doing this internally when in windowed mode (probable) then somewhere inside irrlicht's code must be the stuff I need to build on to make perfect scaling. Or do I really just have to use the methods like emil has and get low level and build a "brand new" 2d image handler for all occasions.?

If anyone has any ideas on this let me know.. thanks
zenaku
Posts: 212
Joined: Tue Jun 07, 2005 11:23 pm

Post by zenaku »

Hmm, scaled GUI images, sounds familiar. ;)

http://irrlicht.sourceforge.net/phpBB2/ ... hp?t=11295

If you want emil's TAnimatedSpriteSceneNode class to work when the screen is resized, you'll need to update the render() function.

Before the the worldview matrix is multiplied, you'll need to update the Ortho matrix to the new screen parameters.

Code: Select all

virtual void render()
{      
    video::IVideoDriver* driver = SceneManager->getVideoDriver();		
    core::matrix4 Trns,Scl,Rot,wrld;
        
    Trns.makeIdentity(); 
    Scl.makeIdentity();
    Rot.makeIdentity();
    wrld.makeIdentity();

    Scl.setScale(RelativeScale);
    Rot.setRotationRadians(RelativeRotation);
    Trns.setTranslation(RelativeTranslation);		

    driver->setTransform(video::ETS_VIEW, wrld);
    driver->setTransform(video::ETS_PROJECTION, wrld);

    //update ortho matrix to new screen size
    core::dimension2d<s32> Screensize = driver->getScreenSize();
    Ortho(0,0) = (f32) ((double)2/(double)Screensize.Width);
    Ortho(1,1) = (f32) ((double)2/(double)Screensize.Height);
		
    wrld = Trns * Ortho * Rot * Scl;

    driver->setTransform(video::ETS_WORLD, wrld);
    driver->setMaterial(Material);  
    driver->drawIndexedTriangleList(&Vertices[0], 4, &Indices[0], 4);
}
Depending on what you are doing, you may want to use the Viewport() instead of the screen size.


I came at the problem the same as you: why doesn't Irrlicht have a draw2DimageScaled() function?!? I even went as far as implementing one in the OpenGL driver before I realized how dumb that was ;)

The problem is that the hardware scaling abilties depend on things like Z coordinates, the current camera position, etc. Any attempt to create a version that was hardware accelerated would require you to keep track of these things. At that point, with Z coordinates and camera angles, it's not really a 2D image. It might as well be a scene node.
Guest

Post by Guest »

Thanks for the reply.

Last night I ended up giving it a go anyway using the animatedspriteclass, and got it working nicely!! As you say it was easier to just implement that (though I haven't worked it into irrlicht yet but it's in my app code - same principle for now).

I made a new class based on emils class stripping out the animating stuff (for simplicity) and just keeping the quad and scaling stuff. It was really the realtive positioning and scaling I was after. I also set up the test with time deltas and finally got rock solid time based modeling and scaling / positioning with 2D images. I have it so when I press 1/2/3 the device resets to a different resolution (for testing) it worked perfectly at each resolution - scale/pos and speed wise as expected!! :)

Also made a quick macro (nothing advanced) so I could pass normal screen co-ords to my class for placement of the image:

#define PX(val) ( ((val)/400.0f) -1.0f )
#define PY(val) ( ((val)/300.0f) -1.0f )

This assumes you are placing images as if it were always an 800x600 screen (and obv scales up/down from there). Seems to work ok.

So, the only thing left now was when I then tried it layered over the top of a 3D scene - I had problems, it was drawing the 2d in world space and not screen space (set transform?) which was obviously no good. The code you posted seems to have a few settransforms in etc that are not in the original so I will test you code out in case that does it - if not do you know off hand how to set it to orthagonal before rendering the images?

Also I won't be using resizable (Dragable) windowed mode, only fixed resolution changes (windowed and full screen) so it seems already to work fine when doing that (again because it's relative not absolute).

thanks :)
Guest

Post by Guest »

zenaku > your code worked great specifically the additional part:

Code: Select all

 wrld.makeIdentity();

  Scl.setScale(RelativeScale);
  Rot.setRotationRadians(RelativeRotation);
  Trns.setTranslation(RelativeTranslation);      

 driver->setTransform(video::ETS_VIEW, wrld);
 driver->setTransform(video::ETS_PROJECTION, wrld);
That was all it took to get it working great. I noticed a slight slow down with your original code (I took out the reseting of the ortho matrix as i'm not resizing a window but reseting the device when I change) I assume that was eating a few frames. Maybe you could just poll for "onResize" there from the irrlicht onresize? (somehow) to only do it on resize. Anyway for my needs it worked great.

Thanks a lot - you saved me another day of trial and error! :)
bitplane
Admin
Posts: 3204
Joined: Mon Mar 28, 2005 3:45 am
Location: England
Contact:

Post by bitplane »

I've added this fix to the code in the wiki - thanks zenaku :)
Submit bugs/patches to the tracker!
Need help right now? Visit the chat room
zenaku
Posts: 212
Joined: Tue Jun 07, 2005 11:23 pm

Post by zenaku »

Anonymous wrote:zenaku > your code worked great specifically the additional part:

Code: Select all

 wrld.makeIdentity();

  Scl.setScale(RelativeScale);
  Rot.setRotationRadians(RelativeRotation);
  Trns.setTranslation(RelativeTranslation);      

 driver->setTransform(video::ETS_VIEW, wrld);
 driver->setTransform(video::ETS_PROJECTION, wrld);
That was all it took to get it working great. I noticed a slight slow down with your original code (I took out the reseting of the ortho matrix as i'm not resizing a window but reseting the device when I change) I assume that was eating a few frames. Maybe you could just poll for "onResize" there from the irrlicht onresize? (somehow) to only do it on resize. Anyway for my needs it worked great.

Thanks a lot - you saved me another day of trial and error! :)
Here's some additional code.

You'll need to add a new private variable to the class:

Code: Select all

core::dimension2d<s32> FrameSize;
In the Load() function, we set FrameSize to the size of the sprite in pixels:

Code: Select all

    FrameSize = core::dimension2d<s32>(frmWidth,frmHeight);

Now that's out of the way, here are some new methods for the class:

Code: Select all

    //Set position based on X,Y pixel coordinates instead of 
    // vector3d(X,Y,Z) -1 -> +1 coordinates
	virtual void setPos(core::position2d<s32> pos)
	{

		pos.X += (s32) (FrmWidth*RelativeScale.X/2);
		pos.Y += (s32) (FrmHeight*RelativeScale.Y/2) + 1;

		core::rect<s32> viewport = SceneManager->getVideoDriver()->getViewPort();

		f32 X = 2.0f * (pos.X - viewport.UpperLeftCorner.X) / viewport.LowerRightCorner.X - 1;
		f32 Y = 2.0f * (pos.Y - viewport.UpperLeftCorner.Y) / viewport.LowerRightCorner.Y - 1;

		ISceneNode::setPosition(irr::core::vector3d<f32>(X,-Y, 0));
	}

    //Get upper left hand corner's coordinates in pixels
	virtual core::position2d<s32> getPos()
	{
		core::vector3d<f32> pos = ISceneNode::getPosition();
		core::rect<s32> viewport = SceneManager->getVideoDriver()->getViewPort();

		s32 X = (s32) ((pos.X * viewport.LowerRightCorner.X + 1)/2 + viewport.getWidth()/2 - FrmWidth *RelativeScale.X/2);
		s32 Y = (s32) ((pos.Y * viewport.LowerRightCorner.Y + 1)/2 - viewport.getHeight()/2 + FrmHeight*RelativeScale.Y/2);

		return core::position2d<s32>(X,-Y, 0);
	}

    //scale image to new size based on pixels
	virtual void setScaleToSize(core::dimension2d<s32> Size)
	{	
		core::vector3df v((f32)Size.Width/(f32)FrameSize.Width, (f32)Size.Height/(f32)FrameSize.Height, 0);
		ISceneNode::setScale(v);
	}

Finally, you may want to do something with those Z values besides setting them to zero.
Guest

Post by Guest »

Also in case anyone wanted to use that macro I posted for 2D co-ords it is actually:

Code: Select all

#define PX(val) ( ((val)/400.0f) -1.0f )
#define PY(val) ( ((-val)/300.0f) +1.0f )
to work properly. Works with custom mouse cursors too.

I see you have 2d positioning in the last code you posted. However as I am not using "sprites" just 2D images (then built on in bitmap font class, gui class, cursor class etc) I ripped out all the framestuff anyway.

In the case of using my #define it obviously get's replaced at compile time with the actual floats rather than converting on the fly at run time (so a small speed increase using the #define but not as clean/transparent or flexible as your code).

thanks again! :)
Guest

Post by Guest »

Actually forget about my longwinded defines.. testing on anything that needs updating each frame is about the same performance and using your setPos function is a lot better than this define:

Code: Select all

#define PXC(val) (( ( (val + (float)gScreenWidth/25))      / (float)(gScreenWidth/2) ) -1.0f)
#define PYC(val) (( ( (-(val + (float)gScreenHeight/18.75f))) / (float)(gScreenHeight/2) ) +1.0f )

// and it's use
gCursorSprite->setPosition(vector3df(PXC(gMousePos.X),PYC(gMousePos.Y),0));
which does the same thing but with silly maths instead of built in matrix stuff, ergo you are great and I am daft.. Thanks for the code (again).
Guest

Post by Guest »

actually the above macros work perfectly for scaling custom cursor position to sprite as the screen size changes. The first macro works well too, and is a bit more flexible than the function (use the #defines in your derived classes for gui/font/cursors etc)
Alberto Rubinato
Posts: 7
Joined: Fri Dec 09, 2005 9:41 pm
Location: Italy

Post by Alberto Rubinato »

Hi,
i0m using this class in a project, but I've got a problem. When I set the position of the node with the new member function setPos added above, seems the the position is relative to the centre of the node and not on topleft corner coordinates. Is it correct? How can I change this in order to have setPos to be relative to topleft corner of the box?

Thanks.
Guest

Post by Guest »

Yeah that's how it should work (think relative) but you can build on it. The easy way it to store the width/height of the texture and just subtract that (/2) dived in half to get your top left corner. Not sure if the "topcorner" function above does it as I didn't need it.

In general I have had great success with the above code in this thread and have built 3 classes around it that give me perfect scaling bitmap fonts, guis and loading screens/bars. I tied it into a global timedelta and set up animations (squashy/bouncey movable gui stuff like menus and clikboxes) looks really smooth and was impossible with draw2Dimage alone.
...

Post by ... »

one thing I have found switching from using screenspace 2d Using Draw2DImage to using the flexible emil/zenaku code is that my graphics look rougher around the edges. Before with draw2dimage or guielement draws the alphablending of the edges of my textures used as osi elements were ultra smooth (of course they didn't scale properly and then looked like crap) - but they were perfect at native res.

Now, even at native res, it seems the edges are rougher, the alpha blend is not as smooth, maybe it's the vertex/texture mapping as it is used: 0-1 instead of the "notorious" 1:1 texel/screen mapping which uses a -0.5 offset (can't remember the details now). If not, what else could it be, any ideas what draw2Dimage does differently to keep those edges smooth? setFilter stuff (linear instead of point for example)??

cheers
...

Post by ... »

I think I have found why it draws it a bit less nicely blended. It's probably because you can't call gDriver->setRenderStates2DMode(false,true,true) which gets called when you use draw2Dimage. It looks like it sets things up better for 2D of course (and I imagine some of the above code would be redundant if you could call this function before the later part of the code).

So I can either getExposedVideoDriverData and do it manually or bring the function from private to public and call it (hopefully). It has some matrix stuff going on so, as I said, we could probably just call that and take out some of the above code (as it would be duplicate).

cheers
zenaku
Posts: 212
Joined: Tue Jun 07, 2005 11:23 pm

Post by zenaku »

... wrote:I think I have found why it draws it a bit less nicely blended. It's probably because you can't call gDriver->setRenderStates2DMode(false,true,true) which gets called when you use draw2Dimage. It looks like it sets things up better for 2D of course (and I imagine some of the above code would be redundant if you could call this function before the later part of the code).

So I can either getExposedVideoDriverData and do it manually or bring the function from private to public and call it (hopefully). It has some matrix stuff going on so, as I said, we could probably just call that and take out some of the above code (as it would be duplicate).

cheers
Are you sure the image is not getting scaled? setRenderStates2DMode() isn't going to make your images have crusty edges.

setRenderStates2DMode() just resets the view, projection, alpha, etc for 2d use. That's already done with:

Code: Select all


//TAnimSpriteSceneNode.h

wrld.makeIdentity(); 
...
driver->setTransform(video::ETS_VIEW, wrld);
 driver->setTransform(video::ETS_PROJECTION, wrld); 
 
// In 3d rendering, alpha channel is handled by the scene node's MaterialType.
// e.g. MaterialType == irr::video::EMT_TRANSPARENT_ALPHA_CHANNEL;



Maybe since the setScaleToSize() function takes integers, you are having rounding errors with odd texture sizes? In order to see your image correctly you may need to set a size to '40.5', not '40' or '41'. Something like that would make the whole image scaled by one pixel, and it would probably look like crap.

Also, maybe the 'makeColorKey' option of load() may be messing your images up? I dunno. It could be lots of things, but I really doubt it's setRenderStates2DMode().

Finally, some texture loaders may behave differently. The loader for .PNG files craps out if the file is > 4096 pixels wide, but for .TGA files it doesn't. You might want to try saving your image as a different format and see if that helps.
-------------------------------------
IrrLua - a Lua binding for Irrlicht
http://irrlua.sourceforge.net/
Guest

Post by Guest »

Well the thing is the images were fine using my old methods (gui/Draw2Dimage). they are exactly the same source assets and they are small pngs (256x256 for example).

It is mostly noticable on detailed textures like pieces of writing (menu text) or in the font class I built around it - again it looked edged of really nice previously.

There is a possibility of the mapping texels being off and that is causing the problem but its ONLY on the alpha'd edges that now look less smooth (though still not bad - just not as good as they were or as I need).

I will try messing around with the quad/texture mapping co-ords etc - and for a test I will stick it 800x600 and not do any maths on it at all (same as before).

The reason I was on about setRender2DStates... is because it does also set some texture filtering that is NOT in the code you posted and I want to see if that is a problem.

I removed the colorkey code altogether as I NEVER use it (always 32bit texture alphad pngs here).

Thanks for the advice I'll see how I get on - I take it it looks fine to you compared to a regular (non scaled) draw2Dimage.?
Post Reply