how to zoom in sdl2?

i want to zoom in sdl2 but i dont know how to do it properly.
can someone tell me how to do that?
thank you and i’m sr if i have any mistake because of my poor english

1 Like

If you’re using SDL_RenderCopy you can reduce the size of the srcrect and/or increase the size of the dstrect.

2 Likes

Is using some combination of SDL_RenderSetScale and SDL_RenderSetViewport appropriate for global zoom? @Peter87 your solution makes sense when rendering a texture, but how about for zooming on a bunch of drawing done directly with functions like SDL_RenderDrawLine?

I was imagining something like this as a way to zoom in by a factor of scale_factor on a square window:

    int scaled_window_dimension = WINDOW_DIMENSION * scale_factor;
	int cutoff = scaled_window_dimension - WINDOW_DIMENSION;
	SDL_Rect viewport = (SDL_Rect) {0 - cutoff / 2, 0 - cutoff / 2, scaled_window_dimension, scaled_window_dimension};
	SDL_RenderSetScale(rend, scale_factor, scale_factor); 
	SDL_RenderSetViewport(rend, &viewport);

The idea is to set the viewport to be a rect that is scale_factor times larger than the window, offset by the amount that ought to be cut off on each side of the window.

It kind of works, though it doesn’t quite zoom on the center of the screen as I expected. I thought the discrepancy was due to cumulative rounding error but tried with more rigorous rounding & casting and the problem persisted. Maybe there’s a different bug in my math, or something about this methodology that is not viable.

1 Like

If you use multiple textures then you would have to do it for each of them (and do the math appropriately).

SDL_Rect dstrect;
dstrect.x = x * scale_factor;
dstrect.y = y * scale_factor;
dstrect.w = width * scale_factor;
dstrect.h = height * scale_factor;
SDL_RenderCopy(renderer, texture, NULL, &dstrect);

(There is no need to call SDL_RenderCopy for textures that are outside the screen bounds.)

I have never really bothered with SDL_RenderDrawLine because there is no way to set the thickness and trying to fake it with SDL_RenderSetScale never worked satisfactory for me.

1 Like

A simpler solution, and the one I adopt, is to render your textures, lines, rectangles etc. to a separate target texture and then SDL_RenderCopy() that target texture to the screen. That means you can zoom simply by adjusting the source or destination rectangle, whilst your rendering can be as complicated as you like (in my case it may easily consist of thousands of separate elements).

To draw a line with a specified thickness you can call thickLineRGBA() in SDL2_gfx.

4 Likes

I’m sure this works great in many situations but it sounds like it would sometimes lead to a lot of unnecessary rendering. If you have a large game world it might be too slow to render everything each frame. And you might not want to just enlarge the pixels of your target texture, instead you might want to still get sharp lines without blur or visible pixels. When zoomed in you might even want to draw additional details or switch to more high-res graphics.

Using an intermediate texture is great for low-res games that you want to upscale. It can give you better consistency and avoid “rounding errors” that could otherwise cause graphical glitches with non-integer scaling factors (this can be especially noticeable with tile maps) but I consider this to be separate from “zooming”.

1 Like

One of the nice things about using a target texture is that you don’t have to render everything every frame! When you’re rendering directly to the screen (i.e. with NULL as the target texture) you have no choice: you must SDL_RenderClear() then re-render your entire scene every frame, because SDL_RenderPresent() will typically destroy the contents.

But if you render to a target texture instead, it will hold its contents through the SDL_RenderPresent() and - if it suits your application to do so - you can safely leave some of your scene unchanged from frame to frame and only re-render what changes.

If you’re going to do scaling of any kind it helps to have a proper understanding of Sampling Theory and filtering, which unfortunately the great majority of people writing games and other graphics applications don’t. :face_with_diagonal_mouth:

Many years ago I wrote a little dissertation on scaling and how to choose appropriate interpolation filters. This concentrates primarily on reducing the size rather than increasing it, but exactly the same mathematics applies.

To me, ‘zooming’ usually implies scaling, not re-rendering everything from scratch to suit a different-sized canvas. When the latter is justified, you may get a ‘sharper’ result but you still have to worry about aliasing, when drawing diagonal lines say.

2 Likes

Interesting.

I was thinking about “zooming” from the user’s perspective. If I look closer or use a magnifying glass I expect to see more details (if there are any).

1 Like

It rather depends on why you’re zooming! Mostly, the reason I want to do it is to run an app ‘fullscreen’ rather than ‘windowed’. I don’t want (or expect) to see more detail, I just want it to be bigger so I can sit further away from the monitor, and not be distracted by all the other stuff on the screen.

Typically, my apps that use OpenGL or shader code directly (e.g. for full 3D rendering) do display more detail when zoomed, because that comes for free (assuming the source textures are big enough). But my 2D apps which are rendered purely by core SDL2 and SDL2_gfx don’t, because that would be a lot of extra work.

1 Like

OK, we were clearly thinking about different things. I was thinking more about the kind of zooming that a player might be able to do in a strategy game or the zooming that would happen automatically when you stop running in some side-scrollers.

1 Like

I guess we need to know what the OP meant! Your original reply to him did propose using SDL_RenderCopy() with a reduced srcrect or an increased dstrect, neither of which increases the amount of detail, so it’s you who seems to have changed direction. :wink:

1 Like

The problem we are having is that there are multiple ways of handling “zoom”, some of them can be done in a couple of lines of code and be good enough, while others can be much more complexed. It all depends on the requirements of the program.

We don’t have enough information.

  • What is the goal or situation? (Paint program, racing game, turn-based rpg, graphing calculator, or something else)

  • What type of inputs are your working with?
    (Do you need to interact with the mouse while zoomed? There is a simple calculation for that, but even that might not be necessary in some programs. In some situations, that calculation can even be left to SDL to handle for us.)

Please help by showing us some code you’ve done so we know where to start, and give us a description of problems that you are dealing with and things you have tried.

1 Like

The increases the amount of detail was an optional part. I guess I got hanged up on what you said about it being “a simpler solution” which made me assume things about what you meant that maybe you didn’t mean. Things that I, at least in my head, had held open as things you might want to do (e.g. I assumed you wouldn’t want to draw “everything” even though I didn’t say so explicitly). But I think I have said enough. There is no disagreement, no hard feelings, only different perspectives (at least that’s how I see it now).

1 Like

Here’s how I might do zoom in a simple line-drawing program. It’s similar to what’s been recommended (Using a target texture as a canvas), but I wanted to control what point the zoom focused on. (I wanted to be able to zoom in and out really far and end up on the same pixel when zooming back in again.)
Controls:

  • plus/equals = zoom in
  • minus = zoom out
  • period = reset canvas position to origin.
  • space = toggle pan mode to move the canvas with the mouse.
    Note that the zoom is focused on the mouse position.
    fastPlay

While I wrote the code for SDL3, function names are just slightly different if you wanted to convert it back to SDL2. It is important that you use SDL_FRect and SDL_FPoint related functions.

// This should give us an image that we can zoom into as needed while still lets us draw with the mouse.
#include <SDL3/SDL.h>

int main()
{
	uint32_t initFlags = SDL_INIT_VIDEO | SDL_INIT_EVENTS;
	SDL_Init(initFlags);
	SDL_Window * win = SDL_CreateWindow("DrawLines", 1000, 1000, SDL_WINDOW_RESIZABLE);
	SDL_Renderer * screen = SDL_CreateRenderer(win, 0, SDL_RENDERER_PRESENTVSYNC);
	SDL_Texture * canvas = SDL_CreateTexture(screen, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 1920, 1080);
	SDL_FRect scale = {0, 0, 1920, 1080};
	SDL_FRect baseSize = {0, 0, 1920, 1080};
	SDL_FPoint mousePos = {0, 0};
	SDL_FPoint downPos = {0, 0};

	SDL_SetRenderTarget(screen, canvas);
	SDL_SetRenderDrawColor(screen, 255, 255, 255, 255);
	SDL_RenderClear(screen);
	SDL_SetRenderTarget(screen, NULL);
	SDL_SetRenderDrawColor(screen, 10, 10, 10, 255);
	SDL_SetTextureScaleMode(canvas, SDL_SCALEMODE_NEAREST);

	// machine states
	bool mouseDown = false;
	bool panMode = false;

	bool run = true;
	while(run)
	{
		while(SDL_WaitEvent(NULL) < 0)
		{

		}
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_EVENT_MOUSE_MOTION:
					mousePos = {ev.motion.x , ev.motion.y};
					if(panMode)
					{
						scale.x += ev.motion.xrel;
						scale.y += ev.motion.yrel;
					}
					break;
				case SDL_EVENT_MOUSE_BUTTON_DOWN:
					downPos = {ev.button.x, ev.button.y};
					mouseDown = true;
					break;
				case SDL_EVENT_MOUSE_BUTTON_UP:
					if(mouseDown)
					{
						// Scaling the line then stroke to the canvas
						SDL_SetRenderTarget(screen, canvas);
						float scalarW = scale.w / baseSize.w;
						float scalarH = scale.h / baseSize.h;
						SDL_RenderLine(screen, (downPos.x - scale.x) / scalarW, floor((downPos.y - scale.y) / scalarH), (mousePos.x - scale.x) / scalarW, floor((mousePos.y - scale.y) / scalarH));
						SDL_SetRenderTarget(screen, NULL);
					}
					mouseDown = false;
					break;
				case SDL_EVENT_KEY_DOWN:
					switch(ev.key.keysym.sym)
					{
						case SDLK_MINUS:
							scale.w *= 0.9f;
							scale.h *= 0.9f;
							scale.x *= 0.9f;
							scale.y *= 0.9f;
							scale.x += mousePos.x * 0.1f;
							scale.y += mousePos.y * 0.1f;
							break;
						case SDLK_EQUALS:
						{
							scale.w *= 1.01f;
							scale.h *= 1.01f;
							scale.x *= 1.01f;
							scale.y *= 1.01f;
							scale.x -= mousePos.x * 0.01f;
							scale.y -= mousePos.y * 0.01f;
						}
							break;
						case SDLK_SPACE:
							panMode = !panMode;
							if(panMode)
							{
								SDL_SetRelativeMouseMode(SDL_TRUE);
							}
							else
							{
								SDL_SetRelativeMouseMode(SDL_FALSE);
							}
							break;
						case SDLK_PERIOD:
							scale.x = 0;
							scale.y = 0;
							break;
					}
					break;
				case SDL_EVENT_KEY_UP:
					break;
				case SDL_EVENT_QUIT:
					run = false;
					break;
			}
		}
		SDL_RenderClear(screen);
		SDL_RenderTexture(screen, canvas, NULL, &scale);
		if(mouseDown)
		{
			// this preview line is relative to the window, not to the canvas, 
			// no scaling required. (see mouse_button_up event above for scaling)
			SDL_RenderLine(screen, downPos.x, downPos.y, mousePos.x, mousePos.y);
		}
		SDL_RenderPresent(screen);
	}
	SDL_DestroyTexture(canvas);
	SDL_DestroyWindow(win);
	SDL_Quit();
}

I like this strategy because it is low complexity and high payoff. Setting the scaling mode to nearest neighbor makes pixel art easier when highly zoomed in (the above gif is blurry because it had linear scaling mode instead).

1 Like

i have a exercise in university of making a game and i decided to make a turned-based game.
i want to zoom when we scroll the mouse wheel.
here is the function that render the texture in my project

void LTexture::render(float x, float y, SDL_Renderer* screen, SDL_Rect* clip, float scale, double angle, SDL_FPoint* center, SDL_RendererFlip flip)
{
	SDL_FRect renderQuad = { x * scale, y * scale, mWidth * scale, mHeight * scale };
	if (clip != NULL)
	{
		renderQuad.w = clip->w * scale;
		renderQuad.h = clip->h * scale;
	}

	SDL_RenderCopyExF(screen, mTexture, clip, &renderQuad, angle, center, flip);
}

float scale is is the number I want to zoom by that many times
i found the result is as same as using SDL_SetRendererScale and both of these way have a problem that the result has gaps between tiles like this(zoom in 2,5 times):

Try setting this line for each of your textures.

	SDL_SetTextureScaleMode(mTexture, SDL_SCALEMODE_NEAREST);

(it doesn’t have to be called each frame, just when an image is loaded)

… Or you could use this to set the default scale type: SDL_HINT_RENDER_SCALE_QUALITY


[Edit]
Actually, looking at the code, perhaps the problem is that the “clip” variable is non-float SDL_Rect,

It might be a rounding error?
Try casting to float before multiplication with scale
renderQuad.w = ((float) clip->w) * scale;

1 Like

Rounding errors and “bleeding”. :unamused:

A possible solution is to render everything onto an intermediate texture first, either without any scaling or with an integer scaling factor (this gets rid of the rounding errors) using SDL_ScaleModeNearest (this gets rid of the bleeding). After that you can freely render the intermediate texture onto the screen using whatever scaling factor you want and whatever scale mode you want without running into these graphic issues. Not sure how to best handle this in a good way with a dynamic scaling factor that’s constantly changing but I think this (or something very similar) was what rtrussell talked about so maybe he can tell you more.

Another possible solution is to make sure that the graphics for each tile has at least one row of pixels around it, where each of those pixels has the same colour as the closest pixel inside the actual tile. This should make so that you don’t notice the effect of the rounding errors/bleeding. Note that the transition between the tiles, if they have different colours, will look sharper than between other pixels withing the tiles (assuming you use SDL_ScaleModeLinear) but this is a different “problem” (if it really is a problem) that was already in your original program (I only mention this because this isn’t the case with the other solution that I proposed).

1 Like

thanks everyone! I have found my problem and fixed it.
i just need to add this function in where the texture is loaded from file

SDL_SetTextureScaleMode(mTexture, SDL_ScaleModeNearest);