来点图片是正确的、必要的、鼓舞人心的。
上期决定添加图片的支持。SDL 作为一个 2D 图形库,对于图片自然有一流的支持,用起来也非常舒心。
导入
和字体支持一样,SDL 的图片支持也是单独成库的。编辑 vcpkg.json 引入 SDL3_image 库:
{
/* ... */
"dependencies": [
/* ... */
{
"name": "sdl3-image",
"features": [
"jpeg",
"png"
]
}
]
/* ... */
}
注意到常见的两个图像格式 .jpg 和 .png 需要作为功能单独开启。SDL 底层实际会去调用 libjpeg-turbo 和 libpng 两个库来处理这两个类型的图像。
然后加入对应的链接库:
# ...
find_package(SDL3_image CONFIG REQUIRED)
# ...
target_link_libraries(Foundation
$<IF:$<TARGET_EXISTS:SDL3_image::SDL3_image-shared>,SDL3_image::SDL3_image-shared,SDL3_image::SDL3_image-static>
)
就可以使用了。
加载图像
和 SDL 2 版本不同,SDL 3 版本的图形库不再需要手动调用 IMG_Init(.) 来初始化支持了。它会在你首次使用对应的图像类型的时候,自动初始化底层库。
要加载一个图像,我们只需要执行 IMG_LoadTexture(.) 就可以将图像加载成为一个纹理。
计算机图像处理一般到了这个时候,就该抽出一张莱娜图了。不过这回我们还是用点别的吧——
I retired from modeling a long time ago. It's time I retired from tech, too.
auto* image = IMG_LoadTexture(sdl.renderer(), "assets/image/pepper.png");
将绘制抽象成类
后续我们应该会用到更多的需要引用外部资源,或者有复杂绘制流程。如果都摊开的话未免有点太乱。以图像为例子,我们可以构建一个类来封装它的资源申请、释放和绘制操作:
class Image {
public:
Image(SdlContext *sdl, int x, int y, const char* path) : _sdl(sdl), _x(x), _y(y) {
_image = IMG_LoadTexture(_sdl->renderer(), path);
}
~Image() {
if (_image) {
SDL_DestroyTexture(_image);
}
}
void render(SdlContext *sdl) {
if (_image) {
_draw_image();
} else {
_draw_missing_texture();
}
}
private:
SdlContext* _sdl;
int _x, _y;
SDL_Texture* _image{nullptr};
void _draw_image() { /* ... */ }
void _draw_missing_texture() { /* ... */ }
}
然后我们就可以这样进行调用:
Image img(&sdl, 10, 10, "assets/image/pepper.png");
img.render(&sdl);
处理资源文件找不到的情况
有些时候,图片文件可能恰好找不到或者是损坏的。这会使得 IMG_LoadTexture(.) 返回一个 nullptr. 我们在绘制的时候需要处理这种情况。
业界常用的方法是绘制一个「材质丢失」站位图,一般是洋红和黑色组成的棋盘格。由于没有图片加载的情况下,我们无从知道这个图像具体的宽高,所以就先统一使用 200x200 大小。
void _draw_missing_texture() {
for (int y = 0, cy = 0; y < 200; y += 16, cy++) {
for (int x = 0, cx = 0; x < 200; x += 16, cx++) {
const SDL_FRect rect = {
.x = _x + static_cast<float>(x),
.y = _y + static_cast<float>(y),
.w = 16.f,
.h = 16.f,
};
if ((xc + yc) % 2 == 0) {
SDL_SetRenderDrawColor(_sdl->renderer(), 255, 0, 255, 255);
} else {
SDL_SetRenderDrawColor(_sdl->renderer(), 0, 0, 0, 255);
}
SDL_RenderFillRect(_sdl->renderer(), &rect);
}
}
}
图 1 棋盘格
绘制图像
图像绘制本身非常简单,只是一个纹理复制的事情:
void _draw_image() {
const SDL_FRect dstRect = {
.x = _x,
.y = _y,
.w = _image->w,
.h = _image->h
};
SDL_SetRenderTarget(_sdl->renderer(), _sdl->texture());
SDL_RenderTexture(_sdl->renderer(), _image, nullptr, &dstRect);
}
图 2 彩椒图案
下一步
现在,图形绘制原语已经基本齐备。接下来或许可以研究一些动画效果了。


正在加载评论……