Tutorial de SDL – Parte 42 – Transmissão de texturas

Continuando nossa série de artigos traduzidos da lazyfoo,  veremos agora como trabalhar com texturas cuja fonte não seja uma imagem bitmap.

Algumas vezes queremos renderizar os dados de um pixel de uma origem que não seja um bitmap, como de uma webcam. Usando a transmissão de texturas, podemos renderizar pixels de qualquer origem.

//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
        //Creates blank texture
        bool createBlank( int width, int height );
        //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();
        void copyPixels( void* pixels );
        int getPitch();
        Uint32 getPixel32( unsigned int x, unsigned int y );
    private:
        //The actual hardware texture
        SDL_Texture* mTexture;
        void* mPixels;
        int mPitch;
        //Image dimensions
        int mWidth;
        int mHeight;
};

Aqui nós adicionamos mais funcionalidade à nossa classe de textura. A função createBlank aloca uma textura em branco onde podemos copiar dados quando estivermos transmitindo. A função copyPixels copia os dados dos pixel que queremos transmitir.

//A test animation stream
class DataStream
{
    public:
        //Initializes internals
        DataStream();
        //Loads initial data
        bool loadMedia();
        //Deallocator
        void free();
        //Gets current frame data
        void* getBuffer();
    private:
        //Internal data
        SDL_Surface* mImages[ 4 ];
        int mCurrentImage;
        int mDelayFrames;
};

Aqui temos nossa classe de transmissão de dados. Não nos aprofundaremos em como ela funciona pois não nos importamos com ela. Quando se lida com API’s de webcam, decodificação de vídeo, tipicamente não se aprofunda muito em como elas funcionam pois tudo que nos importamos é obter os dados de vídeo ou áudio deles.

Tudo que nos importamos aqui é a função getBuffer, que lê os pixels atuais do buffer de dados.

bool LTexture::createBlank( int width, int height )
{
    //Create uninitialized texture
    mTexture = SDL_CreateTexture( gRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, width, height );
    if( mTexture == NULL )
    {
        printf( "Unable to create blank texture! SDL Error: %s\n", SDL_GetError() );
    }
    else
    {
        mWidth = width;
        mHeight = height;
    }
    return mTexture != NULL;
}

Como você pode ver, tudo que essa função faz é criar um textura RGBA de 32 bits com acesso para transmissão. Uma coisa que você tem que se certificar é de que quando criar sua textura o formato dela combine com o formato dos pixels que estamos transmitindo.

void LTexture::copyPixels( void* pixels )
{
    //Texture is locked
    if( mPixels != NULL )
    {
        //Copy to locked pixels
        memcpy( mPixels, pixels, mPitch * mHeight );
    }
}

Aqui temos nossa função que cópia os pixels sendo transmitidos. A função assume que a textura está bloqueada e que os pixels são de uma imagem do mesmo tamanho da textura.

            //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 );
                //Copy frame from buffer
                gStreamingTexture.lockTexture();
                gStreamingTexture.copyPixels( gDataStream.getBuffer() );
                gStreamingTexture.unlockTexture();
                //Render frame
                gStreamingTexture.render( ( SCREEN_WIDTH - gStreamingTexture.getWidth() ) / 2, ( SCREEN_HEIGHT - gStreamingTexture.getHeight() ) / 2 );
                //Update screen
                SDL_RenderPresent( gRenderer );
            }

Na renderização feito no loop principal, bloqueamos nossa transmissão de textura, copiamos os pixels da transmissão e então desbloqueamos a textura. Com a nossa textura guardando a imagem mais recente da transmissão, renderizamos a imagem na tela. Quando lidamos com  APIs de decodificação, as coisas podem ficar mais difíceis, pois talvez teríamos que converter de um formato para outro, mas aqui tudo que temos que fazer é ler os dados dos pixels e copia-los para a tela.

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