美文网首页
20_使用SDL显示BMP图片

20_使用SDL显示BMP图片

作者: 咸鱼Jay | 来源:发表于2022-11-08 17:12 被阅读0次

    文本的主要内容是:使用SDL显示一张BMP图片,算是为后面的《显示YUV图片》做准备。

    为什么是显示BMP图片?而不是显示JPG或PNG图片?

    • 因为SDL内置了加载BMP的API,使用起来会更加简单,便于初学者学习使用SDL
    • 如果想要轻松加载JPG、PNG等其他格式的图片,可以使用第三方库:SDL_image

    png转bmp

    将之前的png图片转成bmp图片
    先通过ffprobe in.png命令查看png图片的一些格式

    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
    

    然后通过ffmpeg命令将png图片转成bmp

    ffmpeg -i in.png -s 512x512 -pix_fmt rgb24 in.bmp
    

    宏定义

    #include <SDL2/SDL.h>
    #include <QDebug>
    
    // 出错了就执行goto end
    #define END(judge, func) \
        if (judge) { \
            qDebug() << #func << "Error" << SDL_GetError(); \
            goto end; \
        }
    

    SDL2 是一个纯 C 语音的库,我们在 C++ 代码中调用不需要加extern "C",是因为 SDL 内部做了判断,如果是 C++ 环境自动帮我们添加 extern "C"

    变量定义

    // 窗口
    SDL_Window *window = nullptr;
    // 渲染上下文
    SDL_Renderer *renderer = nullptr;
    // 像素数据
    SDL_Surface *surface = nullptr;
    // 纹理(直接跟特定驱动程序相关的像素数据)
    SDL_Texture *texture = nullptr;
    

    初始化子系统

    // 初始化Video子系统
    END(SDL_Init(SDL_INIT_VIDEO), SDL_Init);
    

    加载BMP

    #ifdef Q_OS_WIN
        #define FILENAME "../test/in.bmp"
    #else
        #define FILENAME "/Users/zuojie/QtProjects/audio-video-dev/test/in.bmp"
    #endif
    
    // 加载BMP
    surface = SDL_LoadBMP(FILENAME);
    END(!surface, SDL_LoadBMP);
    

    创建窗口

    // 创建窗口
    window = SDL_CreateWindow(
                 // 窗口标题
                 "SDL显示BMP图片",
                 // 窗口的 x 坐标(SDL_WINDOWPOS_UNDEFINED:不指定 SDL_WINDOWPOS_CENTERED:中间)
                 SDL_WINDOWPOS_UNDEFINED,
                 //  窗口的 y 坐标
                 SDL_WINDOWPOS_UNDEFINED,
                 // 窗口宽度(跟图片宽度一样)
                 surface->w,
                 // 窗口高度(跟图片高度一样)
                 surface->h,
                 // 显示窗口,SDL_WindowFlags枚举值
                 SDL_WINDOW_SHOWN
             );
    END(!window, SDL_CreateWindow);
    

    SDL_WindowFlags 枚举:

    typedef enum
    {
        SDL_WINDOW_FULLSCREEN = 0x00000001,         /**< 全屏窗口 */
        SDL_WINDOW_OPENGL = 0x00000002,             /**< 窗口可用于 OpenGL 上下文 */
        SDL_WINDOW_SHOWN = 0x00000004,              /**< 显示窗口 */
        SDL_WINDOW_HIDDEN = 0x00000008,             /**< 隐藏窗口 */
        SDL_WINDOW_BORDERLESS = 0x00000010,         /**< 不显示窗口装饰 */
        SDL_WINDOW_RESIZABLE = 0x00000020,          /**< 窗口可调整大小 */
        SDL_WINDOW_MINIMIZED = 0x00000040,          /**< 窗口最小化 */
        SDL_WINDOW_MAXIMIZED = 0x00000080,          /**< 窗口最大化 */
        SDL_WINDOW_INPUT_GRABBED = 0x00000100,      /**< 窗口可捕获键盘输入焦点 */
        SDL_WINDOW_INPUT_FOCUS = 0x00000200,        /**< 窗口拥有输入焦点 */
        SDL_WINDOW_MOUSE_FOCUS = 0x00000400,        /**< 窗口拥有光标 */
        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,      /**< 窗口永远置于最前 */
        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;
    

    我们也可以从一个已经存在的本地窗口创建 window,传入的参数是指向本地窗口的指针。我创建了一个 QLabel,此处我们传入 QLabel 的 winId() 就可以:

    创建渲染上下文

    // 创建渲染上下文(默认的渲染目标是window)
    renderer = SDL_CreateRenderer(window, 
                                  // 要初始化的渲染设备的索引,设置 -1 则初始化第一个支持 flags 的设备
                                  -1,
                                  SDL_RENDERER_ACCELERATED |
                                  SDL_RENDERER_PRESENTVSYNC);
    if (!renderer) { // 说明开启硬件加速失败
        renderer = SDL_CreateRenderer(window, -1, 0);
    }
    END(!renderer, SDL_CreateRenderer);
    

    renderer 创建时传入的 window 是默认的渲染目标。SDL 文档SDL_GetRenderTarget 中也有说明。

    SDL_RendererFlags:

    /**
    *  \brief Flags used when creating a rendering context
    */
    typedef enum
    {
        SDL_RENDERER_SOFTWARE = 0x00000001,         /**< 使用软件加速 */
        SDL_RENDERER_ACCELERATED = 0x00000002,      /**< 使用硬件加速 */
        SDL_RENDERER_PRESENTVSYNC = 0x00000004,     /**< 和显示器刷新率同步 */
        SDL_RENDERER_TARGETTEXTURE = 0x00000008     /**< 渲染器支持渲染到纹理 */
    } SDL_RendererFlags;
    

    这里我们是参考ffplay源码的写法:


    ffplay 源码

    创建纹理

    // 创建纹理
    texture = SDL_CreateTextureFromSurface(
                  renderer,
                  surface);
    END(!texture, SDL_CreateTextureFromSurface);
    

    渲染

    // 设置绘制颜色(这里随便设置了一个颜色:黄色)
    END(SDL_SetRenderDrawColor(renderer,
                                 255, 255, 0,
                                 SDL_ALPHA_OPAQUE),
          SDL_SetRenderDrawColor);
    
    // 用DrawColor清除渲染目标
    END(SDL_RenderClear(renderer),
          SDL_RenderClear);
    
    // 复制纹理到渲染目标上(默认是window)可以使用SDL_SetRenderTarget()修改渲染目标
    // srcrect源矩形框,dstrect目标矩形框,两者都传nullptr表示整个纹理渲染到整个目标上去
    END(SDL_RenderCopy(renderer, texture, nullptr, nullptr),
          SDL_RenderCopy);
    
    // 将此前的所有需要渲染的内容更新到屏幕上
    SDL_RenderPresent(renderer);
    
    1. srcrect:源矩形框,代表截取纹理哪一部分出来;dstrect:目标矩形框,代表纹理渲染到 Rendering Target 哪一个部分;如下图:


      复制纹理到渲染目标
    2. srcrect 和 dstrect 全都传 nullptr,将整一个纹理渲染到整个渲染目标上去:


      复制纹理到渲染目标

    延迟退出

    // 延迟3秒退出
    SDL_Delay(3000);
    

    如果我想让显示的bmp图片窗口一直显示呢?

    while (!isInterruptionRequested()) {
        SDL_Event event;
        SDL_WaitEvent(&event);
        switch (event.type) {
            case SDL_QUIT:
                goto end;
        }
    }
    

    释放资源

    end:
        // 释放资源
        SDL_FreeSurface(surface);
        SDL_DestroyTexture(texture);
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_Quit();
    

    代码链接

    SDL的扩展用法

    如果我们没有拷贝纹理数据到渲染目标的话,窗口会是一片漆黑。我们是可以设置渲染目标背景色的:

    // 设置绘制颜色(画笔颜色) SDL_ALPHA_OPAQUE = 255
    END(SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE),SDL_SetRenderDrawColor);
    // 设置绘制颜色(画笔颜色)清除渲染目标
    END(SDL_RenderClear(renderer),SDL_RenderClear);
    

    也可以在窗口绘制矩形框:

    // 画一个黄色矩形框
    SDL_SetRenderDrawColor(renderer,255,255,0,SDL_ALPHA_OPAQUE);
    rect = {0, 0, 50, 50};
    // SDL_RenderDrawRect(renderer, &rect);//空心矩形框
    SDL_RenderFillRect(renderer, &rect);//实心矩形框
    

    还可以修改渲染目标。有时候需要把同一个图形在窗口绘制多次,那么我们就可以新建一个 texture,然后修改渲染目标为 texture,在当前 texture 绘制完成后,修改渲染目标为 window,再复制 texture 到 window:

    // 创建Texture
    SDL_Texture *ShowBmpThread::createTexture(SDL_Renderer *renderer){
        // 创建纹理
        SDL_Texture *texture = SDL_CreateTexture(renderer, // 渲染上下文
                                                 SDL_PIXELFORMAT_RGB24, // SDL_PixelFormatEnum,参考文档:https://wiki.libsdl.org/SDL_PixelFormatEnum
                                                 SDL_TEXTUREACCESS_TARGET, // SDL_TextureAccess,此处我们要把纹理作为渲染目标,选择:SDL_TEXTUREACCESS_TARGET,参考文档:https://wiki.libsdl.org/SDL_TextureAccess
                                                 50, // 纹理的宽
                                                 50); // 纹理的高
        if (!texture) return nullptr;
    
        // 设置纹理为渲染目标
        if (SDL_SetRenderTarget(renderer, texture)) return nullptr;
        // 设置 texture 背景色
    //    if (SDL_SetRenderDrawColor(renderer, 0, 155, 0, SDL_ALPHA_OPAQUE)) return nullptr;
    //    if (SDL_RenderClear(renderer)) return nullptr;
        // 设置绘制颜色
        if (SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE)) return nullptr;
        // 绘制矩形框
        SDL_Rect rect = {0, 0, 50, 50};
        if (SDL_RenderDrawRect(renderer, &rect)) return nullptr;
        // 绘制线条
        if (SDL_RenderDrawLine(renderer, 0, 0, 50, 50)) return nullptr;
        if (SDL_RenderDrawLine(renderer, 50, 0, 0, 50)) return nullptr;
        return texture;
    }
    

    监听鼠标点击事件,重新绘制矩形框到渲染目标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;
        // 查询纹理宽高
        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};
    
        // 清除①
        // if (SDL_RenderClear(renderer)) return;
    
        // 复制纹理到渲染目标
        if (SDL_RenderCopy(renderer, texture, nullptr, &dstRect)) return;
    
    //    SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
    //    SDL_RenderDrawRect(renderer, &dstRect);
    
        // 更新渲染操作到屏幕上
        SDL_RenderPresent(renderer);
    }
    
    窗口绘制多次矩形框

    在showClick方法中打开①处代码的注释if (SDL_RenderClear(renderer)) return;,就可以实现显示新的矩形框后清楚原有的矩形框

    SDL显示矩形框

    扩展的代码链接

    注意:上面代码是在window环境下运行在子线程中的,如果是mac环境则需要放在主线程中

    相关文章

      网友评论

          本文标题:20_使用SDL显示BMP图片

          本文链接:https://www.haomeiwen.com/subject/iapitdtx.html