SDL_RenderCopy stretch loses proportion on viewport boundaries

Hello list!

I have bumped into a very annoying issue. It’s weird, so I’d love to
hear what you have to say.

SDL_RenderCopy clips dstrect against the viewport. Then it adjusts the
srcrect by “appropriate” amount of pixels. This amount is actually
wrong, quite a lot, because of the rounding errors introduced in the “*
factor / factor” scale.

        real_srcrect.x += (deltax * real_srcrect.w) / dstrect->w;
        real_srcrect.w += (deltaw * real_srcrect.w) / dstrect->w;

For example:

I have a 32 x 32 srcrect and a 64 x 64 dstrect. So far the
stretching is done perfectly, by a factor of 2.

Now, consider dstrect being clipped against the viewport, so it becomes
56 x 64. Now, the factor becomes 1.75 ! The adjustment to "srcrect"
can’t handle this, cause srcrect is in integers.

And thus we now have incorrect mapping, with dstrect not being in the
right proportion to srcrect.

The problem is most evident when upscaling stuff, like displaying a 8x8
texture with a zoom of 64 or more, and moving it beyond the corners of
the screen. It looks really really bad.

  • Do we really need to clip dstrect against viewport? In OpenGL, I just
    drew stuff and the viewport itself did the clipping for me. Avoided
    the whole mess of uv/uw-screen mapping. Is it considered non-optimal?

  • If the clipping is needed for some specific / weird renderers that
    can’t handle this, would it be possible to move it to the renderers
    in question, and leave the RenderCopy unclipped?

Not filling a bug report, as I’m feeling I’m missing something obvious
here.

Simple program to showcase the behavior:

/* gcc sdl2-config --cflags -o rect sdl2-config --libs rect.c
&& ./rect */ #include <SDL.h>

/* Create a 8x1 texture with 8 pixels of different color */
SDL_Texture *create_pattern(SDL_Renderer *renderer) {
SDL_Texture ret; int i;
//create 8x1 32bit surface
SDL_Surface surf = SDL_CreateRGBSurface(0,8,1,32,0,0,0,0);
//fill with 8 colors
for (i = 0; i < 8; i++) {
SDL_Rect target;
target.w = target.h = 1;
target.y = 0; target.x = i;
SDL_FillRect(surf, &target,
SDL_MapRGB(surf->format, rand()%255, rand()%255,
rand()%255)); }
//create a texture from it
ret = SDL_CreateTextureFromSurface(renderer, surf);
SDL_FreeSurface(surf);
return ret;
}
#define ZOOM 32
int main(int argc, char
argv[]) {
SDL_Window
window;
SDL_Renderer *renderer;
SDL_Texture *pattern;
SDL_Rect dstrect = { 0, 0, 8 * ZOOM, 1 * ZOOM };
int done = 0;
int x = ZOOM;
int dx = -1;

SDL_Init(SDL_INIT_VIDEO);
SDL_CreateWindowAndRenderer(320, 240, SDL_WINDOW_SHOWN,

&window, &renderer);
pattern = create_pattern(renderer); //create our texture

SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);

while (!done) {
	SDL_Event event;
	SDL_zero(event);
	while (SDL_PollEvent(&event)) 
		if (event.type == SDL_QUIT) done = 1; 
	SDL_RenderClear(renderer);    		
	
	dstrect.x = x;
	dstrect.y = 0;
	//render this texture at x, 0
	SDL_RenderCopy(renderer, pattern, NULL, &dstrect);
	SDL_RenderPresent(renderer);
	SDL_Delay(100);
	//move x back and forth
	x += dx;
	if (x < -1 * ZOOM) dx = 1; 
	else if (x > 320 - 7 * ZOOM) dx = -1;
}

SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;

}–
driedfruit

Followup:

SDL_RenderCopyEX does not clip the dstrect, with this curious comment:

/* We don't intersect the dstrect with the viewport as RenderCopy
does because of potential rotation clipping issues... TODO: should
we? */

So, for now, I’ll just move to RenderCopyEX for all my stretched needs.

But, answering to the comment’s question, I’d argue, that NO, we should
not! And that we probably shouldn’t in RenderCopy either, “because of
potential stretching issues”. If we must do it in RenderCopy, this
should be clearly documented and a user should be pointed to
RenderCopyEX.

If, for some reason, we really really have to, I would at least suggest
we move it to a later uw/uv stage, as those are floats, so there’s less
precision loss during the adjustment.

Even if there’s no change to the code, I’ll submit a comment and a wiki
patch at the end of this thread :)On Wed, 19 Jun 2013 18:32:57 +0400 Driedfruit <@Driedfruit> wrote:

Hello list!

I have bumped into a very annoying issue. It’s weird, so I’d love to
hear what you have to say.

SDL_RenderCopy clips dstrect against the viewport. Then it adjusts the
srcrect by “appropriate” amount of pixels. This amount is actually
wrong, quite a lot


driedfruit

Really, the only renderer that should need viewport clipping at all is
the software renderer. All other renderers rely on the GPU for drawing
and in those cases the GPU will do the job on its own.

As a bonus, it’s very likely SDL_RenderCopy in the software renderer
already does non-integer scaling internally, simply because the
logical resolution can be different from the real resolution and
thereby images need to be scaled either way.

That’s my comment, the reason why there’s no clipping done is that rotation
happens after the clipping is applied (if it were applied!), which can
clearly be problematic. The TODO note is me just doubting myself :slight_smile:

2013/6/19 Driedfruit > Followup:

SDL_RenderCopyEX does not clip the dstrect, with this curious comment:

/* We don't intersect the dstrect with the viewport as RenderCopy
does because of potential rotation clipping issues... TODO: should
we? */

So, for now, I’ll just move to RenderCopyEX for all my stretched needs.

But, answering to the comment’s question, I’d argue, that NO, we should
not! And that we probably shouldn’t in RenderCopy either, “because of
potential stretching issues”. If we must do it in RenderCopy, this
should be clearly documented and a user should be pointed to
RenderCopyEX.

If, for some reason, we really really have to, I would at least suggest
we move it to a later uw/uv stage, as those are floats, so there’s less
precision loss during the adjustment.

Even if there’s no change to the code, I’ll submit a comment and a wiki
patch at the end of this thread :slight_smile:

On Wed, 19 Jun 2013 18:32:57 +0400 Driedfruit wrote:

Hello list!

I have bumped into a very annoying issue. It’s weird, so I’d love to
hear what you have to say.

SDL_RenderCopy clips dstrect against the viewport. Then it adjusts the
srcrect by “appropriate” amount of pixels. This amount is actually
wrong, quite a lot


driedfruit


SDL mailing list
SDL at lists.libsdl.org
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org


Gabriel.

Really, the only renderer that should need viewport clipping at all is
the software renderer.

I’ve just checked - software renderer does it on it’s own, anyway. And
it does it as badly. So, I completely agree, viewport clipping should be
removed from RenderCopy. Would also shave off few math operations from
that hard-hitted function.

And ideally, software renderer would be fixed to be more precise.On Wed, 19 Jun 2013 12:06:34 -0300 Sik the hedgehog <sik.the.hedgehog at gmail.com> wrote:


driedfruit

That’s my comment, the reason why there’s no clipping done is that
rotation happens after the clipping is applied (if it were applied!),
which can clearly be problematic. The TODO note is me just doubting
myself :slight_smile:

The question is not why there’s no clipping in RenderCopyEx – the
potential problems you were doubtful about are quite obvious; the
question is why there’s clipping in RenderCopy in the first place :slight_smile:

As it stands now, using RenderCopyEx is a nice work-around for
RenderCopy’s refusal to stretch properly.On Wed, 19 Jun 2013 12:20:54 -0300 Gabriel Jacobo wrote:


driedfruit

Hi, I’m getting some weird behaviour with the size of text.
I’m using a monospaced font, i set kerning off to make sure nothing
funny happens (mono fonts shouldn’t kern anyhow).

So I am printing a string two ways:

(a) as a whole string
(b) in two pieces

To line the two pieces up, I am using TTF_SizeText.

However this doesn’t always work!

What’s actually happening is I have a text editor and when
I click the “caret” moves to the mouse and the RHS of the line
gets pushed one pixel to the right … sometimes, depending
on where I click.

If I click before an M or N I get the bug. But T is ok :wink:

It seems almost like the chars are too big, but then the oversize
is kerned away, but only if there’s a preceding character.

[This may be a bug in freetype, I have 2.4.12]–
john skaller
@john_skaller
http://felix-lang.org

Hi, I’m getting some weird behaviour with the size of text.

If I click before an M or N I get the bug. But T is ok ;

Further thought on this: the bug has to be in the rendering
of the font to the surface. Its putting an extra pixel at the front
when the first character is an N or M but not a T.

Everything lines up with strings printed in one go.
My start positioning cannot be wrong because it doesn’t know
anything about the characters being displayed.

Ah yes, just measure it.

The SDL_surface is the incorrect width.
Its one pixel too big. Or worse! The font size is width 7.
A M is giving 8. a lower case m gives 9 if its the only char.
two m’s give 16.

now I think a bug I got before may be relevant.
Using Courier New.ttf, lower case e was raised one pixel
too high. [I have checked the fonts in my terminal, and they’re fine]

Hmm … SDL or freetype?On 20/06/2013, at 3:33 AM, john skaller wrote:


john skaller
@john_skaller
http://felix-lang.org

Ok, I now know what is happening. Unfortunately this seems
to be a design bug in SDL_ttf, i.e. it cannot be fixed.

Here’s the data:

We compiled against SDL version 2.0.0 …
But we are linking against SDL version 2.0.0.
We compiled against TTF version 2.0.12 …
But we are linking against TTF version 2.0.12.
We compiled against IMG version 2.0.0 …
But we are linking against IMG version 2.0.0.
Opened Font /Library/Fonts/Courier New Bold.ttf Facename: Courier New MONOSPACED
Metrics char 65=65’A’ = minx=0 maxx=7 adv=7
Metrics char 66=66’B’ = minx=-1 maxx=6 adv=7
Metrics char 67=67’C’ = minx=0 maxx=6 adv=7
Metrics char 68=68’D’ = minx=-1 maxx=6 adv=7
Metrics char 69=69’E’ = minx=-1 maxx=6 adv=7
Metrics char 70=70’F’ = minx=-1 maxx=6 adv=7
Metrics char 71=71’G’ = minx=0 maxx=7 adv=7
Metrics char 72=72’H’ = minx=-1 maxx=7 adv=7
Metrics char 73=73’I’ = minx=0 maxx=6 adv=7
Metrics char 74=74’J’ = minx=0 maxx=7 adv=7
Metrics char 75=75’K’ = minx=-1 maxx=7 adv=7
Metrics char 76=76’L’ = minx=-1 maxx=6 adv=7
Metrics char 77=77’M’ = minx=-1 maxx=7 adv=7
Metrics char 78=78’N’ = minx=-1 maxx=7 adv=7
Metrics char 79=79’O’ = minx=0 maxx=6 adv=7
Metrics char 80=80’P’ = minx=0 maxx=6 adv=7
Metrics char 81=81’Q’ = minx=0 maxx=6 adv=7
Metrics char 82=82’R’ = minx=-1 maxx=7 adv=7
Metrics char 83=83’S’ = minx=0 maxx=6 adv=7
Metrics char 84=84’T’ = minx=0 maxx=6 adv=7
Metrics char 85=85’U’ = minx=-1 maxx=7 adv=7
Metrics char 86=86’V’ = minx=-1 maxx=7 adv=7
Metrics char 87=87’W’ = minx=-1 maxx=8 adv=7
Metrics char 88=88’X’ = minx=-1 maxx=7 adv=7
Metrics char 89=89’Y’ = minx=-1 maxx=7 adv=7
Metrics char 90=90’Z’ = minx=0 maxx=6 adv=7
Metrics char 97=97’a’ = minx=0 maxx=7 adv=7
Metrics char 98=98’b’ = minx=-1 maxx=6 adv=7
Metrics char 99=99’c’ = minx=0 maxx=6 adv=7
Metrics char 100=100’d’ = minx=0 maxx=7 adv=7
Metrics char 101=101’e’ = minx=0 maxx=6 adv=7
Metrics char 102=102’f’ = minx=1 maxx=6 adv=7
Metrics char 103=103’g’ = minx=0 maxx=7 adv=7
Metrics char 104=104’h’ = minx=0 maxx=7 adv=7
Metrics char 105=105’i’ = minx=0 maxx=6 adv=7
Metrics char 106=106’j’ = minx=0 maxx=5 adv=7
Metrics char 107=107’k’ = minx=0 maxx=7 adv=7
Metrics char 108=108’l’ = minx=0 maxx=6 adv=7
Metrics char 109=109’m’ = minx=-1 maxx=8 adv=7
Metrics char 110=110’n’ = minx=0 maxx=7 adv=7
Metrics char 111=111’o’ = minx=0 maxx=6 adv=7
Metrics char 112=112’p’ = minx=-1 maxx=6 adv=7
Metrics char 113=113’q’ = minx=0 maxx=7 adv=7
Metrics char 114=114’r’ = minx=0 maxx=6 adv=7
Metrics char 115=115’s’ = minx=0 maxx=6 adv=7
Metrics char 116=116’t’ = minx=0 maxx=6 adv=7
Metrics char 117=117’u’ = minx=0 maxx=7 adv=7
Metrics char 118=118’v’ = minx=0 maxx=7 adv=7
Metrics char 119=119’w’ = minx=-1 maxx=8 adv=7
Metrics char 120=120’x’ = minx=0 maxx=7 adv=7
Metrics char 121=121’y’ = minx=0 maxx=7 adv=7
Metrics char 122=122’z’ = minx=1 maxx=7 adv=7

Now, you see with this font, the character bounding boxes vary,
the mono-spacing is entirely a property of the advance metric.

So when SDL_TTF renders a character with a negative minx within
a string, it just renders left of the origin. However if it is left of the
physical first pixel it cannot do that, so it shifts the character right.

The correct way to do this is that TTF_Render … should write on a user
supplied surface. Alternately the user could specify the origin.
It really messy no matter what.

I can fix this by calculating the minx for the first character in the string
and rendering the surface to the left of where it should be.

There’s probably a similar problem with maxx.On 20/06/2013, at 4:22 AM, john skaller wrote:


john skaller
@john_skaller
http://felix-lang.org

Yeah, it would be nice to be able to provide your own buffer. Barring
that, you do need to use TTF_GlyphMetrics() to figure out where to place
the resulting surface when blitting.

Jonny DOn Wed, Jun 19, 2013 at 10:07 PM, john skaller < skaller at users.sourceforge.net> wrote:

On 20/06/2013, at 4:22 AM, john skaller wrote:

Ok, I now know what is happening. Unfortunately this seems
to be a design bug in SDL_ttf, i.e. it cannot be fixed.

Here’s the data:

We compiled against SDL version 2.0.0 …
But we are linking against SDL version 2.0.0.
We compiled against TTF version 2.0.12 …
But we are linking against TTF version 2.0.12.
We compiled against IMG version 2.0.0 …
But we are linking against IMG version 2.0.0.
Opened Font /Library/Fonts/Courier New Bold.ttf Facename: Courier New
MONOSPACED
Metrics char 65=65’A’ = minx=0 maxx=7 adv=7
Metrics char 66=66’B’ = minx=-1 maxx=6 adv=7
Metrics char 67=67’C’ = minx=0 maxx=6 adv=7
Metrics char 68=68’D’ = minx=-1 maxx=6 adv=7
Metrics char 69=69’E’ = minx=-1 maxx=6 adv=7
Metrics char 70=70’F’ = minx=-1 maxx=6 adv=7
Metrics char 71=71’G’ = minx=0 maxx=7 adv=7
Metrics char 72=72’H’ = minx=-1 maxx=7 adv=7
Metrics char 73=73’I’ = minx=0 maxx=6 adv=7
Metrics char 74=74’J’ = minx=0 maxx=7 adv=7
Metrics char 75=75’K’ = minx=-1 maxx=7 adv=7
Metrics char 76=76’L’ = minx=-1 maxx=6 adv=7
Metrics char 77=77’M’ = minx=-1 maxx=7 adv=7
Metrics char 78=78’N’ = minx=-1 maxx=7 adv=7
Metrics char 79=79’O’ = minx=0 maxx=6 adv=7
Metrics char 80=80’P’ = minx=0 maxx=6 adv=7
Metrics char 81=81’Q’ = minx=0 maxx=6 adv=7
Metrics char 82=82’R’ = minx=-1 maxx=7 adv=7
Metrics char 83=83’S’ = minx=0 maxx=6 adv=7
Metrics char 84=84’T’ = minx=0 maxx=6 adv=7
Metrics char 85=85’U’ = minx=-1 maxx=7 adv=7
Metrics char 86=86’V’ = minx=-1 maxx=7 adv=7
Metrics char 87=87’W’ = minx=-1 maxx=8 adv=7
Metrics char 88=88’X’ = minx=-1 maxx=7 adv=7
Metrics char 89=89’Y’ = minx=-1 maxx=7 adv=7
Metrics char 90=90’Z’ = minx=0 maxx=6 adv=7
Metrics char 97=97’a’ = minx=0 maxx=7 adv=7
Metrics char 98=98’b’ = minx=-1 maxx=6 adv=7
Metrics char 99=99’c’ = minx=0 maxx=6 adv=7
Metrics char 100=100’d’ = minx=0 maxx=7 adv=7
Metrics char 101=101’e’ = minx=0 maxx=6 adv=7
Metrics char 102=102’f’ = minx=1 maxx=6 adv=7
Metrics char 103=103’g’ = minx=0 maxx=7 adv=7
Metrics char 104=104’h’ = minx=0 maxx=7 adv=7
Metrics char 105=105’i’ = minx=0 maxx=6 adv=7
Metrics char 106=106’j’ = minx=0 maxx=5 adv=7
Metrics char 107=107’k’ = minx=0 maxx=7 adv=7
Metrics char 108=108’l’ = minx=0 maxx=6 adv=7
Metrics char 109=109’m’ = minx=-1 maxx=8 adv=7
Metrics char 110=110’n’ = minx=0 maxx=7 adv=7
Metrics char 111=111’o’ = minx=0 maxx=6 adv=7
Metrics char 112=112’p’ = minx=-1 maxx=6 adv=7
Metrics char 113=113’q’ = minx=0 maxx=7 adv=7
Metrics char 114=114’r’ = minx=0 maxx=6 adv=7
Metrics char 115=115’s’ = minx=0 maxx=6 adv=7
Metrics char 116=116’t’ = minx=0 maxx=6 adv=7
Metrics char 117=117’u’ = minx=0 maxx=7 adv=7
Metrics char 118=118’v’ = minx=0 maxx=7 adv=7
Metrics char 119=119’w’ = minx=-1 maxx=8 adv=7
Metrics char 120=120’x’ = minx=0 maxx=7 adv=7
Metrics char 121=121’y’ = minx=0 maxx=7 adv=7
Metrics char 122=122’z’ = minx=1 maxx=7 adv=7

Now, you see with this font, the character bounding boxes vary,
the mono-spacing is entirely a property of the advance metric.

So when SDL_TTF renders a character with a negative minx within
a string, it just renders left of the origin. However if it is left of the
physical first pixel it cannot do that, so it shifts the character right.

The correct way to do this is that TTF_Render … should write on a user
supplied surface. Alternately the user could specify the origin.
It really messy no matter what.

I can fix this by calculating the minx for the first character in the
string
and rendering the surface to the left of where it should be.

There’s probably a similar problem with maxx.


john skaller
skaller at users.sourceforge.net
http://felix-lang.org


SDL mailing list
SDL at lists.libsdl.org
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org

Yeah, it would be nice to be able to provide your own buffer. Barring that, you do need to use TTF_GlyphMetrics() to figure out where to place the resulting surface when blitting.

Yes, but this isn’t documented, in fact the actual
documentation is wrong. It says:

3.3.15 TTF_FontFaceIsFixedWidth

int TTF_FontFaceIsFixedWidth(const TTF_Font *font)

font
The loaded font to get the fixed width status of.
Test if the current font face of the loaded font is a fixed width font. Fixed width fonts are monospace, meaning every character that exists in the font is the same width, thus you can assume that a rendered string’s width is going to be the result of a simple calculation:
glyph_width * string_length

This is not correct. I THINK: If the minx value of the first char is negative its absolute value
is added to the width. I’m not sure what happens if the minx value is positive,
maybe need to subtract that as well.

I don’t know about the maxx value of the last char
but I suspect that is added too.

This also assumes no kerning and I have no idea about hinting.

Furthermore, the glyph_width is NOT the correct multiplier.
The correct multiplier is in fact the advance value.
The width is irrelevant and varies from char to char even in
a monospaced font.

In fact the name of the function is therefore also wrong.
So that’s THREE errors in the documentation :slight_smile:

I am still have trouble making this work,

I’m actually trying to display a line with a selection.
So I’m using TTF_RenderText_Solid, then TTF_RenderText_Shaded,
then TTF_RenderText_Solid. The Shaded bit is required because its
the only function that lets me change the background colour,
and I can’t get that right either: my screen is blue, my background
is supposed to be red but comes out pinkish, as if I specified something
translucent.

In the header file it says of Shaded:

The glyph is rendered without any padding or
centering in the X direction, and aligned normally in the Y direction.

I have no idea what that means but it isn’t stated for Solid.On 20/06/2013, at 10:40 PM, Jonathan Dearborn wrote:


john skaller
@john_skaller
http://felix-lang.org

This seems to work for me now, posting it in the hope it will
be useful to others. This is Felix code but it should be readable
enough to translate to another language.

in this code presel and postsel are integers marking
the position in the input line where the client "selection"
starts and ends. The code below highlights that text by
using a different text and background colour.

// xoffset, yoffset: client area for text in window
// charwidth: the advance amount (NOT the width)
// requires a monospaced font to work right
// lineskip: vertical separation of origins
// lno: text line number, startline: first line to display

    var x = 0;
    fun dstview() => SDL_Rect (
      xoffset+x+xadj,
      yoffset + (lno - startline) * lineskip,
      unused,unused)
    ;

    if presel > 0 do
      var text = line.[to presel];
      var xadj = min (0,minx (font, text.[0].ord));
      var viewport = #dstview;
      var text_rendered = TTF_RenderText_Solid(font,text,white);
      result = SDL_BlitSurface (text_rendered, nullRect, window_surface, &viewport); 
      SDL_FreeSurface text_rendered;
      x = charwidth * presel;
    done

    if presel < postsel do
      text = line.[presel to postsel];
      xadj = 0;
      viewport = #dstview;
      text_rendered = TTF_RenderText_Shaded(font,text,black,red);
      var srcrec = SDL_Rect (0,0,text.len.int * charwidth,lineskip);
      result = SDL_BlitSurface (text_rendered, &srcrec, window_surface, &viewport); 
      SDL_FreeSurface text_rendered;
      x += charwidth * (postsel - presel); 
    done
    
    if postsel < line.len.int do
      text = line.[postsel to];
      xadj = min(0,minx (font, text.[0].ord));
      viewport = #dstview;
      text_rendered = TTF_RenderText_Solid(font,text,white);
      result = SDL_BlitSurface (text_rendered, nullRect, window_surface, &viewport); 
      SDL_FreeSurface text_rendered;
    done

The thing to observe is that when rendering Solid, you have to adjust the
destination to ensure the character origin ends up in the right place.

When rendering Shaded, you do not. The result cannot be perfect because
rendering Shaded does not correctly render the first character, it throws out
any pixels to the left of the character origin. Rendering Solid, however,
keeps them. There’s probably an issue with the ending pixels too but
that happens not to matter at the moment in this application.–
john skaller
@john_skaller
http://felix-lang.org