image stretching [SOLVED.]

For help with general CEGUI usage:
- Questions about the usage of CEGUI and its features, if not explained in the documentation.
- Problems with the CMAKE configuration or problems occuring during the build process/compilation.
- Errors or unexpected behaviour.

Moderators: CEGUI MVP, CEGUI Team

daves
Home away from home
Home away from home
Posts: 253
Joined: Thu Feb 02, 2006 20:12

image stretching [SOLVED.]

Postby daves » Tue Jul 22, 2008 19:15

Trying to figure out what I'm doing wrong. I have an imageset that I create dynamically. It contains a single image that is 1275 x 1650 in size.

I disable autoscale on the imageset and i set the native resoltuion and the width/height to 1275x1650.

Now I set this to the "Image" property on a StaticImage window. The static imagewindow is inside a scrollable pane which is inside a framewindow.

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>

<GUILayout >
    <Window Type="SVTLook/DialogWindow" Name="FocusImage" >
        <Property Name="TitlebarFont" Value="Tahoma-10" />
        <Property Name="SizingEnabled" Value="True" />
        <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
        <Property Name="TitlebarEnabled" Value="True" />
        <Property Name="UnifiedAreaRect" Value="{{0,5},{0,25},{1,-5},{1,-5}}" />
        <Window Type="SVTLook/ScrollablePane" Name="FocusImage/ScrollablePane" >
            <Property Name="ContentArea" Value="l:0 t:0 r:1032 b:689" />
            <Property Name="HorzStepSize" Value="0.1" />
            <Property Name="VertStepSize" Value="0.1" />
            <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
            <Property Name="HorzOverlapSize" Value="0.01" />
            <Property Name="UnifiedAreaRect" Value="{{0,10},{0,30},{1,-10},{1,-10}}" />
            <Property Name="VertOverlapSize" Value="0.01" />
            <Property Name="HorzScrollPosition" Value="0" />
            <Property Name="VertScrollPosition" Value="0" />
            <AutoWindow NameSuffix="__auto_container__" >
                <Property Name="ContentArea" Value="l:0 t:0 r:1032 b:689" />
                <Property Name="ChildExtentsArea" Value="l:0 t:0 r:1032 b:689" />
                <Window Type="SVTLook/StaticImage" Name="FocusImage/ScrollablePane/ImageWindow" >
                    <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
                    <Property Name="UnifiedAreaRect" Value="{{0,0},{0,0},{1,0},{1,0}}" />
                </Window>
            </AutoWindow>
        </Window>
    </Window>
</GUILayout>



I thought that what I needed to do was then set the StaticImage window to have a width/height that is the same as the image (1275 x 1650). When I do this, the image shows up stretched horizontally. If I set the StaticImage window to be square (e.g. 600 x 600) then the stretching dissappears, and the image looks ok.

I would think that the image size and the window size should have the same aspect ratio (width/height). Yet the square window seems to be what I want..

What am I missing? Is it that the scrollable panes content area is somehow affecting this?
Last edited by daves on Thu Jul 24, 2008 10:08, edited 3 times in total.

User avatar
CrazyEddie
CEGUI Project Lead
Posts: 6760
Joined: Wed Jan 12, 2005 12:06
Location: England
Contact:

Postby CrazyEddie » Wed Jul 23, 2008 08:29

You are correct that what you're doing should be correct, so some external influence is occurring...

This artifact is usually produced when the source image (not sure if you have a source image, but here I'm describing the 'usual' case) has dimensions that are not powers of two, the image will be stretched to next power of two dimensions when loaded to the underlying texture by Ogre, this results in similar effects to what you're seeing.

CE.

daves
Home away from home
Home away from home
Posts: 253
Joined: Thu Feb 02, 2006 20:12

Postby daves » Wed Jul 23, 2008 13:52

CrazyEddie wrote:You are correct that what you're doing should be correct, so some external influence is occurring...

This artifact is usually produced when the source image (not sure if you have a source image, but here I'm describing the 'usual' case) has dimensions that are not powers of two, the image will be stretched to next power of two dimensions when loaded to the underlying texture by Ogre, this results in similar effects to what you're seeing.

CE.


Hmm.
Interesting... The code that I use to load the image from disk, and to copy it first into an Ogre::Texture and then into a CEGUI::Texture, and then finally into an imageset is this:

Code: Select all

void DisplayedImage::load(const std::string& imageName, const std::string& imageLabel, const std::string& imageFileName, const std::string& imageGroup)
   {
      assert(mImgWindow && "DisplayedImage::load: null image window");
      mImgsetName = imageName;
      mImgName = imageName;

      try
      {
         /*
         ** 1] Load an image from file
         ** 2] Create a texture from this loaded image
         ** 3] Create an imageset based on this texture
         ** 4] Define the image to be the entire imageset
         */
         Ogre::Image image;
         image.load(imageFileName, imageGroup);
         float w = image.getWidth();
         float h = image.getHeight();
         if(w <= 0 || h <= 0)
         {
            std::stringstream sserr;
            sserr << "Invalid image dimensions " << imageFileName;
            SVTConsole::getSingleton().printError(sserr.str());
         }
         mWidth = w;
         mHeight = h;

         mOgreTexture = Ogre::TextureManager::getSingleton().loadImage(imageName+".Texture", imageGroup, image);
         assert(!mOgreTexture.isNull() && "DisplayedImage::load: failed to load image");
         mCeguiTexture = GUIManager::getSingleton().getRenderer()->createTexture(mOgreTexture);
         assert(mCeguiTexture && "Template1Menu::load: failed to create cegui texture");
         mCeguiImageSet = CEGUI::ImagesetManager::getSingleton().createImageset(mImgsetName, mCeguiTexture);
         assert(mCeguiImageSet && "Template1Menu::load: failed to create imageset");
         mCeguiImageSet->defineImage(mImgName, CEGUI::Point(0.0f, 0.0f),
               CEGUI::Size(mCeguiTexture->getWidth(), mCeguiTexture->getHeight()),
               CEGUI::Point(0.0f,0.0f));
         mCeguiImageSet->setNativeResolution(CEGUI::Size(mWidth, mHeight));
         mCeguiImageSet->setAutoScalingEnabled(false);

         mImgLabelWindow->setText(imageLabel);

         /*
         ** Now set the Image property in order to actually display the image
         */
         std::stringstream ss;
         ss << "set:" << mImgsetName << " image:" << mImgName;
         mImgWindow->setProperty("Image", ss.str().c_str());
         mLoaded = true;
         
      }
      catch (CEGUI::Exception ce)
      {
         SVTConsole::getSingleton().printError(ce);
      }
      catch (Ogre::Exception oe)
      {
         SVTConsole::getSingleton().printError(oe);
      }
   }


So I think that you are saying I should look at the call to Ogre::TextureManager::loadImage. I'll do this.

The code that I subsequently use to draw it to the StaticImage is this:

Code: Select all

   void FocusImage::onShow()
   {
      try {
         CEGUI::Imageset *is = CEGUI::ImagesetManager::getSingleton().getImageset(mImagesetName);
         const CEGUI::Image& i = is->getImage(mImageName);
         getDialogBaseRef().setBackgroundImage(sIW, mImagesetName, mImageName);
         int width = i.getWidth();
         int height = i.getHeight();
         //CEGUI::URect a(CEGUI::UDim(0.0, 0), CEGUI::UDim(0.0, 0), CEGUI::UDim(0.0, i.getWidth()),  CEGUI::UDim(0.0, i.getHeight()));
         CEGUI::URect a(CEGUI::UDim(0.0, 0), CEGUI::UDim(0.0, 0), CEGUI::UDim(0.0, 500),  CEGUI::UDim(0.0, 500));
         getDialogBaseRef().setWidgetArea(sIW, a);
         getDialogBaseRef().getNamedWidget(sIW)->setAlpha(mImageAlpha);
      }
      catch (CEGUI::Exception ce)
      {
         SVTConsole::getSingleton().printError(ce);
      }
   }

void DialogBase::setBackgroundImage(const std::string& widgetName, const std::string& imageset, const std::string& image)
   {
      CEGUI::Window* wcb = getNamedWidget(widgetName);
      CEGUI::DefaultWindow* dw = static_cast<CEGUI::DefaultWindow*>(wcb);

      std::stringstream ss;
      ss << "set: " << imageset << " image: " << image;
      dw->setProperty("Image", ss.str());
   }




Pretty straightforward, so there aren't many locations where I can be going wrong with this.

Knowing what you know about Ogre (and of course assuming that this is what is really happening) is there a way to tell the TextureManager not to do what your suggesting?

User avatar
CrazyEddie
CEGUI Project Lead
Posts: 6760
Joined: Wed Jan 12, 2005 12:06
Location: England
Contact:

Postby CrazyEddie » Wed Jul 23, 2008 17:44

daves wrote:So I think that you are saying I should look at the call to Ogre::TextureManager::loadImage.

Yeah, I believe that's where the 'magic' happens ;)

daves wrote:Knowing what you know about Ogre (and of course assuming that this is what is really happening) is there a way to tell the TextureManager not to do what your suggesting?

I'm not 100% certain if there's a simple, built-in way to get it to do what we need here. In a general sense, for normal 'texture' like operations, this stretching of the source image into the texture is exactly what you want - this way texture co-ords, in the range 0 to 1 behave as you would expect. Of course in CEGUI land we use pixel co-ordinates (showing our 2d heritage, somewhat) and so we have issues.

In the other renderer modules I modified the loading of images in such a way that where needed we manually transfer the image data into the texture buffer; this way we have the needed control to ensure that the image is not stretched, leaving the oversized parts of the texture as 'spare' space. I've not looked at how to do that in Ogre, but it would be a rare omission if there were not some provision for this to be done.

A couple of other things to note / consider also - although not necessarily related to this issue. The relative scale values in the max size for a CEGUI::Window is based on the display size, so while you'll get the required size at high resolutions, at lower resolutions your StaticImage size might get constrained to the display size - in this case you might want to consider using pixel values for the max size in order to side-step this issue.

CE.

daves
Home away from home
Home away from home
Posts: 253
Joined: Thu Feb 02, 2006 20:12

Postby daves » Wed Jul 23, 2008 20:35

Ok, Spent the morning playing with this. I dont actually think the problem is in the creation of the texture. In fact (using a debugger) I verified that the texture seems to have the correct size.

I have three images to display (I built in a function to display the loaded image at 1x, 2x, and 0.5x).

Here is the 1x image displayed within the CEGUI staticimage window (the staticimage window is 500x500 in size):

Image

here is the 0.5x image displayed within the same window (now the staticimage window is 250x250 in size):

Image

Both of these images look fine to me.

Lastly I will display the image at 2x (now the staticimage should be 1000x1000 in size - notice that the scrollbars for the scollablepane kick in):

Image

Notice that in this last image the circle no longer looks like a cirlce.. its now stretched horizontally. This effect is more pronounced with an even larger image window (I'll see if I can post another image).

Here is the source image:

Image

Here is the code that I use to "zoom", as you can see (within the last method applyImageZoom) all i really do is increase the size of the CEGUI window (the staticimage highlighted in the layout window above), that has the image as its background.

Code: Select all

   bool FocusImage::handleZoomFactorChanged(const CEGUI::EventArgs& e)
   {
      const CEGUI::WindowEventArgs& windowEventArgs = static_cast<const CEGUI::WindowEventArgs&>(e);
      CEGUI::Window *w = windowEventArgs.window;
      assert(WidgetMaker::getSingleton().isType(*w, WidgetMaker::DIALOGRADIOBUTTON) && "FocusImage::handleZoomFactorChanged: window type mismatch");
      CEGUI::RadioButton *rb = static_cast<CEGUI::RadioButton*>(w);
      if(!rb->isSelected())
      {
         return true;
      }
      
      float zoom;
      try
      {
         std::string widgetName(w->getName().c_str());
         std::string type = getZoomType(widgetName);

         if(type.compare(sZoom100RadioButton)==0)
         {
            zoom = 1.0;
         }
         else if(type.compare(sZoom200RadioButton)==0)
         {
            zoom = 2.0;
         }
         else if(type.compare(sZoom50RadioButton)==0)
         {
            zoom = 0.5;
         }
         else if(type.compare(sZoom400RadioButton)==0)
         {
            zoom = 4.0;
         }
         else
         {
            assert(0 && "FocusImage::handleZoomFactorChanged: unexpected zoom type");
         }
         applyImageZoom(zoom);
      }
      catch(SVTException se)
      {
         SVTConsole::getSingleton().printError(se);
      }

      return true;
   }

   std::string FocusImage::getZoomType(const std::string& widgetName)
   {
      std::string::size_type f = widgetName.rfind("/");
      assert((f != std::string::npos) && "FocusImage::getZoomType: Failed to extract zoom type");
      std::string zoomType = widgetName.substr(f+1);
      return zoomType;
   }

   void FocusImage::applyImageZoom(float zoomFactor)
   {
      int width = zoomFactor * mImgWindowWidth;
      int height = zoomFactor * mImgWindowHeight;
      CEGUI::URect a(CEGUI::UDim(0.0, 0), CEGUI::UDim(0.0, 0), CEGUI::UDim(0.0, width),  CEGUI::UDim(0.0, height));
      getDialogBaseRef().setWidgetArea(sIW, a);

      CEGUI::ScrollablePane *sp = static_cast<CEGUI::ScrollablePane *>(getDialogBaseRef().getNamedWidget(sSP));
      sp->setContentPaneAutoSized(false);
      sp->setContentPaneArea(CEGUI::Rect(0, 0, width, height));
   }


Another interesting thing to keep in mind. I added the ability to display the pixel location of the cursor.. its the pair of numbers you see in the lower left of the first three images (tough to see in white on the grey background).

The code that I use to display the pixel location is:

Code: Select all

bool FocusImage::handleImageWindowCursorMove(const CEGUI::EventArgs& e)
   {
      const CEGUI::MouseEventArgs& mouseEventArgs = static_cast<const CEGUI::MouseEventArgs&>(e);

      CEGUI::Window *window = mouseEventArgs.window;
      CEGUI::Point mousePos = mouseEventArgs.position;

      CEGUI::Point wndPos = CEGUI::CoordConverter::screenToWindow(*window, mousePos);

      int wndWidth = window->getWidth().asAbsolute(window->getParentPixelWidth());
      int wndHeight = window->getHeight().asAbsolute(window->getParentPixelHeight());
      float xScale = (float) mImgWidth / (float) wndWidth;
      float yScale = (float) mImgHeight / (float) wndHeight;
      CEGUI::Vector2 scale(xScale, yScale);
      
      CEGUI::Point imgPos = wndPos * scale;
   
      std::stringstream ssXCur;
      ssXCur <<  (int)(imgPos.d_x+0.5);
      getDialogBaseRef().getNamedWidget(sCursorXPosLabel)->setText(ssXCur.str());

      std::stringstream ssYCur;
      ssYCur << (int)(imgPos.d_y+0.5);
      getDialogBaseRef().getNamedWidget(sCursorYPosLabel)->setText(ssYCur.str());

   
      return true;
   }


I compare the pixel location displayed in this fashion to the pixel location drawn when I use a tool such as GIMP to display the image (and to view pixel level detail). The displayed location is correct for the first 2 images, but for the third case (once the scrollable pane kicks in) the pixel location is off. I'm pretty sure this is because the screenToWindow function is not accounting for the staticimage windows positional offset into the scrollablepane, and perhaps that is by design. I know this is a separate issue, but perhaps they are related somehow.

So I think the image squashing arises when the scrollable pane scrollbars kick in. I'm pretty sure it looks reasonable otherwise.

Any ideas for me to try?

Thanks.

---
Edit: I just put in a 4x option as well. As I suggested the stretching just becomes more pronounced. The odd thing is that though with the 2x and the 4x the image window should be 1000x1000 and 2000x2000 respectively it seems to be getting "cropped". I can tell this by printing out the window cursor position (in pixels - i.e. the value returned by CEGUI::CoordConverter::screenToWindow(*window, mousePos)) and I see that the cursor exits the imagewindow at a lower right hand corner of approximately 1020x760 pixels (at 4x), 1000x760 (at 2x). The lower right hand corner should be 2000x2000 (for 4x) and 1000x1000 (for the 2x).

Another indicator that the lower right hand corner is "cropped" relates to the EventMouseLeaves event that I capture. This event fires "prematurely" when I move the mouse down or to the right in the 2x and 4x windows.

I have no idea what is causing this, but I'm sure this is related to the stretching. This is awfully close to the 1024x768 which is the current resolution of my main window; can't imagine that this is mere coincidence. I do think this is the first time I've tried to size a CEGUI window to larger than the actual visible window.. is there something that I should keep in mind when doing this?

----Second Edit----
Ok Now I did exactly what you probably would have suggested. I upped the application resolution to 1920x1200. Now the 0.5x, 1.0x, and 2.0x images all look fine. The 4.0x image, which takes me beyond the 1920x1200, looks stretched... im onto something, but dont know what the fix is.

The key, or threshold, seems to be when the CEGUI::Window (the staticimage) is sized to be larger than the main application window... I'm pretty sure that somewhere under the covers the cegui window is "truncated" or "cropped" to be no larger than the application window. On top of this the truncation seems to be a hard limit applied independently in each direction (x/y). This is where the stretching comes in.

User avatar
CrazyEddie
CEGUI Project Lead
Posts: 6760
Joined: Wed Jan 12, 2005 12:06
Location: England
Contact:

Postby CrazyEddie » Thu Jul 24, 2008 08:49

As previously stated:
CrazyEddie wrote:The relative scale values in the max size for a CEGUI::Window is based on the display size, so while you'll get the required size at high resolutions, at lower resolutions your StaticImage size might get constrained to the display size - in this case you might want to consider using pixel values for the max size in order to side-step this issue.


The 'clipping' of the size of the image is related to the above - since the increase in resolution seems to reduce the stretching effect, I guess it's related to the image distortion also (sorry about the texture loading red herring - it's a real issue though :) )

To confirm an issue here, read back the screen area of the StaticText once you get the distortion (using Window::getPixelSize) it's almost certainly different than what it should be (so increase the max size as previously stated).

HTH

CE

daves
Home away from home
Home away from home
Posts: 253
Joined: Thu Feb 02, 2006 20:12

Postby daves » Thu Jul 24, 2008 09:39

Ah, so this truncation is expected. I read that before, and did not really understand it (sorry for being thick!!). I'll think about this and see if I can change my approach.

So called "red-herrings" never bother me, they get me thinking about all the kinds of things that I should be thinking about, so they are a good exercise for me.

:)

daves
Home away from home
Home away from home
Posts: 253
Joined: Thu Feb 02, 2006 20:12

Postby daves » Thu Jul 24, 2008 10:08

Wow. Its simply amazing to me. The solution to such problems is always so easy, and yet the getting there seems so hard.

CE you hit the nail on the head yet again. Let me see if I can pull out my 200th hand so that I can update the count of times that the you've whacked the nail; I use very crude calculators...

MaxSize in pixels solved my problem. So I wont forget this lesson... MaxSize (by default) relative and will restrict a window to the screen resolution.... however, as with everything in cegui, the flexibility is there to grow beyond the screen resolution.

User avatar
CrazyEddie
CEGUI Project Lead
Posts: 6760
Joined: Wed Jan 12, 2005 12:06
Location: England
Contact:

Postby CrazyEddie » Thu Jul 24, 2008 13:08

daves wrote:The solution to such problems is always so easy, and yet the getting there seems so hard.

This is is very true of CEGUI, and probably sums up most people's experience of the library. I think it's also true of things in general also; it highlights the importance of quickly identifying the real issue - which is not always the issue you think it is to start with - in order to know where to look for possible solutions.

CE.

doomedfox
Just popping in
Just popping in
Posts: 4
Joined: Thu Jul 10, 2008 19:31
Contact:

Postby doomedfox » Thu Jul 24, 2008 18:01

CrazyEddie wrote:In the other renderer modules I modified the loading of images in such a way that where needed we manually transfer the image data into the texture buffer; this way we have the needed control to ensure that the image is not stretched, leaving the oversized parts of the texture as 'spare' space. I've not looked at how to do that in Ogre, but it would be a rare omission if there were not some provision for this to be done.


Hi, can I just make a suggestion,

I use the same approach described by CE to load images that don't have powers of 2 metrics. Let me describe it with little code, may be it can help :

Code: Select all

Ogre::Image mImage;
Ogre::TexturePtr mTexture;

mImage.load("filename.ext",GROUPE);

Ogre::TextureManager::getSingletonPtr()->createManual(associatedTextureName,                                                   assignedGroup,Ogre::TEX_TYPE_2D,srcWidth,srcHeight,1,0,Ogre::PF_A8R8G8B8);


This code loads the image file and create a manual texture, now we are able to blit the original image into this newly created texture :

Code: Select all

Ogre::Box imageBox;
imageBox.top=0;
imageBox.left=0;
imageBox.front=0;
imageBox.back=1;
imageBox.right=srcWidth;
imageBox.bottom=srcHeight;

// We just blit the src image directly in the manual texture. This
// prevents image scaling.
mTexture->getBuffer()->blitFromMemory(internalImage.getPixelBox(),imageBox);

The resulting texture contains our image file in it's original metrics without modifications, I use this approach to load my icons etc. I really think that it can resolve the problem.

As a side note, manual textures must be managed differently, especially for reload.

Hope this will help.

/Mehdi

User avatar
CrazyEddie
CEGUI Project Lead
Posts: 6760
Joined: Wed Jan 12, 2005 12:06
Location: England
Contact:

Postby CrazyEddie » Fri Jul 25, 2008 08:45

Thanks for posting how to do that :)

CE


Return to “Help”

Who is online

Users browsing this forum: No registered users and 16 guests