Ponto V!

Home Java Java 2D Trabalhando com Imagens
Vinícius Godoy de Mendonça
Trabalhando com ImagensImprimir
Escrito por Vinícius Godoy de Mendonça

Nos artigos passados, vimos como desenhar imagens através de comandos de pintura. Entretanto, geralmente, não é o programador que desenha imagens, e sim, um artista num software de pintura qualquer. Nesse artigo, vamos ver como trabalhar com essas imagens, gravadas em disco.

Carregando imagens

O primeiro passo para usar qualquer imagem, obviamente, é carrega-las do disco. O Java oferece duas maneiras de se fazer isso, através da carga indireta, assíncrona e através da carga direta, síncrona.

Carga assíncrona

Com a carga assíncrona, comandamos o Java para que carregue uma imagem. Uma thread secundária é disparada para realizar esse trabalho, fazendo com que o método de leitura retorne imediatamente. Não poderemos usar a imagem logo em seguida, pois ela pode não estar na memória. Para sabermos o momento em que ela foi completamente carregada, temos que registrar um ImageObserver, ou utilizar da interface MediaTracker. O código abaixo demonstra um exemplo:

//Disparamos a carga da imagem. O objeto Image representa a imagem sendo carregada.
//Esse método retorna imediatamente.
Image image = Toolkit.getDefaultTookit().getImage(fileName);
//Criamos um MediaTracker para aguardar a carga. O parâmetro this representa um componente, //que pode ser o frame ou o applet onde o tracker está. MediaTracker mt = new MediaTracker(this); //Registramos a imagem com id 0. mt.addImage(image, 0); //Esperamos ela carregar. Poderíamos registrar várias imagens, //e também chamar o método waitForAll(), para carregar todas. try { mt.waitForId(0); } catch (InterruptedException e) { }

Carga síncrona

Embora a carga assíncrona seja adequada para aplicações, sobretudo applets, ela não é adequada para jogos. Em quase 100% dos casos, iremos precisar ter certeza de que a imagem foi carregada, antes de exibi-la. Com a carga síncrona o java carregará uma imagem, e só retornará quando ela estiver integralmente carregada. Fazemos a carga síncrona através da classe ImageIO:

try {
   BufferedImage image = ImageIO.read(new File(fileName));
} catch (IOException e) {
}

Note que o tipo retornado agora é um BufferedImage, não simplesmente um Image. O BufferedImage é uma classe filha de Image que representa uma imagem carregada na memória. A grande diferença das duas é que o BufferedImage irá permitir a manipulação da imagem. Carregar imagens através da classe ImageIO é a forma recomendada pela Sun atualmente.

Desenhando a imagem

Você já viu nos artigos anteriores como trabalhar com a classe Graphics. Como você já deve ter deduzido, existem métodos por lá para desenhar a imagem. Uma das coisas mais interessantes, é que o Java não disponibiliza um, mas sim, dezenas de métodos para isso. Veremos dois dos mais importantes. Os outros, são derivações destes.

O primeiro, e mais simples, é quando queremos desenhar uma imagem num local específico da tela.

boolean Graphics.drawImage(Image img, 
                           int x, int y,
                           ImageObserver observer);

Simplesmente passamos a imagem, duas coordenadas e, opcionalmente, um ImageObserver. Essa interface é útil para quando usamos carga assíncrona, e não carregamos a imagem através do media tracker. O método draw() irá esperar até que a imagem esteja desenhada, para então disparar um evento nos avisando que a pintura terminou. Essa técnica é adequada em aplicações, onde esperar pela carga da imagem no momento da pintura não é um problema. Não é o caso dos jogos. Como já teremos carregado nossa imagem com um MediaTracker, ou teremos usado o ImageIO, podemos passar null nesse parâmetro.

O segundo método de pintura, permite-nos desenhar apenas uma parte da imagem:

boolean Graphics.drawImage(Image img,
                           int dstx1, int dsty1, int dstx2, int dsty2,
                           int srcx1, int srcy1, int srcx2, int srcy2,
                           ImageObserver observer);

Nesse método, fornecemos a imagem, 4 coordenadas no destino (dst) e 4 na origem (src). O Java irá pintar só a porção determinada por essas coordenadas, fazendo a escala para que ela encaixe no destino. Por exemplo, se as coordenadas de origem forem (0,0)-(10,10) e a de destino forem (0,0)-(25,25), a imagem será redimensionada. Esse método é útil para criarmos, por exemplo, uma única imagem com várias partes de uma animação, e desenharmos apenas uma parte por vez no programa.

Desenhando sobre uma imagem

Um dos recursos mais bacanas da API do Java 2D, é que é extremamente fácil desenhar sobre uma imagem. Todo BufferedImage é também uma área de pintura. Podemos criar um BufferedImage em branco e desenhar sobre ele, ou mesmo desenhar sobre uma imagem carregada. Para isso, usamos o método createGraphics(), que retorna um objeto do tipo Graphics2D. A partir daí, basta desenhar na imagem como faríamos na tela.

No artigo anterior, comentei que o desenhar diretamente no Java 2D, sobretudo quando se usa antialiasing e GradientPaint pode ser uma tarefa bastante custosa. Agora, e se fizéssemos esses comandos de desenho uma única vez, e guardássemos o resultado numa BufferedImage? Os desenhos seguintes seriam feitos diretamente através da imagem, sem envolver cálculos de antialising, escala, rotação, preenchimento ou texturas. De fato, fazer isso é possível, e até bastante fácil:

//Criamos uma imagem de 80x100 pixels
BufferedImage ghost = new BufferedImage(
 80, 100, BufferedImage.TYPE_INT_ARGB);

//Obtemos o contexto gráfico dessa imagem
Graphics2D g2d = ghost.createGraphics();

//Desenhamos nela o fantasma do artigo anterior :)
new Ghost().draw(g2d);

//Liberamos o contexto.
g2d.dispose();

Após esse código, poderíamos simplesmente desenhar a imagem definida na variável ghost, quantas vezes quiséssemos. Isso permite manter a qualidade do desenho ao mesmo tempo que mantemos a velocidade de pintura na hora do jogo propriamente dito.

Salvando a imagem no disco

O método ImageIO.write é responsável por salvar a imagem no disco. Devemos passar para o método a imagem a ser salva, o formato do arquivo e o nome. O Java se encarregará de fazer a conversão do formato da BufferedImage para o formato de saída, podendo haver perdas nesse processo. Uma imagem com transparência, por exemplo, poderá ficar com fundo branco se for gravada num formato que não suporte transparência. Da mesma forma, imagens do tipo JPG geralmente são geradas com uma taxa de compactação alta e podem apresentar distorções.

Gravar imagens no disco pode ser uma técnica interessante para criar caches, e evitar penalizar o jogador com operações caras como redimensionamento e desenho toda vez que o jogo é carregado. A assinatura do método é a seguinte:

static boolean ImageIO.write(RenderedImage im,
                             String formatName,
                             File output) throws IOException

A classe BufferedImage implementa a interface RenderedImage. O boolean retornado pelo método indica se um writer para aquele formato foi encontrado ou não. Para os formatos descritos abaixo, o retorno true é garantido. Outros formatos podem ser instalados através da APIs externas.

Formatos de imagem suportados

O Java suporta uma série de formatos de imagem. Abaixo, uma breve descrição dos principais:

  • Bitmaps (BMP): É o formato, de longe, mais rápido de ser carregado e pintado. Porém, não suporta transparência e por não conter compactação, gera arquivos de imagem de tamanho grande.
  • JPG: Possui compactação do tipo lossy, ou seja, pode haver perda de qualidade se a compactação for muito agressiva. Por outro lado, mesmo numa taxa baixa de compactação já apresenta um tamanho de arquivo consideravelmente menor que o bitmap. Também não suporta transparência.
  • GIF: Suporta transparência e vários tipos de compactação. Também suporta que várias imagens sejam gravadas no mesmo arquivo, e sejam exibidas na forma de animação em browsers.
  • PNG: Equivalente ao GIF, mas a compactação geralmente é sem perdas. Como é um formato livre, foi extremamente otimizado na API do Java, e é o formato com transparência recomendado pela Sun.

Concluindo

Nesse artigo, vimos de maneira bem prática e direta como carregar e manipular imagens em Java. Só com o que foi visto, já é possível desenhar qualquer cena do jogo. Sugiro que você tente fazer um programa que carregue algumas imagens, para exercitar o que foi visto.


Comentários (29)
  • Bruno Daniel Marinho  - Melhor artigo ^^
    avatar

    Muito legal o artigo gostei especialmente do metodo Graphics.drawImage sobre carregado para pintar apenas uma parte da imagen estou fazendo algums testes aqui com uma imagen so agente pode desenhar um cenario todo neh?

    SHow!

  • Vinícius Godoy de Mendonça
    avatar

    Sim. Apesar de que várias imagens são comumente usadas para dar o efeito de paralaxe (isto é, que o fundo mova-se mais lentamente em relação à frente).

  • Eduardo Skrepnek Tosin  - Excelente
    avatar

    Achei excelente seu artigo. Me sanou várias dúvidas quanto o carregamento de imagens por sprites.
    Gostaria de saber se tu tens como me ajudar com o flicker e doublebuffering no java, pois não entendi como ele realmente funciona num applet.

    Obrigado.

  • Vinícius Godoy de Mendonça  - Flickering e Buffer
    avatar

    O próximo artigo vai lidar com esse assunto. Você precisa de duas coisas para eliminar o flickering:

    a) Um game loop consistente;
    b) Double buffering.

    Por incrível que pareça, a maior parte dos programadores Java se perde no ponto a), não no b). Você já leu o artigo "animação baseada em tempo", aqui do portal? Ele pode te ajudar.

  • Thiago
    avatar

    Parabéns pelo artigo Vini! Ficou muito bom

  • seudito  - Modo assíncrono
    avatar

    Olá Vini,

    Achei interessante o artigo, parabéns.

    Você acredita ser possível utilizar o modo assíncrono para redimensionar imagens em Java, e em caso afirmativo, desta forma pode ser mais rápido?

    Estou mechendo em uma aplicação onde redimensiono as imagens carregando-as de forma síncrona, e então, lendo o artigo me surgiu esta dúvida. Obrigado!

  • Vinícius Godoy de Mendonça
    avatar

    Você só poderá redimensiona-las depois da imagem completamente carregada. Nesse caso, você deve usar o último parâmetro, que se refere ao image listener. Ele recebe uma notificação quando a carga da imagem está completa, e então, você faz o redimensionamento sob demanda.

  • Anônimo  - Informação
    avatar

    Bom dia Vinicius, achei interessante este seu arquivo.
    Sou iniciante e gostaria de saber se tenho como fazer um adm de templates em java.
    Nesta templates o adm poderá trocar as imagens de promoções da parte inicial do site.. teria como faer isto em java?

  • Vinícius Godoy de Mendonça
    avatar

    Tem sim. Esse portal é mais relacionado a jogos. Como sua dúvida é mais relacionada ao desenvolvimento de uma aplicação web, surgiro que a poste no GUJ.

  • Xilon
    avatar

    Amigo, não tem como voce colocar um exemplo completo, de desde carregar a imagem do disco(indicando até o diretorio q deve estar a imagem), colocá-la em um buffered image e depois desenhá-la na tela, que se eu copiar e colar o código ele funcione aqui direto.......estou desesperado......to a mais de 2 dias tentando criar uma janelinha com uma imagem dentro e não consigo....em todos os tutoriais que eu acho pela net me parecem incompletos e confusos......

  • Mateus  - Duas perguntas
    avatar

    Tem como eu rotacionar uma imagem ou espelha-la?

  • Vinícius Godoy de Mendonça
    avatar

    Tem sim. Para espelhar, basta fornecer os dados da imagem ao contrário (dizer que ela começa na largura e termina em 0).

    Para rotacionar, você precisa alterar a transformação do Graphics2D.
    Veja um exemplo neste post do GUJ.

    Lembre-se que o ângulo é em radianos. Se você quiser fornecer em graus, use o método Math.toRadians().

  • adailton  - carregar gif
    avatar

    cara tem como carregar uma imagem no java no formato gif ou algo assim?

  • Vinícius Godoy de Mendonça
    avatar

    Tem sim, é só seguir o que está escrito nesse artigo. ;)

  • Leandro
    avatar

    parabéns Vinícius, ja add a pagina nos favoritos...

    Tenho um JPanel com o layout GridLayout(2,5)
    e nesse tenho que adcionar até 10 imagens com tamanhos específicos,
    e em determinados momentos preciso trocar essas 10 imagens de 1 em 1 segundo (porém preciso mostrar as imagens)
    tenho aproximadamente umas 10.000 imagens qual é a melhor maneira de se fazer esse processo ?

    obs.: O JFrame fica em fullscreen.
    idéia semelhante: http://online.wsj.com/public/resources/documents/EndOfYearSlideshow-Co olIris.html

  • Vinícius Godoy de Mendonça
    avatar

    Eu implementaria meu próprio JPanel com essa capacidade, sem recorrer ao GridLayout. Basta usar os conceitos já apresentados aqui no portal (entenda bem o Java2D e o GameLoop)

  • Leandro  - re:
    avatar
    Vinícius Godoy de Mendonça Escreveu:
    Eu implementaria meu próprio JPanel com essa capacidade, sem recorrer ao GridLayout. Basta usar os conceitos já apresentados aqui no portal (entenda bem o Java2D e o GameLoop)
    Vinícius Godoy de Mendonça Escreveu:
    Eu implementaria meu próprio JPanel com essa capacidade, sem recorrer ao GridLayout. Basta usar os conceitos já apresentados aqui no portal (entenda bem o Java2D e o GameLoop)

    Poderia mostrar um exemplo ?

    Obrigado.

  • Vinícius Godoy de Mendonça
    avatar

    Esse exemplo não faz exatamente o que você quer, mas já roda o programa num loop:
    http://www.guj.com.br/java/128713-jframe-com-imagem-em-fade#1165351

  • Leandro
    avatar

    Valeu Vinícius

  • Davi  - IloveVINI
    avatar

    vinivnivnivni.. não é a primeira vez q vc me salva!
    bom trabalho!!

  • Vinícius Godoy de Mendonça
    avatar

    Valeu.

  • allan
    avatar

    Olá, olha digamos que eu tenha uma imagem que corresponde ao meu herói como é que eu faço para saber se ela colidiu com outra imagem que seria a do inimigo? Posso colocar a imagem dentro de um retângulo?

  • Vinícius Godoy de Mendonça
    avatar

    Use o método intersects da classe Rectangle2D. O rectangle irá representar o bouding box da imagem (se seu objeto for redondo, talvez seja melhor usar o Ellipse2D ao invés do Rectangle).

  • allan
    avatar

    como eu faço para saber se a imagem do inimigo colidiu com a imagem do inimigo?

  • Vinícius Godoy de Mendonça
    avatar

    Crie um Retangle2D com as coordenadas e dimensões da imagem do herói. Crie outro Rectangle2D com as coordenadas e dimensões da imagem do inimigo. E então use o método intersects nesses dois retângulos.

  • washington  - Redimensionar imagem
    avatar

    Preciso ajuda!!!
    Não estou conseguindo redimensionar a imagem em uma jlabel.
    consigo traze-la, mas fica com tamanho desproporcional à jlabel.
    segue o código abaixo.

    Obrigado;

    private void jb_foto1ActionPerformed(java.awt.event.ActionEvent evt) {

    try

    {
    JFileChooser busca_foto = new JFileChooser();
    busca_foto.setCurrentDirectory(new File("/pcio/imagens/";));
    busca_foto.setDialogTitle("Carregar imagem da OAEs";);
    busca_foto.showOpenDialog(this);
    String foto = ""+busca_foto.getSelectedFile().getName();
    tf_foto1.setText(foto);
    jlb_Foto1.setIcon(new ImageIcon("/pcio/imagens/"+tf_foto1.getText()));
    }
    catch(Exception erro)
    {
    JOptionPane.showMessageDialog(null,"Nao foi possível carregar a foto.";);
    }


    }

  • wilson  - Recuperar a imagem sobreposta
    avatar

    No item :Desenhando sobre uma imagem,
    no final, como eu faço pra recuperar esse g2d, preciso usá-lo, pra salvar no disco, por exemplo.

    //Liberamos o contexto.
    g2d.dispose();

  • Vinícius Godoy de Mendonça
    avatar

    O g2d é apenas a "caneta", não a imagem. O que você quer salvar está no BufferedImage, no caso do exemplo, ghost.

    Para salvar no disco, você faria isso após o dispose():
    ImageIO.write(ghost, "png", new File("C:\ghost.png";));

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