Tutorial de SDL – Parte 10 – Paleta de cores

Continuando nossa série de artigo traduzido do site lazyfoo, agora iremos ver como lidar com a paleta de cores dos elementos exibidos na tela.

Quando se precisa renderizar múltiplas imagens na tela, usar imagens com fundos transparentes normalmente é necessário. Felizmente, o SDL fornece uma maneira fácil de fazer isso usando paleta de cores.

//Texture wrapper class
class LTexture
{
    public:
        //Initializes variables
        LTexture();

        //Deallocates memory
        ~LTexture();

        //Loads image at specified path
        bool loadFromFile( std::string path );

        //Deallocates texture
        void free();

        //Renders texture at given point
        void render( int x, int y );

        //Gets image dimensions
        int getWidth();
        int getHeight();

    private:
        //The actual hardware texture
        SDL_Texture* mTexture;

        //Image dimensions
        int mWidth;
        int mHeight;
};

Nesse artigo, iremos empacotar SDL_Textura em uma classe para que algumas coisa fiquem mais fáceis. Por exemplo, se quiser obter algumas informações sobre a textura como sua largura ou altura você teria que usar algumas funções de SDL para pesquisar a informação da textura. Ao invés disso o que iremos fazer é usar uma classe para empacotar e armazenar a informação sobre a textura.

Em termos de design, usaremos uma classe bem simples. Terá um par construtor/destrutor, um método para carregar um arquivo, um desalocador, um renderizador que recebe uma posição e funções para obter as dimensões da textura. Em relação as variáveis membro, teremos a textura que iremos empacotar e variáveis para armazenar a largura/altura.

//The window we'll be rendering to
SDL_Window* gWindow = NULL;

//The window renderer
SDL_Renderer* gRenderer = NULL;

//Scene textures
LTexture gFooTexture;
LTexture gBackgroundTexture;

Para essa cena usaremos duas texturas que iremos carregar, declaradas como gFooTexturegBackgroundTexture. Iremos usar essa textura:

foo

Ajuste o fundo na cor ciano (azul claro) e renderize sobre esse plano de fundo:

background

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

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

O construtor inicializa as variáveis e o destrutor chama o desalocador que iremos ver mais adiante:

bool LTexture::loadFromFile( std::string path )
{
    //Get rid of preexisting texture
    free();
The texture loading function pretty much works like it did in the texture loading tutorial but with some small but important tweaks. First off we deallocate the texture in case there's one that's already loaded.
    //The final texture
    SDL_Texture* newTexture = NULL;

    //Load image at specified path
    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 ) );

Em seguida, ajustamos a paleta de cores da imagem com SDL_SetColorKey antes de criar um textura para ela. O primeiro argumento é a superfície que queremos ajustar sua paleta de cores, o segundo argumento informa se queremos ativar a paleta de cores e o último argumento é o pixel que queremos usar o paleta de cores.

O jeito mais multiplataforma de criar um pixel a partir de uma core RGB é como SDL_MapRGB. O primeiro argumento é o formato desejado para o pixel. Felizmente a superfície carregada possui uma variável membro com o formato. As últimas três variáveis são os componentes vermelho, verde e azul para a core que você quer mapear. Aqui iremos mapear a core ciano que possui os componentes: vermelho = 0, verde = 255 e azul = 255.

        //Create texture from surface pixels
        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
        {
            //Get image dimensions
            mWidth = loadedSurface->w;
            mHeight = loadedSurface->h;
        }

        //Get rid of old loaded surface
        SDL_FreeSurface( loadedSurface );
    }

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

Depois de aplicar a palete de cores na superfície carregada, criamos uma textura a partir da superfície carregada. Se a textura for criada com sucesso, armazenamos a largura/altura da textura e retornamos se a textura foi carregada com sucesso.

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

O desalocador simplesmente verifica se uma textura existe, destrói ela e reinicializa as variáveis membro.

void LTexture::render( int x, int y )
{
    //Set rendering space and render to screen
    SDL_Rect renderQuad = { x, y, mWidth, mHeight };
    SDL_RenderCopy( gRenderer, mTexture, NULL, &renderQuad );
}

Aqui você pode ver porque precisamos de uma classe empacotando tudo. Até agora,  estivemos renderizando imagens de tela cheia, de forma que não precisávamos especificar a posição. Como não precisávamos especificar a posição, apenas chamávamos SDL_RenderCopy com os dois últimos argumentos como NULL.

Quando for renderizar uma textura em um certo local, você precisa especificar um retângulo de destino que ajusta a posição x/y e a largura/altura. Não podemos especificar a largura/altura sem saber as dimensões da imagem original. Assim, quando renderizarmos nossa textura criamos um retângulo com o argumento da posição e as variáveis membro largura e altura, e passamos esse retângulo para SDL_RenderCopy.

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

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

A última função membro permite-nos obter os valores para a largura/altura quando precisarmos deles.

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

Aqui temos as funções de carregamento de imagem em ação.

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

E aqui temos os desalocadores.

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

Por fim, temos aqui o loop principal com nossa renderização de texturas. É um loop básico que lida com o eventos, limpa a tela, renderiza o plano de fundo, a figura fo boneco sobre o fundo e  atualiza a tela.

Uma coisa importante para se observar é que a ordem importa quando se estar renderizando múltiplos objetos na tela a cada quadro. Se renderizarmos o boneco primeiro, o plano de fundo será  renderizado sobre ele e você não verá a imagem do boneco.

Baixe o código fonte e os arquivo de mídia desse artigo aqui.