SDL2 Render Set Clip Circle?

Hi, i am using C++ with SDL2.
i am using C++ with SDL2 engine. In SDL2 there’s this function:

int SDL_RenderSetClipRect(SDL_Renderer* Renderer, SDL_Rect* clip);

If i call this function, copied textures will be displayed in this clip rect and no outside. My question is… Is it possible to set a circle as a clip? For example… Material design: There are some buttons with the shape of a circle and when you click on them, it appears a small circle from the cursor and this becomes bigger. This small circle is not displayed outside the button but only inside. And so, i want to do this. I want to render the clip of a texture with the shape of a circle and not a rect. Something like… SDL_RenderSetClipCircle? I hope you get what i mean. I am not english.

I think there’s not a function like this. So i am asking how i can implement it or link me something.

Thanks.

The SDL render API only provides rectangular clipping. More complex clipping may be achieved with target textures and masks, but that can be slow and annoying to set up.

I discussed masking with target textures over here, but that post got a bit mangled. Here’s the code again. Perhaps it’s easier with the new custom blend modes (if they’re supported on your target system).

covermask.c
#include "SDL.h"

#define MASK_WIDTH 512

#define TARGET_TEXTURE_WIDTH 512
#define TARGET_TEXTURE_HEIGHT 512

static struct {
	SDL_Window * handle;
	int width, height;
	int texture_cover_width, texture_cover_height;
	SDL_Renderer * renderer;
	SDL_Texture * texture_cover;
	SDL_Texture * texture_mask;
	SDL_Texture * texture_target;
} Window;

static void draw_to_target_texture()
{
	SDL_Rect src_rect;
	int cw = Window.texture_cover_width;
	int ch = Window.texture_cover_height;

	/* Direct the draw commands to the target texture. */
	SDL_SetRenderTarget(Window.renderer, Window.texture_target);

	/* Copy the mask to the target texture. */
	SDL_RenderCopy(Window.renderer, Window.texture_mask, NULL, NULL);

	/* Center the source rectangle on the cover, preserving the aspect ratio. */
	if (cw > ch) {
		src_rect.x = cw / 2 - ch / 2;
		src_rect.y = 0;
		src_rect.w = ch;
		src_rect.h = ch;
	} else {
		src_rect.x = 0;
		src_rect.y = ch / 2 - cw / 2;
		src_rect.w = cw;
		src_rect.h = cw;
	}

	/* Copy the cover to the target texture by modulating the color values. */
	SDL_SetTextureBlendMode(Window.texture_cover, SDL_BLENDMODE_MOD);
	SDL_RenderCopy(Window.renderer, Window.texture_cover, &src_rect, NULL);

	/* Reset the blend mode. We use this texture again later on.*/
	SDL_SetTextureBlendMode(Window.texture_cover, SDL_BLENDMODE_BLEND);

	/* Resetting to the default render target. */
	SDL_SetRenderTarget(Window.renderer, NULL);
}

static int run()
{
	int x, y;
	int box_width, box_border;
	int now;
	int done = 0;
	char title[256] = {0};
	SDL_RendererInfo info;
	SDL_Surface * tmp_surface;
	SDL_Rect mask_rect, cover_rect, result_rect;
	double rot_angle = 0;
	int renderer_has_target_texture_support = 0;

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
		SDL_Log("Failed to initialize SDL: %s", SDL_GetError());
		return 1;
	}

	Window.handle = SDL_CreateWindow("Loading...", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 500, SDL_WINDOW_RESIZABLE);
	if (Window.handle == NULL) {
		SDL_Log("Failed to create window: %s", SDL_GetError());
		return 1;
	}
	Window.renderer = SDL_CreateRenderer(Window.handle, -1, 0);
	if (Window.renderer == NULL) {
		SDL_Log("Failed to create renderer: %s", SDL_GetError());
		return 1;
	}

	SDL_GetWindowSize(Window.handle, &Window.width, &Window.height);

	/* Checking if this renderer supports target textures */
	SDL_GetRendererInfo(Window.renderer, &info);
	renderer_has_target_texture_support = info.flags & SDL_RENDERER_TARGETTEXTURE;

	SDL_Log("Renderer %s started.", info.name);
	SDL_strlcat(title, info.name, sizeof(title));
	if (!renderer_has_target_texture_support) {
		SDL_Log(" Renderer has no target texture support!");
		return 1;
	}
	SDL_strlcat(title, ": Masking With Target Textures", sizeof(title));
	SDL_SetWindowTitle(Window.handle, title);

	/* Image loading part. */
	tmp_surface = SDL_LoadBMP("cover.bmp");
	if (tmp_surface == NULL) {
		SDL_Log("Could not load cover.bmp");
		return 1;
	} else {
		Window.texture_cover_width = tmp_surface->w;
		Window.texture_cover_height = tmp_surface->h;
		Window.texture_cover = SDL_CreateTextureFromSurface(Window.renderer, tmp_surface);
		SDL_FreeSurface(tmp_surface);
		if (Window.texture_cover == NULL) {
			SDL_Log("Failed to create cover texture: %s", SDL_GetError());
			return 1;
		}
	}

	/* Mask creation. We create a surface, write the pixel data manually and then
	   load it into a texture. */
	tmp_surface = SDL_CreateRGBSurface(0, MASK_WIDTH, MASK_WIDTH, 32, 0xff, 0xff00, 0xff0000, 0xff000000);
	if (tmp_surface == NULL) {
		SDL_Log("Failed to create mask surface: %s", SDL_GetError());
		return 1;
	}
	if (SDL_MUSTLOCK(tmp_surface)) {
		SDL_LockSurface(tmp_surface);
	}

	/* This is a rather simple algorithm. It just goes over all pixels and assigns
	   a value depending on how far it is from the center. This isn't the fastest
	   way of doing this. */
	for (y = 0; y < MASK_WIDTH; y++) {
		for (x = 0; x < MASK_WIDTH; x++) {
			Uint8 * p = (Uint8 *)tmp_surface->pixels;
			size_t offset = x * 4 + y * tmp_surface->pitch;
			double xc = x - MASK_WIDTH / 2 + 0.5;
			double yc = y - MASK_WIDTH / 2 + 0.5;
			double r = SDL_sqrt(xc*xc + yc*yc);
			if (r >= MASK_WIDTH / 2) {
				/* This pixel is outside the circle. Assign transparent black. */
				p[offset] = 0;
				p[offset + 1] = 0;
				p[offset + 2] = 0;
				p[offset + 3] = 0;
			} else if (r < MASK_WIDTH / 2 - 1) {
				/* This pixel is inside the circle. Assign opaque white. */
				p[offset] = 255;
				p[offset + 1] = 255;
				p[offset + 2] = 255;
				p[offset + 3] = 255;
			} else {
				/* This pixel is on the edge of the circle. Select the value by
				   checking how far form the edge it is. This gives the circle
				   a smoother appearance. */
				int edgevalue = (int)((1 - (r - SDL_floor(r))) * 255);
				p[offset] = edgevalue;
				p[offset + 1] = edgevalue;
				p[offset + 2] = edgevalue;
				p[offset + 3] = edgevalue;
			}
		}
	}

	if (SDL_MUSTLOCK(tmp_surface)) {
		SDL_UnlockSurface(tmp_surface);
	}

	Window.texture_mask = SDL_CreateTextureFromSurface(Window.renderer, tmp_surface);
	SDL_FreeSurface(tmp_surface);
	if (Window.texture_mask == NULL) {
		SDL_Log("Failed to create mask texture: %s", SDL_GetError());
		return 1;
	}
	/* The blend mode gets set to NONE because we require an exact copy of the pixels
	   for the target texture. */
	SDL_SetTextureBlendMode(Window.texture_mask, SDL_BLENDMODE_NONE);

	/* Creation of the target texture. */
	Window.texture_target = SDL_CreateTexture(Window.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, TARGET_TEXTURE_WIDTH, TARGET_TEXTURE_HEIGHT);
	if (Window.texture_target == NULL) {
		SDL_Log("Failed to create target texture: %s", SDL_GetError());
		return 1;
	}
	/* Blend mode defaults to NONE, but we want it to blend it with the background. */
	SDL_SetTextureBlendMode(Window.texture_target, SDL_BLENDMODE_BLEND);

	/* All is set up. Creating the final result. */
	draw_to_target_texture();

	while (!done) {
		SDL_Event e;
		while (SDL_PollEvent(&e)) {
			if (e.type == SDL_QUIT) {
				done = 1;
			} else if (e.type == SDL_KEYUP) {
				Uint32 sym = e.key.keysym.sym;
				if (sym == SDLK_ESCAPE) {
					done = 1;
				} else if (sym == SDLK_f) {
					if (SDL_GetWindowFlags(Window.handle) & SDL_WINDOW_FULLSCREEN) {
						SDL_SetWindowFullscreen(Window.handle, SDL_FALSE);
					} else {
						SDL_SetWindowFullscreen(Window.handle, SDL_WINDOW_FULLSCREEN_DESKTOP);
					}
				}
			} else if (e.type == SDL_WINDOWEVENT) {
				if (e.window.event == SDL_WINDOWEVENT_RESIZED || e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
					SDL_GetWindowSize(Window.handle, &Window.width, &Window.height);

					/* Some backends *cough*direct3d*cough* reset when the window size changes.
					   Target textures are not saved and have to be redrawn. */
					draw_to_target_texture();
				}
			}
		}

		now = SDL_GetTicks();
		rot_angle += SDL_sin(now * 0.001 - 1.55) + 1;

		/* Calculating the position for the three textures drawn in this example. */
		if (Window.width < Window.height * 3) {
			box_width = Window.width / 3;
		} else {
			box_width = Window.height;
		}
		box_border = box_width / 10;
		box_width -= box_border * 2;

		mask_rect.x = box_border;
		mask_rect.y = box_border;
		mask_rect.w = box_width;
		mask_rect.h = box_width;

		cover_rect.x = box_border * 3 + box_width;
		cover_rect.y = box_border;
		/* Preserving aspect ratio of the cover and centering it. */
		if (Window.texture_cover_width > Window.texture_cover_height) {
			cover_rect.w = box_width;
			cover_rect.h = (int)(box_width * ((double)Window.texture_cover_height / Window.texture_cover_width));
			cover_rect.y += (cover_rect.w - cover_rect.h) / 2;
		} else {
			cover_rect.h = box_width;
			cover_rect.w = (int)(box_width * ((double)Window.texture_cover_width / Window.texture_cover_height));
			cover_rect.x += (cover_rect.h - cover_rect.w) / 2;
		}

		result_rect.x = box_border * 5 + box_width * 2;
		result_rect.y = box_border;
		result_rect.w = box_width;
		result_rect.h = box_width;

		/* Drawing everything to the screen. */
		SDL_SetRenderDrawColor(Window.renderer, 128, 128, 128, 255);
		SDL_RenderClear(Window.renderer);

		SDL_RenderCopy(Window.renderer, Window.texture_mask, NULL, &mask_rect);
		SDL_RenderCopy(Window.renderer, Window.texture_cover, NULL, &cover_rect);
		SDL_RenderCopyEx(Window.renderer, Window.texture_target, NULL, &result_rect, rot_angle, NULL, SDL_FLIP_NONE);

		/* Drawing the * and = symbols on the screen. */
		SDL_SetRenderDrawColor(Window.renderer, 255, 255, 255, 255);
		SDL_RenderDrawLine(Window.renderer,
			mask_rect.x + mask_rect.w + box_border / 2, box_border + box_width / 2,
			cover_rect.x - box_border / 2, box_border + box_width / 2);
		SDL_RenderDrawLine(Window.renderer,
			mask_rect.x + mask_rect.w + box_border / 2, box_border + box_width / 2 - box_border / 2,
			cover_rect.x - box_border / 2, box_border + box_width / 2 + box_border / 2);
		SDL_RenderDrawLine(Window.renderer,
			mask_rect.x + mask_rect.w + box_border / 2, box_border + box_width / 2 + box_border / 2,
			cover_rect.x - box_border / 2, box_border + box_width / 2 - box_border / 2);
		SDL_RenderDrawLine(Window.renderer,
			cover_rect.x + cover_rect.w + box_border / 2, box_border + box_width / 2 - box_border / 4,
			result_rect.x - box_border / 2, box_border + box_width / 2 - box_border / 4);
		SDL_RenderDrawLine(Window.renderer,
			cover_rect.x + cover_rect.w + box_border / 2, box_border + box_width / 2 + box_border / 4,
			result_rect.x - box_border / 2, box_border + box_width / 2 + box_border / 4);

		SDL_RenderPresent(Window.renderer);

		SDL_Delay(3);
	}

	return 0;
}

int main(int argc, char * argv[])
{
	int result = run();

	if (Window.texture_mask)
		SDL_DestroyTexture(Window.texture_mask);
	if (Window.texture_mask)
		SDL_DestroyTexture(Window.texture_cover);
	if (Window.texture_mask)
		SDL_DestroyTexture(Window.texture_target);
	if (Window.texture_mask)
		SDL_DestroyRenderer(Window.renderer);
	if (Window.texture_mask)
		SDL_DestroyWindow(Window.handle);
	if (SDL_WasInit(SDL_INIT_EVERYTHING))
		SDL_Quit();

	return result;
}
1 Like