PreFound:07 - 图片拼贴报

/ dousha99

来点图片是正确的、必要的、鼓舞人心的。

上期决定添加图片的支持。SDL 作为一个 2D 图形库,对于图片自然有一流的支持,用起来也非常舒心。

导入

和字体支持一样,SDL 的图片支持也是单独成库的。编辑 vcpkg.json 引入 SDL3_image 库:

{
    /* ... */
    "dependencies": [
        /* ... */
        {
            "name": "sdl3-image",
            "features": [
                "jpeg",
                "png"
            ]
        }
    ]
    /* ... */
}

注意到常见的两个图像格式 .jpg.png 需要作为功能单独开启。SDL 底层实际会去调用 libjpeg-turbolibpng 两个库来处理这两个类型的图像。

然后加入对应的链接库:

# ...
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);
        }
    }
}

checkerboard.png

图 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);
}

pepper.png

图 2 彩椒图案

下一步

现在,图形绘制原语已经基本齐备。接下来或许可以研究一些动画效果了。

正在加载评论……

发表评论

您的评论将由管理员审核后方可公开显示。

Your comments will be submitted to a human moderator and will only be shown publicly after approval.