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.
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;
}