Como rotacionar uma imagem

Olá, eu gostaria de saber como eu posso rotacionar uma imagem, a imagem vai ficar em um ponto e quando mover o mouse, a imagem gire sempre companhando o cursor do mouse. Para poderem visualizar melhor, o código que adiciona a imagem no JFrame, é o que está em um tópico que fiz a cerca de um mês, este aqui:

tópico

É muito difícil fazer isso usando JLabel, recomendo que use JComponent

class Imagem extends JComponent {
  static final int ESPACO_INTERNO = 45; // necessário um espaço para girar a imagem, pois os componentes swing são todos retangulares
  final BufferedImage image;

  // double angulo;

  public Imagem(File arquivo) {
    image = ImageIO.read(arquivo);
    Dimension size = new Dimension(image.getWidth()+ESPACO_INTERNO*2, image.getHeight()+ESPACO_INTERNO*2);
    setPreferredSize(dimension);
    setMinimumSize(dimension);
  }
  public void paintComponent(Graphics g) {
    if (g != null) {
      Graphics2D g2d = (Graphics2D)g;
      // g2d.rotate(angulo, ESPACO_INTERNO + image.getWidth() / 2, ESPACO_INTERNO + image.getHeight() / 2);
      g2d.drawImage(image, ESPACO_INTERNO, ESPACO_INTERNO, null);
    }
  }
 // void setAngulo(double angulo) {
 //   this.angulo = ângulo;
 //   repaint(); // atualiza o component
 // }
}

Para girar a imagem, deixei em comentário porque não sei se vai funcionar, acho que o ângulo é em radianos.

Bem, eu ainda to aprendendo sobres os recursos do java, então eu ainda não sei como funciona o JComponent e não entendi muito bem ainda como trabalhar com Graphics e bufferedImage.

E pode me tirar uma duvida, eu não entendi bem o que seria essa variavel “espaco_interno”.

Se girar a seguinte imagem:

image

ficará assim, cortada nas pontas:

image

então o JComponent precisa ter um espaço maior para poder ficar assim:

image

Acho que o calculo desse espaço seria:

 maiorLadoDaImagem * raizQuadrada(2) / 2

O BufferedImage é um Image capaz de manipular os dados da imagem, Image armazena os dados de uma imagem para exibição. Basicamente o Image é uma imagem não editável e o BufferedImage é uma imagem editável.

O Graphics e o Graphics2D são ferramentas para desenhar. Todos os comonentes Swing tem um método paint que desenha a imagem deles. Sobrescrever o método permite a manipulação dessa imagem. O BufferedImage também tem o método createGraphics que permite a edição.

No link abaixo explica como usar o graphics
http://www.pontov.com.br/site/index.php/java/48-java2d


O JComponent é a base para todos os tipos de componentes do Swing, também usada para criar componentes, portanto é a implementação mais simples que se tem. Poderia usar o JPanel se quiser.

1 curtida

ok, muito obg por me explicar, me esclareceu bastante coisa sobre o que eu não entendia. Vou começar a estudar mais afundo usando o link que vc me passou.

Essa classe que é extendida pro JComponente, para poder modificar a imagem que será inserida no JFrame, deve ser adicionado ao JFrame com a funcao add()? Da mesma forma que se faz com JLabel, JPane e entre outros…

Sim, exatamente igual.

O File seria tipo isso:

File diretorio = new File(getClass().getResource(nomeDaImagem.png));

ou é outra forma? não conheço muito bem essa classe, ja vi algumas vezes serem usados, li no site da Oracle.

Tem varias formas, essa é uma.

Vc estava usando Stream, então poderia fazer:

InputStream stream = getClass().getResourceAsStream("/imagens/Habitante_Zul.png");
image = ImageIO.read(stream);

Cara, eu criei a classe imagem, coloquei os metodos e as variaveis, criei uma classe que é extendida do JFrame e adicionei a classe imagem, agr o que eu preciso fazer pra que quando eu inicie o JFrame a imagem apareca ou só isso ja faz com que ela esteja aparecendo no JFrame?

A imagem deveria aparecer, se não apareceu é porque falta algo. Talvez ela não gire no momento.

Talvez eu esteja fazendo errado kkkk, vou postar aqui tudo e o que estou usando, menos a classe que está chamando pq não tem nada, ela só faz a chamada.

Essa é a classe da janela que vou adicionar a imagem, ela só tem isso:

package testes;

import java.awt.Dimension;
import java.io.IOException;
import javax.swing.JFrame;

public class JanelaImagens extends JFrame{
    Dimension dimensao;
    Imagens2D imagem;
    
    public JanelaImagens() throws IOException {
        dimensao = new Dimension(800,600);
        imagem = new Imagens2D("imagem_giratoria.png");
        setLayout(null);
        setPreferredSize(dimensao);
        setSize(getPreferredSize());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().add(imagem);
    }
}

Essa é a classe que iria girar a imagem, eu só não modifiquei nada pra girar ainda pra eu poder aprender devagar como se faz a implementação:

package testes;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.swing.JComponent;

public class Imagens2D extends JComponent {
    int espaco;
    BufferedImage image;
    double angulo;
    
    public Imagens2D(String diretorio) throws IOException {
        InputStream stream = getClass().getResourceAsStream(diretorio);
        image = ImageIO.read(stream);
        espaco = (int) (Math.max(image.getWidth(), image.getHeight())*Math.pow(2,0.5)/2);
        Dimension dimensoes = new Dimension(image.getWidth() + espaco*2, image.getHeight() + espaco*2);
        setPreferredSize(dimensoes);
        setMinimumSize(dimensoes);
    }
    
    @Override
    public void paintComponent(Graphics g) {
        if(g != null) {
            Graphics2D g2d = (Graphics2D)g;
            g2d.rotate(angulo, espaco + image.getWidth()/2, espaco + image.getHeight()/2);
            g2d.drawImage(image,espaco,espaco,null);
        }
    }
    
    public void setAngulo(double angulo) {
        this.angulo = angulo;
        repaint();
    }
}

por enquanto só tem isso, pra poder girar ela e fazer com que ele pinte se não me engano, eu deveria chamar os métodos paintComponent e setAngule toda vez que eu quise-se girar certo?

Com apenas ele não gera a imagem no JFrame, o que falta? a unica forma que aprendi a colocar uma imagem no JFrame foi da forma como está no link que eu citei nesse tópico, usando o JLabel.

Quase, é o método repaint que já está no setAngulo e não o paintComponent, portanto só chamar o setAngulo.

Não está funcionando pq vc está usando layout nulo, adicione o setSize no Imagens2D

    setPreferredSize(dimensoes);
    setMinimumSize(dimensoes);
    setSize(dimensoes);

ok, não posso colocar nulo, porém se eu não fizer isso, a imagem não se move caso eu queira fazer isso ou ela gera outros tipos de problema.Bom, pelo menos foi isso que presenciei nas outras vezes que eu tentei mover a imagem no JFrame usando o JLabel, a não ser que dessa forma não tenha esse problema, vou fazer alguns testes aqui.

O layout executa automaticamente o setSize, se o layout é nulo então precisa faze manualmente

A sim, entendo, como eu faço para pegar a posicao da imagem? eu pego a posicao do JComponent com getX() e getY()?
É ou eu não preciso? Pois eu tenho q definir um angulo em radianos, então eu preciso fazer com que ele gire conforme as coordenadas do mouse em relação ao centro da imagem certo?

Sim, essas são as posições relativas ao “contentPane” do JFrame, aí vc teria que pegar as coordenadas do mouse relativa ao “contentPane” do JFrame também. Um componente pode estar dentro de outro, então as posições relativas são sempre com o “parentComponent”.

O Swing não foi feito para fazer isso, por isso é complicado. O código abaixo é para TESTE:

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import java.awt.*;

public class Imagens2D extends JComponent {
    int espaco;
    BufferedImage image;
    double angulo;
    
    public Imagens2D() throws IOException {
        //InputStream stream = getClass().getResourceAsStream(diretorio);
        //image = ImageIO.read(stream);
        
        image = new BufferedImage(100,100, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0,0,100,100);
        g2d.setColor(Color.RED);
        g2d.fillRect(4,4,100 - 8,100 - 8);
        g2d.dispose();
        
        espaco = (int) (Math.max(image.getWidth(), image.getHeight())*Math.pow(2,0.5)/2);
        Dimension dimensoes = new Dimension(image.getWidth() + espaco*2, image.getHeight() + espaco*2);
        setPreferredSize(dimensoes);
        setMinimumSize(dimensoes);
        setSize(dimensoes);
    }
    
    @Override
    public void paintComponent(Graphics g) {
        if(g != null) {
            Graphics2D g2d = (Graphics2D)g;
            g2d.rotate(angulo, espaco + image.getWidth()/2, espaco + image.getHeight()/2);
            g2d.drawImage(image,espaco,espaco,null);
        }
    }
    
    public void setAngulo(double angulo) {
        this.angulo = angulo;
        repaint();
    }
}

import java.awt.Dimension;
import java.io.IOException;
import javax.swing.JFrame;
import java.awt.*;
import java.awt.event.*;

public class JanelaImagens extends JFrame{
    Dimension dimensao;
    Imagens2D imagem;
    
    public JanelaImagens() throws IOException {
        dimensao = new Dimension(800,600);
        imagem = new Imagens2D();
        
        long  eventMask = AWTEvent.MOUSE_MOTION_EVENT_MASK;
        Toolkit.getDefaultToolkit().addAWTEventListener(
            new AWTEventListener(){
                public void eventDispatched(AWTEvent event) {
                    if (!imagem.isVisible()) return;
                    MouseEvent mouseEvent = (MouseEvent) event;
                    int mouse_x = mouseEvent.getXOnScreen();
                    int mouse_y = mouseEvent.getYOnScreen();
                    int imagem_x = imagem.getLocationOnScreen().x;
                    int imagem_y = imagem.getLocationOnScreen().y;
                    imagem_x = imagem_x + imagem.getWidth() / 2; // desloca para o centro
                    imagem_y = imagem_y + imagem.getHeight() / 2; // desloca para o centro

                    double d = Point.distance(imagem_x, imagem_y, mouse_x, mouse_y);
                    if (mouse_x - imagem_x > 0)
                        imagem.setAngulo(Math.asin((mouse_y - imagem_y) / d));
                    else
                        imagem.setAngulo(Math.asin((imagem_y - mouse_y) / d));
                }
            }, eventMask);
        
        setLayout(null);
        setPreferredSize(dimensao);
        setSize(getPreferredSize());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        getContentPane().add(imagem);
        setVisible(true);
    }
    
}

No código usei a posição relativa ao Screen, pois ambas as posições, do mouse e da imagem, estão relativas a mesma coisa.

Pq do Toolkit e da variavel long? Eu não entendi muito bem o pq deles.

Geralmente em java2D é usado o Panel e não o JFrame, o melhor jeito de aprender a usar esses recursos é pegando jogos com código e vendo como funciona. Tem vários jogos simples com pouco código que ajuda muito a aprender a usar, depois é só substituir a imagem do jogo pela sua.

Nem eu sei direito a resposta. Os eventos do mouse são sobre os componentes, portanto para cada componente teria que colocar um MouseMotionListener para capturar o evento. Essa foi a forma que encontrei para capturar o evento criando apenas um único listener para que funcionasse sobre todos os componentes.

Sobre o long, usei pq estava no link onde achei sobre o assunto, acho que pode ser int:

A variável eventMask é um filtro, existem inúmeros eventos e somente o evento do mouse é necessário, observe que no artigo acima o filtro é diferente.

Como foi dito antes, swing não foi feito para ter efeitos e animações.

Se usasse JavaFx, seria muito mais simples.