The main content of the text is: use SDL Displaying a BMP picture is a preparation for the later "Display YUV Picture".
Why are BMP pictures displayed? instead of displaying JPG or PNG images?
- Because SDL has a built-in API for loading BMP, it is easier to use, and it is convenient for beginners to learn to use SDL
- If you want to easily load images in other formats such as JPG, PNG, etc., you can use a third-party library: SDL_image
png to bmp
Convert the previous png image to bmp image
First view some formats of png images through the ffprobe in.png command
Input #0, png_pipe, from 'in.png': Duration: N/A, bitrate: N/A Stream #0:0: Video: png, rgb24(pc), 512x512, 25 tbr, 25 tbn, 25 tbc
Then convert the png image to bmp through the ffmpeg command
ffmpeg -i in.png -s 512x512 -pix_fmt rgb24 in.bmp
macro definition
#include <SDL2/SDL.h> #include <QDebug> // If an error occurs, execute goto end #define END(judge, func) \ if (judge) { \ qDebug() << #func << "Error" << SDL_GetError(); \ goto end; \ }
SDL2 is a pure C speech library. We do not need to add extern "C" in the C++ code call, because SDL has made a judgment internally. If it is the C++ environment, it will automatically add extern "C" for us.
Variable definitions
// window SDL_Window *window = nullptr; // rendering context SDL_Renderer *renderer = nullptr; // pixel data SDL_Surface *surface = nullptr; // Textures (pixel data directly related to a specific driver) SDL_Texture *texture = nullptr;
Initialize the subsystem
// Initialize the Video subsystem END(SDL_Init(SDL_INIT_VIDEO), SDL_Init);
load BMP
#ifdef Q_OS_WIN #define FILENAME "../test/in.bmp" #else #define FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test/in.bmp" #endif // load BMP surface = SDL_LoadBMP(FILENAME); END(!surface, SDL_LoadBMP);
create window
// create window window = SDL_CreateWindow( // window title "SDL show BMP picture", // x-coordinate of the window (SDL_WINDOWPOS_UNDEFINED: not specified SDL_WINDOWPOS_CENTERED: middle) SDL_WINDOWPOS_UNDEFINED, // the y-coordinate of the window SDL_WINDOWPOS_UNDEFINED, // Window width (same as image width) surface->w, // Window height (same as picture height) surface->h, // Display window, SDL_WindowFlags enumeration value SDL_WINDOW_SHOWN ); END(!window, SDL_CreateWindow);
SDL_WindowFlags enumeration:
typedef enum { SDL_WINDOW_FULLSCREEN = 0x00000001, /**< full screen window */ SDL_WINDOW_OPENGL = 0x00000002, /**< Window is available in OpenGL context */ SDL_WINDOW_SHOWN = 0x00000004, /**< display window */ SDL_WINDOW_HIDDEN = 0x00000008, /**< hide window */ SDL_WINDOW_BORDERLESS = 0x00000010, /**< Don't show window decorations */ SDL_WINDOW_RESIZABLE = 0x00000020, /**< Window is resizable */ SDL_WINDOW_MINIMIZED = 0x00000040, /**< window minimized */ SDL_WINDOW_MAXIMIZED = 0x00000080, /**< window maximized */ SDL_WINDOW_INPUT_GRABBED = 0x00000100, /**< Window can capture keyboard input focus */ SDL_WINDOW_INPUT_FOCUS = 0x00000200, /**< window has input focus */ SDL_WINDOW_MOUSE_FOCUS = 0x00000400, /**< window has cursor */ SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ), SDL_WINDOW_FOREIGN = 0x00000800, /**< window not created by SDL */ SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000, /**< window should be created in high-DPI mode if supported. On macOS NSHighResolutionCapable must be set true in the application's Info.plist for this to have any effect. */ SDL_WINDOW_MOUSE_CAPTURE = 0x00004000, /**< window has mouse captured (unrelated to INPUT_GRABBED) */ SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000, /**< The window is always brought to the front */ SDL_WINDOW_SKIP_TASKBAR = 0x00010000, /**< window should not be added to the taskbar */ SDL_WINDOW_UTILITY = 0x00020000, /**< window should be treated as a utility window */ SDL_WINDOW_TOOLTIP = 0x00040000, /**< window should be treated as a tooltip */ SDL_WINDOW_POPUP_MENU = 0x00080000, /**< window should be treated as a popup menu */ SDL_WINDOW_VULKAN = 0x10000000, /**< window usable for Vulkan surface */ SDL_WINDOW_METAL = 0x20000000 /**< window usable for Metal view */ } SDL_WindowFlags;
We can also create a window from an existing local window, passing in a pointer to the local window. I created a QLabel, here we pass in the winId() of the QLabel:
Create a rendering context
// Create a rendering context (the default render target is window) renderer = SDL_CreateRenderer(window, // The index of the rendering device to initialize, set -1 to initialize the first device that supports flags -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!renderer) { // Description Failed to enable hardware acceleration renderer = SDL_CreateRenderer(window, -1, 0); } END(!renderer, SDL_CreateRenderer);
The window passed in when the renderer is created is the default render target. SDL documentation SDL_GetRenderTarget is also explained.
SDL_RendererFlags:
/** * \brief Flags used when creating a rendering context */ typedef enum { SDL_RENDERER_SOFTWARE = 0x00000001, /**< Use software acceleration */ SDL_RENDERER_ACCELERATED = 0x00000002, /**< Use hardware acceleration */ SDL_RENDERER_PRESENTVSYNC = 0x00000004, /**< Synchronized with monitor refresh rate */ SDL_RENDERER_TARGETTEXTURE = 0x00000008 /**< Renderer supports rendering to textures */ } SDL_RendererFlags;
Here we refer to the writing of the ffplay source code:
Create textures
// Create textures texture = SDL_CreateTextureFromSurface( renderer, surface); END(!texture, SDL_CreateTextureFromSurface);
render
// Set the drawing color (here a random color is set: yellow) END(SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE), SDL_SetRenderDrawColor); // Clear render target with DrawColor END(SDL_RenderClear(renderer), SDL_RenderClear); // Copy the texture to the render target (default is window), you can use SDL_SetRenderTarget() to modify the render target // srcrect source rectangle, dstrect destination rectangle, both pass nullptr to indicate that the entire texture is rendered to the entire target END(SDL_RenderCopy(renderer, texture, nullptr, nullptr), SDL_RenderCopy); // Update all previous content that needs to be rendered to the screen SDL_RenderPresent(renderer);
- srcrect: source rectangle, representing which part of the texture to be intercepted; dstrect: destination rectangle, representing which part of the Rendering Target the texture is rendered to; as shown below:
- Both srcrect and dstrect pass nullptr to render the entire texture to the entire render target:
delayed exit
// Exit with a 3 second delay SDL_Delay(3000);
What if I want the displayed bmp picture window to be displayed all the time?
while (!isInterruptionRequested()) { SDL_Event event; SDL_WaitEvent(&event); switch (event.type) { case SDL_QUIT: goto end; } }
release resources
end: // release resources SDL_FreeSurface(surface); SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit();
Extended usage of SDL
If we didn't copy the texture data to the render target, the window would be pitch black. We can set the render target background color:
// Set paint color (brush color) SDL_ALPHA_OPAQUE = 255 END(SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE),SDL_SetRenderDrawColor); // Set paint color (brush color) clear render target END(SDL_RenderClear(renderer),SDL_RenderClear);
You can also draw a rectangle in the window:
// draw a yellow rectangle SDL_SetRenderDrawColor(renderer,255,255,0,SDL_ALPHA_OPAQUE); rect = {0, 0, 50, 50}; // SDL_RenderDrawRect(renderer, &rect);//Hollow rectangle SDL_RenderFillRect(renderer, &rect);//solid rectangle
Render targets can also be modified. Sometimes we need to draw the same graphic on the window multiple times, then we can create a new texture, and then modify the rendering target to texture. After the current texture is drawn, modify the rendering target to window, and then copy the texture to the window:
// Create Texture SDL_Texture *ShowBmpThread::createTexture(SDL_Renderer *renderer){ // Create textures SDL_Texture *texture = SDL_CreateTexture(renderer, // rendering context SDL_PIXELFORMAT_RGB24, // SDL_PixelFormatEnum, reference documentation: https://wiki.libsdl.org/SDL_PixelFormatEnum SDL_TEXTUREACCESS_TARGET, // SDL_TextureAccess, here we want to use the texture as the rendering target, select: SDL_TEXTUREACCESS_TARGET, reference document: https://wiki.libsdl.org/SDL_TextureAccess 50, // the width of the texture 50); // high texture if (!texture) return nullptr; // Set texture as render target if (SDL_SetRenderTarget(renderer, texture)) return nullptr; // Set texture background color // if (SDL_SetRenderDrawColor(renderer, 0, 155, 0, SDL_ALPHA_OPAQUE)) return nullptr; // if (SDL_RenderClear(renderer)) return nullptr; // set drawing color if (SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE)) return nullptr; // draw a rectangle SDL_Rect rect = {0, 0, 50, 50}; if (SDL_RenderDrawRect(renderer, &rect)) return nullptr; // draw lines if (SDL_RenderDrawLine(renderer, 0, 0, 50, 50)) return nullptr; if (SDL_RenderDrawLine(renderer, 50, 0, 0, 50)) return nullptr; return texture; }
Listen for mouse click events and redraw the rectangle to the render target window:
while (!isInterruptionRequested()) { SDL_Event event; SDL_WaitEvent(&event); switch (event.type) { case SDL_QUIT: goto end; case SDL_MOUSEBUTTONUP: showClick(event, renderer, texture); break; } }
void ShowBmpThread::showClick(SDL_Event &event, SDL_Renderer *renderer, SDL_Texture *texture) { SDL_MouseButtonEvent btn = event.button; int w = 0; int h = 0; // Query texture width and height if (SDL_QueryTexture(texture, nullptr, nullptr, &w, &h)) return; int x = btn.x - (w >> 1); int y = btn.y - (h >> 1); SDL_Rect dstRect = {x, y, w, h}; // Clear① // if (SDL_RenderClear(renderer)) return; // Copy texture to render target if (SDL_RenderCopy(renderer, texture, nullptr, &dstRect)) return; // SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255); // SDL_RenderDrawRect(renderer, &dstRect); // Update the rendering operation to the screen SDL_RenderPresent(renderer); }
Open the comment if (SDL_RenderClear(renderer)) return; of the code at ① in the showClick method, you can realize the display of the new rectangle and clear the original rectangle
Note: The above code is run in the child thread in the window environment, if it is in the mac environment, it needs to be placed in the main thread