Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: subpixel accuracy of sprite of image  (Read 15672 times)

0 Members and 1 Guest are viewing this topic.

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« on: January 30, 2008, 10:57:17 pm »
Hi there

Since I am a nitpicker regarding pixel moving I stumbled upon a problem: when I create a sprite from an image and draw that sprite on the screen, the border pixel of the image are only half as big as the inner pixel of the image.

...and now with pictures:

Source image, 32 x 32 pixel, with colorful 1 pixel wide border and checkerboard within:



Screenshot, rendered with SFML, sprite scaled quite large and rendered four times around the center of the screen:



As you can see the colorful border pixel are only half as big as the black and white checkerboard pixel.

Relevant piece of code:
Code: [Select]

  // render window
  sf::RenderWindow MyRenderWindow(sf::VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT), "SFML", sf::Style::Close);
  MyRenderWindow.OptimizeForNonOpenGL(true);
  MyRenderWindow.SetBackgroundColor(sf::Color(255, 0, 255));

  // image
  sf::Image MyImage;
  MyImage.LoadFromFile(IMAGE_FILE);
  MyImage.SetSmooth(false);

  // sprite
  sf::Sprite MySprite(MyImage);
  MySprite.Scale(20.f, 20.f);

  // center of screen
  float CenterX = (float) MyRenderWindow.GetWidth()  / 2.0f;
  float CenterY = (float) MyRenderWindow.GetHeight() / 2.0f;

  // big loop
  while (Looping)
  {
    // draw top left sprite
    MySprite.SetPosition(CenterX - MySprite.GetWidth(), CenterY - MySprite.GetHeight());
    MyRenderWindow.Draw(MySprite);

    // draw top right sprite
    MySprite.SetPosition(CenterX                      , CenterY - MySprite.GetHeight());
    MyRenderWindow.Draw(MySprite);

    // draw bottom left sprite
    MySprite.SetPosition(CenterX - MySprite.GetWidth(), CenterY                       );
    MyRenderWindow.Draw(MySprite);

    // draw bottom right sprite
    MySprite.SetPosition(CenterX                      , CenterY                       );
    MyRenderWindow.Draw(MySprite);

    // display screen
    MyRenderWindow.Display();


I don't like that and I would like to change that. Unfortunately I'm not sure whether it's due to SFML or due to OpenGL or due to the fact that I am totally missing something. Please somebody enlighten me  :D

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
subpixel accuracy of sprite of image
« Reply #1 on: January 31, 2008, 02:34:51 am »
Weird, that's not supposed to happen. Which version of SMFL are you using ?
Laurent Gomila - SFML developer

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« Reply #2 on: January 31, 2008, 09:33:16 pm »
I was using revision 408. I now updated to revision 442 but the result is exactly the same.

Any other info I could provide? Do you want the whole VC2005 project of my test application?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
subpixel accuracy of sprite of image
« Reply #3 on: February 01, 2008, 02:47:24 am »
Quote
Do you want the whole VC2005 project of my test application?

Yep, I'll try it at home.
Laurent Gomila - SFML developer

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
subpixel accuracy of sprite of image
« Reply #4 on: February 03, 2008, 09:56:54 am »
The problem seems to come from the high scale. Using a bigger image and applying no scale works just fine. I guess it has to do with the OpenGL filtering / rasterization algorithm, combined with my texture coordinate trick (subtracting a half-texel to make the OpenGL coordinates match the screen ones).
Laurent Gomila - SFML developer

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« Reply #5 on: February 04, 2008, 08:06:30 pm »
So whenever I "zoom in" on a sprite by giving it any scale over 1.0 the border of the sprite looses its "pixel preciseness"? Sorry to say, but I'd consider that a bummer :shock:

For something I am tinkering on I do have a seamless grass texture being 256 x 256 pixel in size. My application "tiles" that texture to create a grass landscape and can zoom in and out from it. I stumbled about that pixel un-precision because I noticed "edges" between my perfectly fine seamless textures when I zoomed in on my terrain, not only at "zoom deep in" but at any kind of "zoom in".

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
subpixel accuracy of sprite of image
« Reply #6 on: February 05, 2008, 02:13:03 am »
Quote
So whenever I "zoom in" on a sprite by giving it any scale over 1.0 the border of the sprite looses its "pixel preciseness"? Sorry to say, but I'd consider that a bummer

I wouldn't say that, I think it will produce this kind of issue only with high scales.

Anyway, I don't think we can avoid this when using scale or zoom, as the scene units become different from screen pixels ; you just can't get a perfect mapping.
Laurent Gomila - SFML developer

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« Reply #7 on: February 06, 2008, 11:34:23 pm »
Quote from: "Laurent"
I wouldn't say that, I think it will produce this kind of issue only with high scales.

No. It happens with any scale larger than 1.0.

Quote from: "Laurent"
Anyway, I don't think we can avoid this when using scale or zoom

:cry:


While trying that again out I even stumbled about the next problem (sooorry), this time related to image smoothing.


Original image - 32x32 pixel, drawn 4 times around center of screen, image smoothing is enabled:



Scale factor 1.0 - please note those nasty "circles", where do those come from?!?
Might they come from the "high contrast" checkerboard plus some fancy Moire patterns?
But at a scale factor of 1.0 everything should be copied exactly, shouldn't it?
(sidenote: image is reduced to 256 colors, but even in full color mode those circles are clearly visible)



Scale factor 1.1 - please note the "circles" and please even note the "half size border pixel":



Scale factor 1.5 - including "half size border pixel":



Scale factor 2.0 - including "circles" (even while not that visible) and "half size border pixel":



Scale factor 0.9 - "circles":



Scale factor 0.5 - total bogus?!




SFML revision 449. Help.


Code: [Select]
// --- includes ---

// windows
#include <windows.h>

// SFML
#include "SFML/Graphics.hpp"

// STL
#include <iostream>

// --- defines ---

#define IMAGE_FILE "checkerboard_1x1.bmp"
// #define IMAGE_FILE "image_1x1.bmp"
const int SCREEN_WIDTH = 200;
const int SCREEN_HEIGHT = 200;
const float SCALE_FACTOR = 1.0f;
const bool IMAGE_SMOOTHING = true;

// --- signal handler ---

BOOL WINAPI MyHandlerRoutine(DWORD dwCtrlType)
{
  std::cout << "exiting the hard way" << std::endl;
  exit(0);
  return TRUE;
}

// --- run ---

bool runSFML()
{
  // variables
  bool Looping(true);
  sf::Event MyEvent;

  // render window
  sf::RenderWindow MyRenderWindow(sf::VideoMode(SCREEN_WIDTH, SCREEN_HEIGHT), "SFML", sf::Style::Close);
  MyRenderWindow.OptimizeForNonOpenGL(true);
  MyRenderWindow.SetBackgroundColor(sf::Color(255, 0, 255));

  // image
  sf::Image MyImage;
  if (!MyImage.LoadFromFile(IMAGE_FILE))
  {
    std::cout << "failed to load image '" << IMAGE_FILE << "'" << std::endl;
    return false;
  }
  MyImage.SetRepeat(true);
  MyImage.SetSmooth(IMAGE_SMOOTHING);

  // sprite
  sf::Sprite MySprite(MyImage);
  MySprite.Scale(SCALE_FACTOR, SCALE_FACTOR);

  // center of screen
  float CenterX = (float) MyRenderWindow.GetWidth()  / 2.0f;
  float CenterY = (float) MyRenderWindow.GetHeight() / 2.0f;

  // big loop
  while (Looping)
  {
    // draw top left sprite
    MySprite.SetPosition(CenterX - MySprite.GetWidth(), CenterY - MySprite.GetHeight());
    MyRenderWindow.Draw(MySprite);

    // draw top right sprite
    MySprite.SetPosition(CenterX                      , CenterY - MySprite.GetHeight());
    MyRenderWindow.Draw(MySprite);

    // draw bottom left sprite
    MySprite.SetPosition(CenterX - MySprite.GetWidth(), CenterY                       );
    MyRenderWindow.Draw(MySprite);

    // draw bottom right sprite
    MySprite.SetPosition(CenterX                      , CenterY                       );
    MyRenderWindow.Draw(MySprite);

    // display screen
    MyRenderWindow.Display();

    // poll events
    while (MyRenderWindow.GetEvent(MyEvent))
    {
      // check whether window was closed
      if (MyEvent.Type == sf::Event::Closed)
      {
        Looping = false;
      }

      // check whether escape was pressed
      if ((MyEvent.Type == sf::Event::KeyReleased) && (MyEvent.Key.Code == sf::Key::Escape))
      {
        Looping = false;
      }
    }
  }

  return true;
}

// --- main ---

int main(int argc, char *argv[])
{
  // add signal handler routine
  if (!SetConsoleCtrlHandler(MyHandlerRoutine, TRUE))
  {
    std::cout << "failed to add signal handler routine" << std::endl;
    exit(1);
  }

  // start
  std::cout << "started" << std::endl;

  // run SFML
  runSFML();

  // stopped
  std::cout << "stopped" << std::endl;

  return 0;
}

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« Reply #8 on: February 06, 2008, 11:55:38 pm »
...and another post that I am not just nitpicking in checkerboard stress tests but actually do notice that effects on "real life data" alias "I want to draw a landscape made of grass tiles":

Original image - which tiles wonderfully seamless:



Scale factor 1.0 - when looking close I can see an "edge":



Scale factor 2.0 - I obviously do see an "edge":



Those "edges"  get even more visible when I scroll around over a landscape made of those tiles.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
subpixel accuracy of sprite of image
« Reply #9 on: February 07, 2008, 01:58:34 am »
Good demonstration of the problem ;)

I'll see what I can do.
Laurent Gomila - SFML developer

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« Reply #10 on: February 07, 2008, 08:12:49 pm »
Ha! After tinkering around in Sprite.cpp and changing line 184 in Sprite::Render from...
Code: [Select]
// Calculate the texture coordinates
FloatRect TexCoords = myImage->GetTexCoords(mySubRect);

...into...
Code: [Select]
// Calculate the texture coordinates
FloatRect TexCoords = myImage->GetTexCoords(mySubRect, false);

...and by so basically disabling your "half texel texture coordinate trick" I now get the following results:

Checkerboard 1.0:



Checkerboard 1.5:



Checkerboard 2.0:



Checkerboard 0.5:



Grass 2.0:



 :D  :D  :D

This leads me to the following conclusions:

My original "half border pixel" problem is caused (or at least affected) by your "texture coordinate trick" and by disabling that "trick" I exactly totally 100% get what I want (pixel perfect seamless landscapes). On the other hand I do not know why you initially put that "trick" into the code. But maybe you give that reason a second thought (please).

My following "strangle circles appearing" problem seems to be caused by some Moire patterns on my "extreme contrast checkerboard" image in combination with the "not pixel precise" usage of the image. When the pixel precision is done correctly the circles simply disappear.

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« Reply #11 on: February 07, 2008, 09:24:14 pm »
P.S.: are the values in the following code snippet casting in a correct way so that no precision is lost? Personally I'd put several (float) in front of the Rect.Left, Rect.Top, Rect.Right and Rect.Bottom because int + float = int in my opinion and I'd prefer to have (float) int + float = float.
Code: [Select]
FloatRect Image::GetTexCoords(const IntRect& Rect, bool Adjust) const
{
    float Offset = Adjust ? 0.5f : 0.f;
    return FloatRect((Rect.Left   + Offset) / myTextureWidth,
                     (Rect.Top    + Offset) / myTextureHeight,
                     (Rect.Right  - Offset) / myTextureWidth,
                     (Rect.Bottom - Offset) / myTextureHeight);
}

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32504
    • View Profile
    • SFML's website
    • Email
subpixel accuracy of sprite of image
« Reply #12 on: February 08, 2008, 05:16:51 am »
Quote
On the other hand I do not know why you initially put that "trick" into the code. But maybe you give that reason a second thought (please).

OpenGL texels doesn't map exactly to the screen pixels, they are offseted by half a pixel. If I don't use this trick, I get unwanted pixels on the borders of images with smoothing activated. But I think I could just use the trick when smoothing is enabled.

Quote
P.S.: are the values in the following code snippet casting in a correct way so that no precision is lost? Personally I'd put several (float) in front of the Rect.Left, Rect.Top, Rect.Right and Rect.Bottom because int + float = int in my opinion and I'd prefer to have (float) int + float = float.

int + float = float.
If there's at least one float as an operand, the result of any operator will be a float.
Laurent Gomila - SFML developer

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« Reply #13 on: February 08, 2008, 09:05:44 am »
Quote from: "Laurent"
If I don't use this trick, I get unwanted pixels on the borders of images with smoothing activated.

Isn't that what GL_REPEAT and GL_CLAMP are for? (assuming the sprite is made of the whole image).

In case you have one image and make several sprites out of it, yes, not using the trick will include unwanted pixel of the neighbor sprite. To be honest that even is my full intention: I put my grass tile 25 times into an image (5x5) and make a sprite out of the center tile to achieve a correct smoothing "to the neighbor" and so a perfectly seamless landscape. If it would be that single sprite only I could use GL_REPEAT, too, but in those 24 tiles around the center are several "borders" to create "round borders of grass". And for those "half grass, half empty" tiles I indeed don't want any neighbor pixel included on an "empty" edge - so my source image is GL_CLAMP.

Finally a suggestion:

Code: [Select]
////////////////////////////////////////////////////////////
/// Enable or disable several image options:
/// Repeat: how to define pixels outside the texture range
/// Smooth: image smoothing filter
/// AdjustTexel: prevent pixel outside the sprite range being
///              used when smoothing filter is enabled
////////////////////////////////////////////////////////////
void Image::SetOptions(bool Repeat, bool Smooth, bool AdjustTexel)
{
    // Remember options
    myRepeat = Repeat;
    mySmooth = Smooth;
    myAdjustTexel = AdjustTexel;

    if (myGLTexture)
    {
        // Change OpenGL texture wrap mode
        GLint PreviousTexture;
        GLCheck(glGetIntegerv(GL_TEXTURE_BINDING_2D, &PreviousTexture));
        GLCheck(glBindTexture(GL_TEXTURE_2D, myGLTexture));
        GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, Repeat ? GL_REPEAT : GL_CLAMP));
        GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, Repeat ? GL_REPEAT : GL_CLAMP));
        GLCheck(glBindTexture(GL_TEXTURE_2D, PreviousTexture));

        // Change OpenGL texture filter
        GLint PreviousTexture;
        GLCheck(glGetIntegerv(GL_TEXTURE_BINDING_2D, &PreviousTexture));
        GLCheck(glBindTexture(GL_TEXTURE_2D, myGLTexture));
        GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, Smooth ? GL_LINEAR : GL_NEAREST));
        GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Smooth ? GL_LINEAR : GL_NEAREST));
        GLCheck(glBindTexture(GL_TEXTURE_2D, PreviousTexture));
    }
}

////////////////////////////////////////////////////////////
/// Convert a subrect expressed in pixels, into float
/// texture coordinates
////////////////////////////////////////////////////////////
FloatRect Image::GetTexCoords(const IntRect& Rect) const
{
    float Offset = mySmooth && myAdjustTexel ? 0.5f : 0.f;
    return FloatRect((Rect.Left   + Offset) / myTextureWidth,
                     (Rect.Top    + Offset) / myTextureHeight,
                     (Rect.Right  - Offset) / myTextureWidth,
                     (Rect.Bottom - Offset) / myTextureHeight);
}

////////////////////////////////////////////////////////////
/// Create the OpenGL texture
////////////////////////////////////////////////////////////
bool Image::CreateTexture()
{
    // [...snip...]

    // Create the OpenGL texture
    GLCheck(glGenTextures(1, (GLuint*)&myGLTexture));
    GLCheck(glBindTexture(GL_TEXTURE_2D, myGLTexture));
    GLCheck(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, myTextureWidth, myTextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL));

    // Set texture options
    SetOptions(bool myRepeat, bool mySmooth, bool myAdjustTexel)

    // Copy the pixel buffer to the OpenGL texture
    Update();

    // Put back the previous texture
    GLCheck(glBindTexture(GL_TEXTURE_2D, PreviousTexture));

    return true;
}

T.T.H.

  • Full Member
  • ***
  • Posts: 112
    • View Profile
subpixel accuracy of sprite of image
« Reply #14 on: March 10, 2008, 10:54:17 pm »
(note: late answer because I was in holidays)

Updating to 492 I've seen you changed something in 474:
Quote from: "svn log"
Removed Image::SetRepeat (useless and even incorrect when texture was padded with extra pixels)
Removed the "half-pixel trick" on texture coordinates when an image is not smooth

Unfortunately my problem still exists: there is a visible edge between my seamless tiles.


To solve my problem (and doing so without hacking around inside SFML itself) I need a way to control the "half-pixel trick" myself (via some SFML API). Currently this is not possible because Sprite::Render calls...
Code: [Select]
// Calculate the texture coordinates
// FloatRect TexCoords = myImage->GetTexCoords(mySubRect);

...and in Image.hpp...
Code: [Select]
FloatRect GetTexCoords(const IntRect& Rect, bool Adjust = true) const;
...the default value for the parameter Adjust is true.


In addition I'd like to get back an API to set the "repetition" of images or at least to have the default value GL_REPEAT instead of GL_CLAMP for the setting GL_TEXTURE_WRAP of images. You are even saying yourself about GL_CLAMP...
Quote
...(useless and even incorrect when texture was padded with extra pixels)



Yes, I could make both those changes locally in my very own version of SFML, but a) I'd prefer to have an API for it and b) I'm probably not the only person wishing pixel preciseness.

 

anything