Ponto V!

Home OpenGL Carregando imagens com a SDL
Vinícius Godoy de Mendonça
Carregando imagens com a SDLImprimir
Escrito por Vinícius Godoy de Mendonça

No artigo anterior finalmente terminamos os conceitos básicos de luzes. Você deve ter ficado impressionado com os resultados e, certamente, já pode até ter feito algo para impressionar sua família e amigos. Entretanto, a menos que sua ambição seja fazer um jogo de sinuca, é bom que as formas que desenhamos na tela tenham texturas.

Nesse artigo, iremos ver como executar o primeiro passo na criação de imagens com texturas, que é ler uma imagem do disco. Como a OpenGL não fornece nenhuma função nativa para isso, iremos usar a SDL para carregar a imagem de maneira simples e multi-plataforma e analisar suas propriedades.

Carregando imagens do disco

Se você se lembrar, quando criamos nossa janela SDL, eu comentei que qualquer coisa que possa ser desenhada na tela com a SDL era uma superfície e, portanto, uma SDL_Surface. Com imagens não podia ser diferente.

Carregamos uma imagem na SDL através da função IMG_Load. Essa função não está entre os pacotes básicos da SDL e pertence a extensão SDL_image. Se você não configurou ainda seu code::blocks para usar essa função, volte ao artigo sobre a criação do ambiente para jogos e o faça isso agora. A opção do pacote básico é a função SDL_LoadBMP mas. como o nome já diz, ela está restrita a imagens sem compactação no formato bitmap.

Carregar a imagem em si é uma tarefa muito simples. Basta usarmos a função passando como parâmetro o nome da imagem a ser carregada. Testamos então se o retorno não é nulo, o que indica uma falha no carregamento. Se não for, nossa imagem já foi corretamente carregada. Por exemplo:

SDL_Surface* imagem = SDL_Image("cake.png");
if (imagem == NULL)
{
   std::cout << "Sorry, no cake for you." << std::endl;
   return;
}

//Chama uma função para desenhar o bolo
drawCake(cake);

Existem outras formas de carregar a imagem, como nos casos onde ela está dentro de um zip, por exemplo, mas não nos aprofundaremos nesse tema aqui. Caso queira ler a respeito, confira esse artigo no Vertex Buffer.

Finalmente, após usarmos a imagem, usamos o comando SDL_FreeSurface para liberá-la da memória.

Identificando as propriedades da imagem

Como a OpenGL não sabe como a imagem foi carregada, teremos que informar para ela detalhes sobre o formato da imagem. A forma mais fácil de fazer isso seria convencionar que todas as imagens carregadas tem o formato ARGB ou RGB, por exemplo, e criar funções para carregar desses dois formatos.

Agora, o ideal seria se pudéssemos identificar as propriedades da imagem em tempo de execução, para dinamicamente fornecer esses dados para a OpenGL. Felizmente a SDL nos dá esse mecanismo e o estudaremos agora.

Toda SDL_Surface tem uma propriedade chamada format, do tipo SDL_PixelFormat. Ela nada mais é do que uma struct com diversos campos interessantes como:

  1. palette: Dados da paleta de cor, caso a imagem use palhetas, como o GIF, do tipo SDL_Palette;
  2. BitsPerPixel: Quantidade de bits num único pixel de cor. Se só os formatos RGB e ARGB existirem em sua aplicação essa informação já será suficiente para diferenciar as duas imagens, já que um usa 24 bits por pixel e o outro 32;
  3. BytesPerPixel: Equivalente ao de cima, mas em bytes;
  4. [RGBA]mask: Quatro propriedades que definem a máscara de como as informações de cor estão dispostas. Falaremos mais sobre esse campo abaixo;
  5. [RGBA]shift: Deslocamento para direita que os bits devem sofrer para isolar apenas o valor dessa propriedade em um byte.
  6. [RGBA]loss: Deslocamento para a esquerda que os bits devem sofrer devido a perda de precisão;
  7. ColorKey: Usado em formatos RGB que suportam transparência. Nesse caso, a cor indicada por esse valor é considerada transparente.
  8. Alfa: Quantidade geral do componente alfa na imagem;

Se quisermos identificar realmente a ordem dos bits em nossa imagem, teremos que analisar as propriedades [RGBA]mask. Cada propriedade dessas dá um número que, aplicado & sobre o pixel, isolará apenas os bits correspondentes ao componente de cor desejado. Os escovadores de bits de plantão já sacaram, mas se você ainda estiver confuso, vamos a um exemplo. Suponha que você leu a imagem, já usou a propriedade BytesPerPixel e descobriu que sua imagem tem 4 bytes (logo, existe um componente alfa). Um pixel qualquer em sua imagem, por exemplo, poderia ter esse valor:

10011101 10111101 11011101 11111111

Como descobrir só o valor do componente verde? O primeiro passo é olhar a propriedade Gmask. O valor dela poderia ser o número 16711680 (0xFF0000). Isso não é muito significativo em decimal, mas vejamos em binário:

00000000 11111111 00000000 00000000

Agora, veja o que ocorre se aplicarmos esse valor ao número original. Obtemos:

10011101 10111101 11011101 11111111 &
00000000 11111111 00000000 00000000 =
00000000 10111101 00000000 00000000

Notou? Todos os componentes de cor que não são verdes forem zerados após a operação lógica E em cada bit. E se quiséssemos isolar o valor somente daquele componente? Bem, teríamos que desloca-lo 16 bits para direita. Esse valor é exatamente o que está armazenado na propriedade Gshift. Ou seja, pixel >> Gshift nos daria:

00000000 00000000 00000000 10111101

Ok, mas e a propriedade Gloss? O que pode ocorrer é que nosso componente verde poderia ter sido compactado. Uma maneira comum de se fazer isso é descartando o bit menos significativo, deixando o tipo com 7 bits. Nesse caso, GShift conteria o valor 17, deslocando um bit a mais para direita e o componente Gloss teria o valor 1, indicando que devemos voltar esse bit a mais. Observe a operação bit a bit nesse caso:

10011101 10111101 11011101 11111111 & Gmask =
00000000 10111101 00000000 00000000 >> Gshift (17 bits) =
00000000 00000000 00000000 01011110 << Gloss (1 bit) =
00000000 00000000 00000000 10111100

Note que o último bit, menos significativo, foi descartado. Esquemas de compactação mais agressivos podem até mesmo reduzir 2 bits, o que é raro, mas possível.

Vejamos essa mesmas operações para obter o valor do componente vermelho do primeiro pixel de uma imagem qualquer:

//Lemos a imagem do disco
SDL_Surface* imagem = SDL_Image("cake.png");
if (imagem == NULL)
{
   std::cout << "Sorry, no cake for you." << std::endl;
   return;
}
SDL_PixelFormat *fmt = imagem->format;

//Obtém os pixels da imagem, veja o próximo tópico
SDL_LockSurface(surface);
Uint32* pixel=*((Uint32*)surface->pixels);
SDL_UnlockSurface(surface);

//Já estamos apontando para o primeiro pixel,
//vamos ler seu componente vermelho
Uint32 temp = pixel & fmt->Rmask; //Separamos só o vermelho
temp=temp >> fmt->Rshift;//Deslocamos os bits para direita
temp=temp << fmt->Rloss; //Voltamos os bits para esquerda
std::cout << "O valor no vermelho é:";
std::cout << static_cast(temp);

Aquele cast não era realmente necessário, mas deixei-o para demonstrar que o valor resultante é mesmo um único byte, com o valor do componente de cor.

Manipulação da imagem bit-a-bit

Um dos grandes poderes da SDL é que ela permite a manipulação bit-a-bit da superfície. Poderíamos usar isso para gerar imagens dinamicamente, copiar trechos de imagem uns sobre os outros, ou mesmo aplicar efeitos antes de gerar nossa textura.

Para isso toda SDL_Surface tem a propriedade pixels, que retorna um conjunto de bytes contendo os pixels no formato descrito anteriormente. Esse array pode ser alterado, causando a mudança imediata da imagem.

Para poder utiliza-lo, entretanto, algumas superfícies precisam estar trancadas (locked). Fazemos isso através da função SDL_LockSurface, que pode retornar 0 em caso de sucesso ou -1 em caso de falha. Esse comando prepara a superfície para manipulação direta de bits.  O comando oposto, SDL_UnlockSurface, também deve ser usado, quando terminarmos de manipular a imagem.

Não exploraremos muito essa funcionalidade aqui, mas exemplos de seu uso podem ser encontrados no artigo SDL em processamento de imagens, do Diogo RBG.

Concluindo

Nesse artigo vimos como carregar imagens de maneira multi-plataforma usando a SDL. Essa é uma funcionalidade importante pois a OpenGL não possui método nativo para carga de figuras. Também vimos como analisar o tipo da imagem, o que nos permitirá criar uma classe inteligente para carga de texturas na OpenGL.

Veja mais em


Comentários (17)
  • Mateus  - copiar parte de uma imagem
    avatar

    Gostaria de saber como faço para copiar parte de uma SDL_surface
    carregada pelo SDL_Image, para um nova criada pelo SDL_CreateRGBSurface.
    Eu estou tentando o SDL_BlitSurface,
    mas não está dando certo, não sei se é por terem especificações
    diferentes(format, flags..), embora não tenha encontrado ainda
    nenhuma diferença. :(
    Alguma sugestão?

    parte do meu código em C:

    char *url="teste.png";
    int largura=32, altura=48;

    SDL_Surface* img = SDL_Image(url);

    SDL_Surface *destinoImg=SDL_CreateRGBSurface(img->flags, largura, altura, img->format->BitsPerPixel, img->format->Rmask, img->format->Gmask, img->format->Bmask, img->format->Amask);

    SDL_Rect origemRet;

    origemRet.w=largura; origemRet.h=altura;

    origemRet.x=origemRet.y=0;
    SDL_BlitSurface(img, &origemRet, destinoImg, NULL);

    Obrigado.

  • Mateus  - copiar parte de uma imagem
    avatar

    Acho que encontrei uma resposta:
    acrescentei
    SDL_SetAlpha(img, 0, img->format->alpha);
    antes do SDL_BlitSurface, e agora está funcionando! :lol:
    isso tem algum fundamento?

    Obs: antes de fazer esta alteração a
    imagem estava completamente transparente, não preta.

    Obrigado.

  • Vinícius Godoy de Mendonça
    avatar

    Tem sim, sua imagem poderia estar sendo encarada como completamente transparente.

  • Tales
    avatar

    Esse método até funciona, mas não é muito recomendável. Quando preciso copiar uma imagem, uso o SDL_ConvertSurface que cria a imagem e já copia o conteúdo. Dá menos bug.

  • Bruno  - Limpar tela
    avatar

    Bem, eu estava fazendo uns teste com imagens(usando o dev-c++), coisas como carregar, gerenciar cliques e movimentos, ae em um dos eventos de clique, surgiu uma dúvida, como eu faço para limpar a tela? apagar todas as imagens da tela e talz, já tentei usar SDL_FreeSurface() mas não funcionou, se pudessem dar uma olhada no código e me ajudar eu agradeço : http://notepad.cc/share/uMC 5T2rIMf

    Tem outro problema também que todo hora que fecho o programa, aparece uma mensagem dizendo que houve um erro na execução.

  • Vinícius Godoy de Mendonça
    avatar

    Use o comando:
    SDL_FillRect(SDL_GetVideoSurface(), NULL, 0);

  • Paulo Márcio  - Erro ao carregar imagem
    avatar

    Não estou conseguindo carregar uma imagem, não entendo o que faço de errado no código:

    Aqui o método iniciar da classe Janela:

    Código:

    this->largura = largura;
    this->altura = altura;
    this->bpp = bpp;
    this->telacheia = telacheia;

    ///Configurando as flags para criar a janela
    int f = SDL_HWPALETTE;

    if(telacheia)
    f |= SDL_FULLSCREEN;

    const SDL_VideoInfo* video = SDL_GetVideoInfo();

    if(video->hw_available)
    f |= SDL_HWSURFACE;
    else
    f |= SDL_SWSURFACE;

    if(video->blit_hw)
    f |= SDL_HWACCEL;
    ///

    janela = SDL_SetVideoMode(largura, altura, bpp, f);

    if(janela == NULL)
    {
    erro = SDL_GetError();
    throw (Erro("Não foi possível criar a janela.";));
    }

    SDL_WM_SetCaption(titulo.c_str(), NULL);

    aberta = true;

    ///Loop Principal que mantém a janela aberta
    while(aberta)
    {
    Logica();
    Desenhar();
    }

    Os métodos implementados na classe filha

    Código:

    void Logica(){Eventos();}
    void Desenhar()
    {
    SDL_FillRect(janela, NULL, SDL_MapRGB(janela->format, 255, 0, 255));
    imagem->Desenhar();

    SDL_Flip(janela);
    }

    Agora o construtor da clase Imagem e o método desenhar:

    Código:

    int Imagem::carregar_imagem(std::string imagem)
    {
    SDL_Surface* temp = SDL_LoadBMP(imagem.c_str());

    if(temp == NULL)
    return -1;

    this->imagem = temp;

    this->imagem = SDL_DisplayFormat(temp);

    //SDL_SetColorKey(this->imagem, SDL_SRCCOLORKEY, SDL_MapRGB(this->imagem->format, 255, 0, 255));
    SDL_FreeSurface(temp);

    return 0;
    }

    Imagem::Imagem(SDL_Surface* surface, int x, int y,std::string imagem)
    {
    if(carregar_imagem(imagem) == -1)
    throw(Erro("Não foi possivel carregar a imagem " +imagem+ ".";));

    this->surface = surface;
    area.x = x;
    area.y = y;
    };
    Código:

    void Imagem::Desenhar()
    {
    SDL_BlitSurface(imagem, NULL, surface, &area);
    }

    Não consigo encontrar o erro, ele não desenha nada na tela.

    Utilizei a flag SDL_INIT_EVERYTHING, SDL_Init() é chamada no construtor da classe Janela.

  • Paulo Márcio  - Imagem carregada
    avatar

    Entendi o erro, Eu tentei usar a função SDL_DisplayFormat antes mesmo de criar a janela, inverti a ordem e agora está carregando.

  • Paulo Márcio  - Video
    avatar

    Existe alguma forma de reproduzir vídeos com a SDL? ou alguma API que possa ser usada em conjunto ou eu tenho que usar as imagens que seriam do video para criar o vídeo no própio programa?

  • Vinícius Godoy  - Vídeo
    avatar

    Você pode dar uma olhada na lib do VCL: http://wiki.videolan.org/LibVLC_SampleCode_SDL

  • Silas Brasil  - SDL e arquivos *.vol (Volumétrico)
    avatar

    Galera com SDL posso carregar arquivo *.vol e aplicar as transformações geométricas nele?

  • ViniGodoy
    avatar

    Se vc souber como ler esse arquivo, nada impede vc de renderiza-lo com SDL + OpenGL. Mas a SDL em si não possui por padrão um leitor desse formato.

  • Silas Brasil
    avatar

    Ok, obrigado!

  • fred  - Carregando a imagem
    avatar

    como eu carrego a imagem no lugar da função "RotatingTriangle"? Utilizei o seguinte código abaixo mas ele da erro...

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);


    SDL_Surface *image;
    SDL_Surface *temp;

    temp = SDL_LoadBMP("/test.bmp";);

    image = SDL_DisplayFormat(temp);
    SDL_FreeSurface(temp);

    SDL_Rect src, dest;

    src.x = 0;
    src.y = 0;
    src.w = image->w;
    src.h = image->h;

    dest.x = 100;
    dest.y = 100;
    dest.w = image->w;
    dest.h = image->h;

    SDL_BlitSurface(image, &src, GameWindow().window, &dest);

    a mensagem é que GameWindow é privada.

  • Anônimo
    avatar

    Você precisa passar uma referencia para o valor que você precisa para o blit. Não tem sentido esse código, pois ao fazer GameWindow() você esta invocando o construtor de GameWindow() e criando um objeto temporário.

  • Anônimo
    avatar

    E se você esta usando OpenGL, não tem sentido também usar blit.

  • fredy  - Carregando a Imagem
    avatar

    Respondendo a sua pergunta, estou usando uma janela SDL. O Código funcionou depois que fiz as alterações abaixo. Fico no aguardo de dicas. E sim agora entendo melhor o gamewindow.

    // INICIO DA ROTINA QUE CARREGA IMAGEM (FUNCIONANDO)
    SDL_Surface *image;
    SDL_Surface *temp;
    SDL_Surface *screen; // LINHA ACRESCENTADA
    screen = SDL_SetVideoMode(640, 480, 0, 0);

    // SELECIONANDO AS IMAGENS DO TATU
    char diretorioImg[100] = "Imagens/Personagens/tatua/";

    strcat(diretorioImg, AcaoDoTatu); //PassosDoTatu[0]);
    // FIM DE SELEÇÃO DA IMAGEM DO TATU

    temp = SDL_LoadBMP(diretorioImg); //"Imagens/Personagens/tatua/"+PassosDoTatu[1]); //parado.bmp";);


    image = SDL_DisplayFormat(temp);
    SDL_FreeSurface(temp);

    SDL_Rect src, dest;

    src.x = 0;
    src.y = 0;
    src.w = image->w;
    src.h = image->h;

    dest.x = DirecaoDoMovimento; //0; // 100
    dest.y = 0; // 100
    dest.w = image->w;
    dest.h = image->h;

    SDL_BlitSurface(image, &src, screen , &dest); //SDL_GetVideoSurface()
    SDL_UpdateRect(screen, 0, 0, 0, 0); // LINHA ACRESCENTADA
    // FIM DA ROTINA QUE CARREGA IMAGEM

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter