How to make the picture rotate?(怎样让图片转动?)

I use SDL to create a music player interface, when the song playback, to show the state of play.
I want to achieve such an effect, what should I do by SDL? Thanks.
My email is : wheretogo0815@163.com

我用SDL来制作一个音乐播放器的界面,当歌曲播放时,要显示播放的状态。
我想实现这样的一个效果,请问该怎么做?谢谢。

Have an angle variable in your code, that you increase every frame, and then use that rotation angle when you render your texture with SDL_RenderCopyEx.

Click the button below to see code example.

SDL_Texture rotation example
#include <SDL.h>

//////////////////////////////////////////////////////////////////////////

#define WINDOW_WIDTH	(800)
#define WINDOW_HEIGHT	(600)

//////////////////////////////////////////////////////////////////////////

SDL_Texture* pTexture = nullptr;

float RotationAngle = 0.0f;

double DeltaTime = 0.0;
double OldTime	 = 0.0;
double NewTime	 = 0.0;

bool Running = true;

//////////////////////////////////////////////////////////////////////////

int main(int argc, char *argv[])
{
	// Create the SDL window, the SDL renderer, your texture etc
	Create();

	// Main loop
	while(Running)
	{
		// Handle events (with SDL_PollEvent() etc)
		HandleEvents();

		//////////////////////////////////////////////////////////////////////////

		// Calculate the deltatime
		NewTime   = SDL_GetTicks();
		DeltaTime = (NewTime - OldTime) / 1000.0;
		OldTime   = NewTime;

		//////////////////////////////////////////////////////////////////////////

		const float RotationSpeed = 30.0f;

		// Increase the rotation angle (based on deltatime so the speed is consistent between different computers and their speeds)
		RotationAngle += RotationSpeed * (float)DeltaTime;

		if(RotationAngle > 360.0f)
			RotationAngle = 0.0f;

		//////////////////////////////////////////////////////////////////////////

		SDL_RenderClear(pRenderer);

		int TextureWidth  = 0;
		int TextureHeight = 0;

		SDL_QueryTexture(pTexture, nullptr, nullptr, &TextureWidth, &TextureHeight);

		const SDL_Rect PositionQuad = {	(WINDOW_WIDTH  / 2) - (TextureWidth  / 2),
						(WINDOW_HEIGHT / 2) - (TextureHeight / 2),
						TextureWidth,
						TextureHeight};

		// Render your texture with the specified rotation angle, positioned at the middle of the window
		SDL_RenderCopyEx(pRenderer, pTexture, nullptr, &PositionQuad, RotationAngle, nullptr, SDL_FLIP_NONE);

		SDL_RenderPresent(pRenderer);
	}

	// Destroy everything
	Destroy();

	return 0;
}

Hope it helps. :slight_smile:

Thank you very much.The code works.
But I have a new demand,I just want the white part to rotate,the green part not to rotate.
Can you give me some ideas ? Thanks.

This sounds like an image editing question. You need to remove the green background and save it in a format that supports transparency, like PNG for example.

If you’re not sure what software to use for this, I can recommend GIMP. It is free open-source software and while it doesn’t quite compare to the more expensive commercial solutions, it covers many use cases that you will encounter.

If you’ve never used an image editing program before, here are the steps you need to take to edit your image.

Step by step editing example
  1. Load your image into GIMP. Menu File, option Open… (Ctrl + O).
  2. If you loaded the .gif file, it will import all the animation frames. You should see them in the Layers docking window. Press Ctrl + L if you don’t have that docking window yet.
  3. Delete all layers except the one called Background. We don’t need the other frames since you do the animation yourself. You can delete a layer by selecting it and then clicking the trash bin icon in the lower right corner.
  4. Change the image mode by opening the Image menu and then the Mode menu, then select RGB. The GIF format is an indexed format (It only supports a color palette with a maximum of 256 colors) and this will get in the way of the editing. We want all 2^32 possible combinations.
  5. Add an alpha channel to the Background layer by opening the Layer menu and then the Transparency menu, then click on Add Alpha Channel. GIMP usually doesn’t add an alpha channel to background layers if it doesn’t need one, but we want it here.

    We’re ready to edit the image now.
  6. Select the Select by Color tool and click on the green background of the image. The icon of the tool is a hand that points to one of three colored squares. Its purpose is to create a selection of a specific color which is perfect since we want to remove the green. You now should see the selection around the green background, but it’s not quite what we need yet.
  7. Grow the selection by 1 pixel by going into the Select menu and clicking on Grow…. In the new window that pops up, enter the number 1 and click OK. The selection now goes slightly over the edge of the optical disc.
  8. Feather the selection by 3 pixels by going into the Select menu and clicking on Feather…. In the new window that opens, enter the number 3 and click OK. This will smooth the edges of the selection and the pixels there will only get slightly affected by the editing we do.
  9. Clear the selection by pressing the Delete key or by going into the menu Edit and clicking on Clear. Since we feathered the selection, the edges are now slightly translucent, giving a better look than the hard edge it would have had without it. You should now see the checkered background that indicates that part of the image is transparent.
  10. The image is now ready, go ahead and save the image by going into the File menu and clicking on Export As…. A new dialog will show up, asking where you want to save it. Enter a new name with the .png extension. When you click Export, GIMP sees that you want to export a PNG and offers you some additional options. You can uncheck all the options if you want to save a few bytes, but it’s not necessary. Click again on Export and you will have an image with just the optical disc without the green background that you can load into a SDL_Texture and start rotating.

If the edge of the optical disc still looks too hard for you, repeat the steps again, but use slightly higher numbers for growing and feathering the selection.

Your answer is very detailed.Thanks.Forgive me for not describing clearly,My real intention is using SDL1.2 to make a music player’s UI like this:

1.I have many audio files,so I can get their album pictures.For example,their formats are jpg. One of them is below:

  1. The color of my music player’s main window is gray.
    3.Now I just want show a circle part of the album picture and the showed part rotates during playing.

How can I achieve this by SDL1.2 or SDL2 ?

Now I just achieve this,picture can rotates.

SDL2_gfx-1.0.3/test/testrotozoom.c

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:

  1. Load the cover image into a texture.
  2. 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.
  3. 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.
  4. Create a target texture with an alpha channel.
    Note that not every system supports target textures, but they should be very widely available.
  5. 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.
  6. Draw the mask texture to the target texture.
  7. 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.
  8. Draw the cover texture to the target texture.
    This operation completes the creation of the new texture.
  9. 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.
  10. 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. :slight_smile:

[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
1 Like

Thank you very much.I tried your code. Everything is OK now.