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 gFooTexture e gBackgroundTexture. Iremos usar essa textura:
Ajuste o fundo na cor ciano (azul claro) e renderize sobre esse plano de fundo:
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.