文本的主要内容是:使用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> |
| |
| |
| #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; |
初始化子系统
| |
| 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 |
| |
| |
| surface = SDL_LoadBMP(FILENAME); |
| END(!surface, SDL_LoadBMP); |
创建窗口
| |
| window = SDL_CreateWindow( |
| |
| "SDL显示BMP图片", |
| |
| SDL_WINDOWPOS_UNDEFINED, |
| |
| SDL_WINDOWPOS_UNDEFINED, |
| |
| surface->w, |
| |
| surface->h, |
| |
| SDL_WINDOW_SHOWN |
| ); |
| END(!window, SDL_CreateWindow); |
SDL_WindowFlags 枚举:
| typedef enum |
| { |
| SDL_WINDOW_FULLSCREEN = 0x00000001, |
| SDL_WINDOW_OPENGL = 0x00000002, |
| 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, |
| SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000, |
| |
| |
| SDL_WINDOW_MOUSE_CAPTURE = 0x00004000, |
| SDL_WINDOW_ALWAYS_ON_TOP = 0x00008000, |
| SDL_WINDOW_SKIP_TASKBAR = 0x00010000, |
| SDL_WINDOW_UTILITY = 0x00020000, |
| SDL_WINDOW_TOOLTIP = 0x00040000, |
| SDL_WINDOW_POPUP_MENU = 0x00080000, |
| SDL_WINDOW_VULKAN = 0x10000000, |
| SDL_WINDOW_METAL = 0x20000000 |
| } SDL_WindowFlags; |
我们也可以从一个已经存在的本地窗口创建 window,传入的参数是指向本地窗口的指针。我创建了一个 QLabel,此处我们传入 QLabel 的 winId() 就可以:
创建渲染上下文
| |
| renderer = SDL_CreateRenderer(window, |
| |
| -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:
| |
| |
| |
| typedef enum |
| { |
| SDL_RENDERER_SOFTWARE = 0x00000001, |
| SDL_RENDERER_ACCELERATED = 0x00000002, |
| SDL_RENDERER_PRESENTVSYNC = 0x00000004, |
| SDL_RENDERER_TARGETTEXTURE = 0x00000008 |
| } SDL_RendererFlags; |
这里我们是参考ffplay源码的写法:

创建纹理
| |
| texture = SDL_CreateTextureFromSurface( |
| renderer, |
| surface); |
| END(!texture, SDL_CreateTextureFromSurface); |
渲染
| |
| END(SDL_SetRenderDrawColor(renderer, |
| 255, 255, 0, |
| SDL_ALPHA_OPAQUE), |
| SDL_SetRenderDrawColor); |
| |
| |
| END(SDL_RenderClear(renderer), |
| SDL_RenderClear); |
| |
| |
| |
| END(SDL_RenderCopy(renderer, texture, nullptr, nullptr), |
| SDL_RenderCopy); |
| |
| |
| SDL_RenderPresent(renderer); |
- srcrect:源矩形框,代表截取纹理哪一部分出来;dstrect:目标矩形框,代表纹理渲染到 Rendering Target 哪一个部分;如下图:

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

延迟退出
如果我想让显示的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的扩展用法
如果我们没有拷贝纹理数据到渲染目标的话,窗口会是一片漆黑。我们是可以设置渲染目标背景色的:
| |
| 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_RenderFillRect(renderer, &rect); |
还可以修改渲染目标。有时候需要把同一个图形在窗口绘制多次,那么我们就可以新建一个 texture,然后修改渲染目标为 texture,在当前 texture 绘制完成后,修改渲染目标为 window,再复制 texture 到 window:
| |
| SDL_Texture *ShowBmpThread::createTexture(SDL_Renderer *renderer){ |
| |
| SDL_Texture *texture = SDL_CreateTexture(renderer, |
| SDL_PIXELFORMAT_RGB24, |
| SDL_TEXTUREACCESS_TARGET, |
| 50, |
| 50); |
| if (!texture) return nullptr; |
| |
| |
| if (SDL_SetRenderTarget(renderer, texture)) 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_RenderCopy(renderer, texture, nullptr, &dstRect)) return; |
| |
| |
| |
| |
| |
| SDL_RenderPresent(renderer); |
| } |

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

扩展的代码链接
注意:上面代码是在window环境下运行在子线程中的,如果是mac环境则需要放在主线程中
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!