首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(译)SDL编程入门(10)Color Key

(译)SDL编程入门(10)Color Key

作者头像
arcticfox
发布2020-10-09 15:19:20
1K0
发布2020-10-09 15:19:20
举报

Color Key

当在屏幕上渲染多个图像时,通常需要让图像具有透明背景。幸运的是,SDL提供了一种使用颜色键控的简单方法来实现这一点。

//纹理包装类
class LTexture
{
    public:
        //初始化
        LTexture();

        //释放
        ~LTexture();

        //在指定路径上加载图像
        bool loadFromFile( std::string path );

        //释放纹理
        void free();

        //渲染指定点的纹理
        void render( int x, int y );

        //获取图像尺寸
        int getWidth();
        int getHeight();

    private:
        //实际的硬件纹理
        SDL_Texture* mTexture;

        //图像尺寸
        int mWidth;
        int mHeight;
};

在本教程中,我们将把SDL_Texture包装在一个类中,以使一些事情变得更简单。例如,如果你想获得某些关于纹理的信息,如它的宽度或高度,你将不得不使用一些SDL函数来查询纹理的信息。相反,我们要做的是使用一个类来封装和存储纹理的信息。

从设计上来说,这是一个相当直接的类。它有一个构造函数/析构函数对,一个文件加载器,一个deallocator,一个接收位置的渲染器,以及获取纹理尺寸的函数。对于成员变量,它有我们要包裹的纹理,以及存储宽度/高度的变量。

//我们要渲染的窗口
SDL_Window* gWindow = NULL;

//窗口渲染器
SDL_Renderer* gRenderer = NULL;

//场景纹理
LTexture gFooTexture;
LTexture gBackgroundTexture;

对于此场景,我们将在此处加载两个纹理,它们分别声明为“ gFooTexture”和“ gBackgroundTexture”。我们将采用这种foo纹理:

对青色(浅蓝色)背景设置颜色键,并将其渲染在此背景之上:

LTexture::LTexture()
{
    //Initialize
    mTexture = NULL;
    mWidth = 0;
    mHeight = 0;
}

LTexture::~LTexture()
{
    //Deallocate
    free();
}

构造函数初始化变量,而析构函数调用deallocator,这一点我们后面会讲到。

bool LTexture::loadFromFile( std::string path ){
    //释放现有的纹理
    free();
     //最终纹理
    SDL_Texture* newTexture = NULL;

    //在指定路径加载图像
    SDL_Surface* loadedSurface = IMG_Load( path.c_str() );
    if( loadedSurface == NULL )
    {
        printf( "Unable to load image %s! SDL_image Error: %s\n", path.c_str(), IMG_GetError() );
    }
    else
    {
        //Color key image
        SDL_SetColorKey( loadedSurface, SDL_TRUE, SDL_MapRGB( loadedSurface->format, 0, 0xFF, 0xFF ) );
        //用表面像素创建纹理
        newTexture = SDL_CreateTextureFromSurface( gRenderer, loadedSurface );
        if( newTexture == NULL )
        {
            printf( "Unable to create texture from %s! SDL Error: %s\n", path.c_str(), SDL_GetError() );
        }
        else
        {
            //获取图片尺寸
            mWidth = loadedSurface->w;
            mHeight = loadedSurface->h;
        }

        //释放旧的表面
        SDL_FreeSurface( loadedSurface );
    }

    //Return success
    mTexture = newTexture;
    return mTexture != NULL;
}

纹理加载功能的工作原理和之前的纹理加载课程中的差不多,但做了一些小的但重要的调整。首先,我们对纹理进行重新分配,以防有一个已经加载的纹理。

接下来,在创建纹理之前,我们使用 SDL_SetColorKey[1] 对图像进行颜色抠像。第一个参数是我们要进行颜色抠像的表面,第二个参数涵盖了我们是否要启用颜色抠像,最后一个参数是我们要进行颜色抠像的像素。

从RGB颜色创建像素最跨平台的方法是用SDL_MapRGB[2]。第一个参数是我们想要的像素的格式。幸运的是,加载的表面有一个格式成员变量。最后三个变量是你想要映射的颜色的红、绿、蓝三个组件。这里我们要映射的是青色,也就是红色0,绿色255,蓝色255。

在对加载的表面进行颜色键控后,我们从加载和颜色键控的表面创建一个纹理。如果纹理创建成功,我们存储纹理的宽度/高度,并返回纹理是否加载成功。

void LTexture::free(){
    //Free texture if it exists
    if( mTexture != NULL )
    {
        SDL_DestroyTexture( mTexture );
        mTexture = NULL;
        mWidth = 0;
        mHeight = 0;
    }
}

deallocator简单地检查纹理是否存在,销毁它,并重新初始化成员变量。

void LTexture::render( int x, int y ){
    //设置渲染空间并渲染到屏幕上
    SDL_Rect renderQuad = { x, y, mWidth, mHeight };
    SDL_RenderCopy( gRenderer, mTexture, NULL, &renderQuad );
}

在这里你可以看到为什么我们需要一个包装类。到目前为止,我们一直在渲染全屏图像,所以我们不需要指定位置。因为我们不需要指定位置,所以我们只需要调用SDL_RenderCopy,最后两个参数为NULL。

当渲染某个地方的纹理时,你需要指定一个目标矩形,设置x/y位置和宽度/高度。在不知道原始图像的尺寸的情况下,我们无法指定宽度/高度。所以这里当我们渲染纹理时,我们用位置参数和成员宽度/高度创建一个矩形,并将这个矩形传入SDL_RenderCopy。

int LTexture::getWidth(){
    return mWidth;
}

int LTexture::getHeight(){
    return mHeight;
}

最后这些成员函数允许我们在需要的时候获得宽度/高度。

bool loadMedia(){
    //Loading success flag
    bool success = true;

    //Load Foo' texture
    if( !gFooTexture.loadFromFile( "10_color_keying/foo.png" ) )
    {
        printf( "Failed to load Foo' texture image!\n" );
        success = false;
    }
    
    //Load background texture
    if( !gBackgroundTexture.loadFromFile( "10_color_keying/background.png" ) )
    {
        printf( "Failed to load background texture image!\n" );
        success = false;
    }

    return success;
}

下面是图片加载功能的操作。

void close(){
    //Free loaded images
    gFooTexture.free();
    gBackgroundTexture.free();

    //Destroy window    
    SDL_DestroyRenderer( gRenderer );
    SDL_DestroyWindow( gWindow );
    gWindow = NULL;
    gRenderer = NULL;

    //Quit SDL subsystems
    IMG_Quit();
    SDL_Quit();
}

而这里是deallocators。

            //While application is running
            while( !quit )
            {
                //Handle events on queue
                while( SDL_PollEvent( &e ) != 0 )
                {
                    //User requests quit
                    if( e.type == SDL_QUIT )
                    {
                        quit = true;
                    }
                }

                //Clear screen
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF );
                SDL_RenderClear( gRenderer );

                //Render background texture to screen
                gBackgroundTexture.render( 0, 0 );

                //Render Foo' to the screen
                gFooTexture.render( 240, 190 );

                //Update screen
                SDL_RenderPresent( gRenderer );
            }

这里是我们纹理渲染的主循环。这是一个基本的循环,它处理事件,清除屏幕,渲染背景,渲染它上面的简笔画,并更新屏幕。

需要注意的是,当你每一帧向屏幕渲染多个东西时,顺序很重要。如果我们先渲染简笔画,背景就会渲染在它上面,你就看不到简笔画了。

这里[3]下载本教程的媒体和源代码。

参考资料

[1]

SDL_SetColorKey: https://wiki.libsdl.org/SDL_SetColorKey

[2]

SDL_MapRGB: https://wiki.libsdl.org/SDL_MapRGB

[3]

这里: http://www.lazyfoo.net/tutorials/SDL/10_color_keying/10_color_keying.zip

[4]

原文链接: http://www.lazyfoo.net/tutorials/SDL/10_color_keying/index.php

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程之路从0到1 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Color Key
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档