20_Display BMP pictures using SDL

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);
  1. 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:
  2. 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();

code link

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

Extended code link

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

Posted by sciwaysoft on Tue, 11 Oct 2022 05:31:58 +0530