[Resolvido]:Java2D: comportamento inesperado ao rolar JPanel depois de usar AffinneTransform

Título Anterior: Java2D: comportamento inesperado ao rolar JPanel depois de usar AffinneTransform (zoom)

Boa tarde a todos.

Estou com um projeto em Java2D e, dentre as coisas que estou tentando fazer, está um mecanismo para zoom em um painel. O scale funciona adequadamente, mas como o painel está dentro de um ScrollPane, ao rolar os elementos a serem desenhados são desenhados incorretamente, ficando cortados quando são rolados para fora da tela. A rolagem funciona normalmente se eu não colocar o AffineTransform. Alguns outros problemas ocorrem junto com este, como o botão “Zoom” sendo desenhado do lado direito às vezes, mas creio que são problemas relacionados. Segue código do Painel:

[code]public class DrawPanel extends JPanel implements MouseListener{
private ArrayList itens;
private float zoom = 1;

public DrawPanel() {
	super();
	itens = new ArrayList<MyItem>(30);
	setSize(1200, 1200);
	setPreferredSize(getSize());

	addMouseListener(this);
}

@Override
protected void paintComponent(Graphics g){
	super.paintComponent(g);		
	Graphics2D g2 = (Graphics2D) g.create();
	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

	AffineTransform at = new AffineTransform();  
	at.scale(zoom, zoom);
	g2.setTransform(at); // sem esta linha, a rolagem não apresenta problemas

	setBackground(Color.WHITE);

	for(MyItem my : itens){
		my.desenhar(g2);
	}

	g2.setStroke(new BasicStroke(3));
	g2.drawRect(40, 40, 50, 50); //  para efeito de testes
	g2.drawRect(30, 30, 50, 50); //  para efeito de testes

	g2.dispose();
}

public void adicionar(MyItem my){
	itens.add(my);
}

private void selecionar(Point2D ponto){
	for(MyItem my : itens){
		my.selecionar(ponto);
	}
}

public void redesenhar(){
	repaint();
}

public void setZoom(float zoom){
	this.zoom = zoom;
	this.setPreferredSize(new Dimension((int) (1200 * zoom), (int) (1200 * zoom)));
	redesenhar();
}

private Point2D traduce(Point2D ponto){
	ponto.setLocation(ponto.getX() / zoom, ponto.getY() / zoom);
	return ponto;
}

@Override
public void mousePressed(MouseEvent evt) {
	selecionar(traduce(evt.getPoint()));
	redesenhar();
}

@Override
public void mouseClicked(MouseEvent evt) {}
@Override
public void mouseEntered(MouseEvent evt) {}
@Override
public void mouseExited(MouseEvent evt) {}
@Override
public void mouseReleased(MouseEvent evt) {}

}[/code]
MyItem, MyCircle e MyRectangle são classes simples, que basicamente armazenam e desenham as formas (um círculo e um retângulo). O código do JFrame é este:

[code]package gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import elements.MyCircle;
import elements.MyRectangle;

public class Screen extends JFrame {
private DrawPanel painel;
private JButton bAddCircle;
private JButton bAddRect;
private JButton bZoom;
private JScrollPane sp;

public Screen(){
	super("Teste de Zoom");
	this.setLayout(new BorderLayout());
	painel = new DrawPanel();
	setMinimumSize(new Dimension(300, 300));

	bAddCircle = new JButton("Adicionar Círculo");
	bAddRect = new JButton("Adicionar Retângulo");
	bZoom = new JButton("Zoom");

	sp = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
	sp.setViewportView(painel);
	sp.setAutoscrolls(true);

	getContentPane().add(sp, BorderLayout.CENTER);
	getContentPane().add(bAddCircle, BorderLayout.NORTH);
	getContentPane().add(bAddRect, BorderLayout.SOUTH);
	getContentPane().add(bZoom, BorderLayout.EAST);

	setExtendedState(JFrame.MAXIMIZED_BOTH);

	setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

	bAddCircle.addActionListener(new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent arg0) {
			adicionarCirculo();
		}
	});
	
	bAddRect.addActionListener(new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent arg0) {
			adicionarRetangulo();
		}
	});

	bZoom.addActionListener(new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent arg0) {
			float f = Float.parseFloat(JOptionPane.showInputDialog("Zoom:"));
			painel.setZoom(f);
		}
	});
}

private void adicionarCirculo(){
	painel.adicionar(new MyCircle());
	painel.redesenhar();
}

private void adicionarRetangulo(){
	painel.adicionar(new MyRectangle());
	painel.redesenhar();
}

public static void main(String[] args){
	new Screen().setVisible(true);
}

}[/code]O fonte completo está em anexo. É um projeto do Eclipse. Eis uma imagem do problema:

[URL=http://imageshack.us/photo/my-images/577/rolagemantes.png/][/URL]
[URL=http://imageshack.us/photo/my-images/850/rolagemdepois.png/][/URL]

Agradeço desde já a atenção.

Abraço.

Bom dia a todos. É bom quando a gente descobre as coisas! :smiley: :smiley: :smiley: :smiley: :smiley:
Consegui resolver este caso. A solução é tecnicamente simples, mas para chegar nela foi um bocado de tempo. O código final do paintComponent ficou:

[code]@Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

	AffineTransform at = new AffineTransform();  
	at.scale(zoom, zoom);
	//g2.setTransform(at); // tira-se essa linha
	g2.transform(at); //  adiciona-se essa

	setBackground(Color.WHITE);

	for(MyItem my : itens){
		my.desenhar(g2);
	}

	g2.setStroke(new BasicStroke(3));
	g2.drawRect(40, 40, 50, 50); //  para efeito de testes
	g2.drawRect(30, 30, 50, 50); //  para efeito de testes

	g2.dispose();
}

[/code]Depois de um longo “ragestorm” (porque o “brainstorm” já tinha acabado faz tempo), fui pego pelo óbvio: por que há dois métodos (setTransform e transform) para fazer a mesma coisa? A resposta é simples: não há. Nada como ir na fonte para obter um pouco de conhecimento: http://docs.oracle.com/javase/6/docs/api/java/awt/Graphics2D.html#transform%28java.awt.geom.AffineTransform%29.

O método que aplica a transformação no contexto gráfico g2 é o tranform(). O setTransform() sobrescreve todas as tranformações do contexto gráfico, o que pode levar a comportamentos inesperados (como os que eu relatei). Pela documentação, o objetivo do setTransform é restaurar as transformações originais depois que toda a renderização é concluída.

Este é um dos raros casos em que, infelizmente, os nomes dos métodos não ajudaram muito. Junte isso a um pouco de falta de atenção e…

Abraços.