본문 바로가기

JAVA

[Java] 자바로 그림판 만들기(펜, 도형, 파일 읽기 쓰기, 색깔, 굵기)

0. 기능

  • 펜으로 글씨 쓰기
  • 선, 네모, 원 도형 그리기
  • 색깔, 굵기 조절
  • 파일 저장, 파일 불러오기
  • 전체 지우기, 지우개로 그림 지우기
  • 그림판에 계속 그릴 때 마다 사라지지 않게 BufferedImage 사용
  • 도형 선택해서 드래그 할 때 잔상으로 도형 크기 보이게 하기

 

1. 실행화면

그림1. 실행화면

 

 

2. 주요 메소드 설명

 

2-1 (버튼들 Event 처리)

 JButton버튼이 눌렸을 때는 JButton의 이름으로 String shapeString에 저장한다.

 여기서 JButton은 지우개, 원, 네모, 선, 펜에 해당한다.

 (예를 들어 원이 눌렸다면 shapeString에 원이라는 String이 저장된다.)

 

 또한 색깔이나 굵기가 선택된거에 따라 변수값에 넣는다.

그림2. 버튼 이벤트 처리

 

2-2 (선 그리기 마우스 처리)

  mousePressed 메소드는 클릭이 시작됐을 때 전에 사용됐던 위치 저장 변수를 초기화시킨다.

  그리고 클릭된 곳의 위치를 firstPointer라는 변수에 저장한다.

 

  mouseRelease 메소드는 마우스에서 클릭이 끝났을 때이다. 

  여기서 if로 펜이 아닌경우를 건 이유는 펜은 계속 그려져야 하는 상황이기 때문이다.  나머지 상황에서는 마지막으로 클릭된 위치를 secondPointer에 저장하고 그림을 그린다.

 

그림3. 마우스 이벤트 처리

 

2-3 updatePaint() 메소드

  updatePaint() 메소드가 길어서 일부만 설명하겠다. 

  width, height은 마우스가 시작점에 따라 음수가 될 수 있기 때문에 절대값을 붙여준다.

  minPointx, minPointy는 도형그리는 메소드가 왼쪽위에서 아래로 그려지기 때문에 필요하다.

  이 변수를 안쓰고 firstPointersecondPointer로만 사각형을 오른쪽에서 왼쪽으로 그려보면 필요성을 알 것이다.  

  

  Graphics2D는 그림판에 그리기 위한 라이브러리 클래스이며,

  createGraphics()이나, getGraphic() 이후에, 다 그리고 난 이후에는 꼭!! dispose()를 불러여한다.

  이를 안하면 객체가 계속 살아 있어서 그릴 때마다 안지워져서 성능저하가 일어날 수 있다.

  그리고 다 그린이후에는 repaint() 메소드를 불러준다~.

 

그림4. updatePaint() 메소드

 

 

  2-4 paintComponent()

   지난번 포스트에 중요하다고 했던 메소드다. 그냥 간단히 설명하자면 다른곳에서 신나게 그려왔다면

   이 메소드를 통해 그려왔던거 그림을 액자에 넣는것이다. 

   물론 이 메소드가 불린 이후에도 덧붙여서 그릴 수 있다.

3. 전체 코드

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;

public class CanvasDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				createAndShowGUI();
			}
		});

	}

	private static void createAndShowGUI() {
		System.out.println("Created GUI on EDT?" + SwingUtilities.isEventDispatchThread());
		JFrame f = new JFrame("Swing Paint Demo");
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		RectPanel rectPanel = new RectPanel();
		f.add(rectPanel, BorderLayout.NORTH);
		f.pack();
		f.setVisible(true);

	}

}

class RectPanel extends JPanel implements ActionListener, MouseListener, MouseMotionListener {

	String shapeString = ""; // 도형의 형태를 담는 변수
	Point firstPointer = new Point(0, 0);
	Point secondPointer = new Point(0, 0);
	BufferedImage bufferedImage;
	Color colors = Color.black;
	Float stroke = (float) 5;
	JComboBox<Color> colorComboBox;
	JComboBox<Float> strokeComboBox; // float로 설정해주는 이유는 setStroke에서 받는 인자 자료형이 float

	int width;
	int height;
	int minPointx;
	int minPointy;
	
	public RectPanel() {

		colorComboBox = new JComboBox<Color>();
		strokeComboBox = new JComboBox<Float>();
		JButton eraseAllButton = new JButton("전체지우기");
		JButton rectButton = new JButton("네모");
		JButton lineButton = new JButton("선");
		JButton circleButton = new JButton("원");
		JButton penButton = new JButton("펜");
		JButton eraseButton = new JButton("지우개");
		JButton saveButton = new JButton("Save");
		JButton openButton = new JButton("Open");

		colorComboBox.setModel(new DefaultComboBoxModel<Color>(new Color[] { Color.black, Color.red, Color.blue,
				Color.green, Color.yellow, Color.pink, Color.magenta }));

		strokeComboBox.setModel(new DefaultComboBoxModel<Float>(
				new Float[] { (float) 5, (float) 10, (float) 15, (float) 20, (float) 25 }));

		add(eraseAllButton);
		add(penButton);
		add(lineButton);
		add(rectButton);
		add(circleButton);
		add(colorComboBox);
		add(strokeComboBox);
		add(eraseButton);

		add(saveButton);
		add(openButton);

		Dimension d = getPreferredSize();
		bufferedImage = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
		setImageBackground(bufferedImage); // save 할 때 배경이 default로 black이여서 흰색으로

		eraseAllButton.addActionListener(this);
		rectButton.addActionListener(this);
		lineButton.addActionListener(this);
		circleButton.addActionListener(this);
		penButton.addActionListener(this);
		eraseButton.addActionListener(this);
		colorComboBox.addActionListener(this);
		strokeComboBox.addActionListener(this);
		saveButton.addActionListener(new SaveL(this, bufferedImage));
		openButton.addActionListener(new OpenL(this, bufferedImage));

		addMouseListener(this);
		addMouseMotionListener(this);

	}

	public void mousePressed(MouseEvent e) {

		// 다시 클릭됐을경우 좌표 초기화
		firstPointer.setLocation(0, 0);
		secondPointer.setLocation(0, 0);

		firstPointer.setLocation(e.getX(), e.getY());

	}

	public void mouseReleased(MouseEvent e) {

		if (shapeString != "펜") {
			secondPointer.setLocation(e.getX(), e.getY());
			updatePaint();
		}
	}

	public void actionPerformed(ActionEvent e) {

		if (e.getSource().getClass().toString().contains("JButton")) {
			shapeString = e.getActionCommand();
		}

		else if (e.getSource().equals(colorComboBox)) {
			colors = (Color) colorComboBox.getSelectedItem();
		}

		else if (e.getSource().equals(strokeComboBox)) {
			stroke = (float) strokeComboBox.getSelectedItem();
		}

	}

	public Dimension getPreferredSize() {
		return new Dimension(500, 700);
	}

	public void updatePaint() {

		width = Math.abs(secondPointer.x - firstPointer.x);
		height = Math.abs(secondPointer.y - firstPointer.y);

		minPointx = Math.min(firstPointer.x, secondPointer.x);
		minPointy = Math.min(firstPointer.y, secondPointer.y);

		Graphics2D g = bufferedImage.createGraphics();

		// draw on paintImage using Graphics
		switch (shapeString) {

		case ("선"):
			g.setColor(colors);
			g.setStroke(new BasicStroke(stroke));
			g.drawLine(firstPointer.x, firstPointer.y, secondPointer.x, secondPointer.y);

			break;

		case ("네모"):
			g.setColor(colors);
			g.setStroke(new BasicStroke(stroke));
			g.drawRect(minPointx, minPointy, width, height);

			break;

		case ("원"):
			g.setColor(colors);
			g.setStroke(new BasicStroke(stroke));
			g.drawOval(minPointx, minPointy, width, height);
			break;

		case ("펜"):
			g.setColor(colors);
			g.setStroke(new BasicStroke(stroke));
			g.drawLine(firstPointer.x, firstPointer.y, secondPointer.x, secondPointer.y);
			break;

		case ("지우개"):
			g.setColor(Color.white);
			g.setStroke(new BasicStroke(stroke));
			g.drawLine(firstPointer.x, firstPointer.y, secondPointer.x, secondPointer.y);
			break;
			
		case ("전체지우기"):
			setImageBackground(bufferedImage);
			shapeString ="";
			break;
			
		default:
			break;

		}

		g.dispose();
		
		repaint();
	}

	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		g.drawImage(bufferedImage, 0, 0, null);

	}

	public void setImageBackground(BufferedImage bi) {
		this.bufferedImage = bi;
		Graphics2D g = bufferedImage.createGraphics();
		g.setColor(Color.white);
		g.fillRect(0, 0, 500, 700);
		g.dispose();
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		// TODO Auto-generated method stub
		
		width = Math.abs(secondPointer.x - firstPointer.x);
		height = Math.abs(secondPointer.y - firstPointer.y);

		minPointx = Math.min(firstPointer.x, secondPointer.x);
		minPointy = Math.min(firstPointer.y, secondPointer.y);
		
		
		
		if (shapeString == "펜" | shapeString == "지우개") {
			if (secondPointer.x != 0 && secondPointer.y != 0) {
				firstPointer.x = secondPointer.x;
				firstPointer.y = secondPointer.y;
			}
			secondPointer.setLocation(e.getX(), e.getY());
			updatePaint();
		} else if (shapeString == "선") {

			Graphics g = getGraphics();
			
			
			g.drawLine(firstPointer.x, firstPointer.y, secondPointer.x, secondPointer.y);
			secondPointer.setLocation(e.getX(), e.getY());
			repaint();
			g.dispose();
		} else if (shapeString == "네모") {

			Graphics g = getGraphics();
			g.setColor(Color.BLACK);
			g.setXORMode(getBackground());
			
			g.drawRect(minPointx, minPointy, width, height);
			secondPointer.setLocation(e.getX(), e.getY());
			repaint();
			g.dispose();
		} else if (shapeString == "원") {

			Graphics g = getGraphics();
			g.setColor(Color.BLACK);
			g.setXORMode(getBackground());
			
			g.drawOval(minPointx, minPointy, width, height);
			secondPointer.setLocation(e.getX(), e.getY());
			
			g.dispose();
			repaint();
		}
		
		
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

}// Class dotButton

class OpenL implements ActionListener {

	RectPanel rectPanel;
	BufferedImage bufferedImage;
	JFileChooser jFileChooser = new JFileChooser();;

	OpenL(RectPanel rectPanel, BufferedImage bufferedImage) {
		this.rectPanel = rectPanel;
		this.bufferedImage = bufferedImage;

	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		FileNameExtensionFilter filter = new FileNameExtensionFilter("JPEG", "jpeg", "jpg", "png", "bmp", "gif");
		jFileChooser.addChoosableFileFilter(filter);

		int rVal = jFileChooser.showOpenDialog(null);
		if (rVal == JFileChooser.APPROVE_OPTION) {
			File selectedFile = jFileChooser.getSelectedFile();
			try {
				rectPanel.bufferedImage = ImageIO.read(new File(selectedFile.getAbsolutePath()));
				rectPanel.repaint();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		if (rVal == JFileChooser.CANCEL_OPTION) {

		}

	}

}// class OpenL

class SaveL implements ActionListener {

	RectPanel rectPanel;
	BufferedImage bufferedImage;
	JFileChooser jFileChooser;

	SaveL(RectPanel rectPanel, BufferedImage bufferedImage) {
		this.rectPanel = rectPanel;
		this.bufferedImage = bufferedImage;

	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		jFileChooser = new JFileChooser();
		jFileChooser.setFileFilter(new FileNameExtensionFilter("*.png", "png"));
		int rVal = jFileChooser.showSaveDialog(null);
		if (rVal == JFileChooser.APPROVE_OPTION) {
			File file = jFileChooser.getSelectedFile();
			try {
				ImageIO.write(bufferedImage, "png", new File(file.getAbsolutePath()));
				System.out.println("saved Correctly " + file.getAbsolutePath());
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				System.out.println("Failed to save image");
			}
		}
		if (rVal == JFileChooser.CANCEL_OPTION) {
			System.out.println("No file choosen");
		}

	}

}// class SaveL