Oh, I see now what you mean. I can only speak for SDL 2 because I haven’t used SDL 1.2 in a few years.
The SDL 2 renderer API doesn’t have a direct way of doing this, but there should be a few multi-step ways to achieve the desired result. I can show you one with target textures:
The main idea is that we create a new texture that will contain the circle cutout of the cover texture. Target textures allow us to manipulate texture data on the graphics card which is very efficient. With the help of blend modes and a mask we can simply make the parts of the cover we don’t want transparent. SDL 2 has only 4 blend modes and it’s not always easy to find a solution with this limit. However, the MOD blend mode is exactly what we need for this problem. The steps could look something like this:
- Load the cover image into a texture.
-
Create a mask texture.
The pixel values of the mask will determine how the color values from the cover texture will be copied to the new texture. Opaque white means copy the value exactly, transparent black means discard the color and leave the pixel transparent, and everything between makes it translucent. The data for this mask could come from another image or you can create it during runtime. In the code example below, I use an SDL_Surface
and write the pixel data directly to it, then load it into a texture.
-
Set the blend mode of the mask texture to
SDL_BLENDMODE_NONE
.
The way we have to do it here requires a direct copy of the mask data into the target texture. The NONE blend mode gives us exactly that.
-
Create a target texture with an alpha channel.
Note that not every system supports target textures, but they should be very widely available.
-
Set the render target to the target texture.
The way target textures work is that you redirect the drawing calls to the texture instead of the screen. See SDL_SetRenderTarget
.
- Draw the mask texture to the target texture.
-
Set the blend mode of the cover texture to
SDL_BLENDMODE_MOD
.
The MOD blend mode will multiply the colors from the target and the cover texture with each other, giving us the masking effect we desire. Note that this blend mode doesn’t touch the alpha value. I didn’t look into how to copy those from the cover texture, but I think you don’t need them here.
-
Draw the cover texture to the target texture.
This operation completes the creation of the new texture.
-
Reset the blend mode of the cover texture to
SDL_BLENDMODE_BLEND
.
Don’t forget to change the settings back or you’ll ask yourself why the texture doesn’t draw properly when you later draw it somewhere else.
-
Select the default render target.
Don’t forget to reset to the default render target or the result will never show up on the screen.
The target texture now contains the circle with the cover art. You are free to draw it like any other texture.
Here’s the whole thing in C. Note that there’s no error checking here. I strongly recommend you add it yourself.
[details=circlemask.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);
}
int main(int argc, char * argv[])
{
int i, 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;
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
Window.handle = SDL_CreateWindow("Loading...",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 500, SDL_WINDOW_RESIZABLE);
Window.renderer = SDL_CreateRenderer(Window.handle, -1, 0);
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);
}
/* 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 (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);
/* 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);
SDL_FreeSurface(tmp_surface);
/* Creation of the target texture. */
Window.texture_target = SDL_CreateTexture(Window.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, TARGET_TEXTURE_WIDTH, TARGET_TEXTURE_HEIGHT);
/* 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);
}
SDL_DestroyTexture(Window.texture_mask);
SDL_DestroyTexture(Window.texture_cover);
SDL_DestroyTexture(Window.texture_target);
SDL_DestroyRenderer(Window.renderer);
SDL_DestroyWindow(Window.handle);
SDL_Quit();
return 0;
}
```[/details]
It should look like this:
https://discourse.libsdl.org/uploads/default/original/2X/9/9afe6c4a618f31e795ce264112969180dc3e2b78.mp4