Tutorial de SDL – Parte 40 – Manipulação de texturas

Continuando nossa série de artigos traduzidos do site lazyfoo, veremos agora como aplicar alguns efeitos nas texturas de uma imagem, manipulando duas características.
Para criar efeitos gráficos frequentemente é necessário acesso aos pixels. Nesse artigo, iremos alterar os pixels de uma imagem para clarear o fundo.

//Texture wrapper class
class LTexture
{
    public:
        //Initializes variables
        LTexture();
        //Deallocates memory
        ~LTexture();
        //Loads image at specified path
        bool loadFromFile( std::string path );
        #ifdef _SDL_TTF_H
        //Creates image from font string
        bool loadFromRenderedText( std::string textureText, SDL_Color textColor );
        #endif
        //Deallocates texture
        void free();
        //Set color modulation
        void setColor( Uint8 red, Uint8 green, Uint8 blue );
        //Set blending
        void setBlendMode( SDL_BlendMode blending );
        //Set alpha modulation
        void setAlpha( Uint8 alpha );
        //Renders texture at given point
        void render( int x, int y, SDL_Rect* clip = NULL, double angle = 0.0, SDL_Point* center = NULL, SDL_RendererFlip flip = SDL_FLIP_NONE );
        //Gets image dimensions
        int getWidth();
        int getHeight();
        //Pixel manipulators
        bool lockTexture();
        bool unlockTexture();
        void* getPixels();
        int getPitch();
    private:
        //The actual hardware texture
        SDL_Texture* mTexture;
        void* mPixels;
        int mPitch;
        //Image dimensions
        int mWidth;
        int mHeight;
};

Aqui iremos adicionar nova funcionalidade à classe da textura. Temos funções para bloquear/desbloquear a textura pois para podermos acessar os pixels da textura temos que bloqueá-la e uma vez que tivermos terminados de lidar com os pixels temos que desbloqueá-la.

Temos uma função  para obter os pixels brutos e uma função para obter o pitch. O pitch é basicamente a largura da texteura na memória. Em alguns equipamentos mais antigos e portáteis, existem limitações do tamanho da textura que você pode ter. Se você criar uma textura com uma lardura de 100 pixels, pode ser que ela seja alterada para 128 pixels (a potência de 2 mais próxima). Usando o pitch, saberemos como a imagem é armazenada na memória.

Em termos de atributos, temos um ponteiro para os pixels após o bloqueio da textura e o pitch.

bool LTexture::loadFromFile( std::string path )
{
    //Get rid of preexisting texture
    free();
    //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
    {
        //Convert surface to display format
        SDL_Surface* formattedSurface = SDL_ConvertSurface( loadedSurface, SDL_GetWindowSurface( gWindow )->format, NULL );
        if( formattedSurface == NULL )
        {
            printf( "Unable to convert loaded surface to display format! SDL Error: %s\n", SDL_GetError() );
        }
        else
        {
            //Create blank streamable texture
            newTexture = SDL_CreateTexture( gRenderer, SDL_GetWindowPixelFormat( gWindow ), SDL_TEXTUREACCESS_STREAMING, formattedSurface->w, formattedSurface->h );
            if( newTexture == NULL )
            {
                printf( "Unable to create blank texture! SDL Error: %s\n", SDL_GetError() );
            }

Para que sejamos capazes de editar a textura, temos que carregar nossa textura de forma diferente. Quando criamos texturas a partir de superfícies em artigo anteriores, elas tinham o valor de SDL_TextureAccess padrão SDL_TEXTUREACCESS_STATIC, o que significava que não podíamos altera-la após ter sido criada. Para podermos ser capazes de editar os pixels da textura temos que criar a textura como SDL_TEXTUREACCESS_STREAMING. Primeiro temos que carregar a imagem como uma superfície como fizemos anteriormente. Então temos que converter a superfície para o mesmo formato de pixel da janela. Por fim, criamos uma textura em branco com SDL_Createtexture.

            else
            {
                //Lock texture for manipulation
                SDL_LockTexture( newTexture, NULL, &mPixels, &mPitch );
                //Copy loaded/formatted surface pixels
                memcpy( mPixels, formattedSurface->pixels, formattedSurface->pitch * formattedSurface->h );
                //Unlock texture to update
                SDL_UnlockTexture( newTexture );
                mPixels = NULL;
                //Get image dimensions
                mWidth = formattedSurface->w;
                mHeight = formattedSurface->h;
            }
            //Get rid of old formatted surface
            SDL_FreeSurface( formattedSurface );
        }
        //Get rid of old loaded surface
        SDL_FreeSurface( loadedSurface );
    }
    //Return success
    mTexture = newTexture;
    return mTexture != NULL;
}

Depois que a textura é criada, temos que manualmente copias os pixels da superfície para a textura. Para capturas os pixels da textura usamos SDL_LockTexture. O primeiro argumento é a textura que queremos capturar seus pixels. O segundo argumento é a região de onde queremos capturas os pixels, e como iremos capturas os pixels de toda a textura configuramos esses argumento como NULL. O terceira argumento é o ponteiro que será usado como endereço dos pixels, e o último argumento será configurada com o pitch da textura.

Depois que tivermos os pixels da textura, copiamos os pixels da superfície para a textura usando memcpy. O primeiro argumento é o destino, o segundo é a origem, e o terceiro argumento é o número de bytes que iremos copiar. Felizmente, o pitch do SDL nos fornece o número de bytes por linha de pixels, de forma que precisamos apenas multiplicar ele pela altura da superfície para copiar todos os pixels da image. Quando tivermos copiados os pixels da superfície para a textura, desbloqueamos a textura para atualiza-la com os novos pixels usando SDL_UnlickTexture. Depois que a textura é desbloqueada o ponteiro de pixels é invalidado configurando ele como NULL.

Com os pixels da superfície copiados para a textura, nos livramos das superfícies antigas e retornamos true se a textura tiver sido carregada com sucesso.

bool LTexture::lockTexture()
{
    bool success = true;
    //Texture is already locked
    if( mPixels != NULL )
    {
        printf( "Texture is already locked!\n" );
        success = false;
    }
    //Lock texture
    else
    {
        if( SDL_LockTexture( mTexture, NULL, &mPixels, &mPitch ) != 0 )
        {
            printf( "Unable to lock texture! %s\n", SDL_GetError() );
            success = false;
        }
    }
    return success;
}
bool LTexture::unlockTexture()
{
    bool success = true;
    //Texture is not locked
    if( mPixels == NULL )
    {
        printf( "Texture is not locked!\n" );
        success = false;
    }
    //Unlock texture
    else
    {
        SDL_UnlockTexture( mTexture );
        mPixels = NULL;
        mPitch = 0;
    }
    return success;
}

Aqui temos nossas função de bloqueio e desbloqueio da textura após o carregamento da imagem.

void* LTexture::getPixels()
{
    return mPixels;
}
int LTexture::getPitch()
{
    return mPitch;
}

Finalmente, temos os métodos de acesso para os pixels e para o pitch enquanto a textura estiver bloqueada. Agora que criamos uma textura que pode ser editável e bloqueada/desbloqueada, chegou a hora de executar algum processamento nos pixels da textura.

bool loadMedia()
{
    //Loading success flag
    bool success = true;
    //Load foo' texture
    if( !gFooTexture.loadFromFile( "40_texture_manipulation/foo.png" ) )
    {
        printf( "Failed to load corner texture!\n" );
        success = false;
    }
    else
    {
        //Lock texture
        if( !gFooTexture.lockTexture() )
        {
            printf( "Unable to lock Foo' texture!\n" );
        }

Na nossa função de carregamento de mídia, depois que carregamos a textura, bloqueamos ela de forma que possamos alterar os pixels.

        //Manual color key
        else
        {
            //Get pixel data
            Uint32* pixels = (Uint32*)gFooTexture.getPixels();
            int pixelCount = ( gFooTexture.getPitch() / 4 ) * gFooTexture.getHeight();

Depois que a textura é bloqueada, iremos percorrer os pixels e tornar todos os pixels do fundo transparentes. O que estamos fazendo é essencialmente manualmente codificando as cores da imagem.

Primeiro precisamos capturas todos os pixels. Nosso método de acesso aos pixels retorna um ponteiro void e queremos pixels de 32 bits, assim sobrecarregamos o tipo para um inteiro sem sinal de 32 bits (unsigned integer).

Em seguida, queremos obter o número de pixels. Temos o pitch que é a largura em bytes. Precisamos da largura em pixels e como temos 4 bytes por pixel. tudo que precisamos fazer é dividir por 4 para obtermos o pitch em pixels. Então multiplicamos o pitch pela altura para o obter o número total de pixels.

            //Map colors
            Uint32 colorKey = SDL_MapRGB( SDL_GetWindowSurface( gWindow )->format, 0, 0xFF, 0xFF );
            Uint32 transparent = SDL_MapRGBA( SDL_GetWindowSurface( gWindow )->format, 0xFF, 0xFF, 0xFF, 0x00 );
            //Color key pixels
            for( int i = 0; i < pixelCount; ++i )
            {
                if( pixels[ i ] == colorKey )
                {
                    pixels[ i ] = transparent;
                }
            }
            //Unlock texture
            gFooTexture.unlockTexture();
        }
    }
    return success;
}

O que iremos fazer é encontrar todos os pixels da codificação de cores e substitui-los por pixels transparentes. Primeiro mapeamos o código de cores e a cor transparente. Em seguida percorremos todos os pixels e verificamos se o pixel bate com a codificação das cores. Se bater, damos a ele o valor de um pixel transparente.

Após tivermos percorrido os pixels, desbloqueamos a textura para atualizar-la com os novos pixels.

Baixe os arquivos de mídia e de código fonte do exemplo desse artigo aqui.