《Java程式碼成精了——會跳薩日朗的火柴人》實現步驟三:繪畫火柴人

2020-08-12 14:35:26

在此之前,我們已經實現專案的第二步,處理了每一張圖的顏色,我們讓小人的顏色是黑色,背景整體調整爲白色。本小節要做的就是在這些圖片上進行繪畫火柴人。因爲我們繪畫水平有限,我們希望在原圖片的輪廓上進行繪畫。

 

一、swing組建

我們要想在已有的圖片上進行火柴人繪畫,首先得有繪畫的圖形介面。可以使用java中的swing組建來實現。

談到Java的圖形介面程程,指的就是GUI(圖形用戶介面)。Graphical User Interface,用圖形的方式,來顯示計算機操作的介面。

Java爲GUI提供的物件都在java.awt和javax.swing兩個包中。

  • java.awt,Abstract Window ToolKit(抽象視窗工具包),需要呼叫本地系統方法實現功能。屬重量級控制元件。
  • javax.swing,在awt的基礎上,建立的一套圖形介面系統,其中提供了更多的元件,而且完全由Java實現。增強了移植性,屬輕量級控制元件。

相對於AWT而言Swing包中提供了更多的豐富的、快捷的、強大的GUI元件,而且這些元件都是java語言編寫而成,因此Swing不依賴本地平臺,可以真正做到跨平臺執行。通常而言我們把AWT稱之爲重量級元件,Swing稱之爲輕量級軟體,一般而言Swing元件都是在AWT元件名稱前加J。


1、JFrame 視窗

JFrame 用來設計類似於 Windows 系統中視窗形式的介面。JFrame 是 Swing 元件的頂層容器,該類繼承了 AWT 的 Frame 類,支援 Swing 體系結構的高階 GUI 屬性。

在Swing元件中最常見的就是JFrame,他和Frame一樣是一個獨立存在的頂級視窗,不能放置在其他容器中,JFrame支援所有視窗的操作,例如視窗最小化,設定視窗大小。

JFrame():構造一個初始時不可見的新表單。
JFrame(String title):建立一個具有 title 指定標題的不可見新表單。

範例程式碼:

package qf.day05.demo;

import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
/**
 * 建立圖形介面程式的思路:
 * 
 * step1:建立frame表單,JFrame
 * 
 * step2:對表單進行基本的設定:
 * 	大小,位置,佈局等等,標題。。。
 * 
 * step3:定義裏面的元件:
 * 	按鈕,文字。。。---->JPanel
 * 
 * step4:通過add()方法,將這些元件,新增到表單中。
 * 
 * step5:讓表單顯示:setVisible(true)
 * 
 * @author ruby
 *
 */
public class Demo01_JFrame {

	public static void main(String[] args) {
		//1.建立frame表單
		JFrame frame =new JFrame();
		
		//2.設定表單
		frame.setSize(400, 200);//視窗的大小,畫素
		frame.setLocation(300, 500);
		
		//frame.setBounds(300, 500, 400, 200);
		frame.setTitle("我的第一個GUI程式,哈哈哈哈");//設定標題
		frame.setLayout(new FlowLayout());//設定佈局
		
		//3.擺放個按鈕
		JButton button = new JButton("我是一個按鈕");
		//將這個按鈕放在frame上
		frame.add(button);
		JTextField textField =new JTextField("haha",30);
		frame.add(textField);
		
		
		//設定表單可見
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//通過點選按鈕可以直接關閉程式
	}

}

執行結果如下:


2、JPanel 面板

JPanel 是一種中間層容器,它能容納元件並將元件組合在一起,但它本身必須新增到其他容器中使用。JPanel 類的構造方法如下。

JPanel():使用預設的佈局管理器建立新面板,預設的佈局管理器爲 FlowLayout。
JPanel(LayoutManagerLayout layout):建立指定佈局管理器的 JPanel 物件。

範例程式碼:

package qf.day05.demo;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Demo02_JPanel {

	public static void main(String[] args) {
		JFrame frame = new JFrame("王二狗的程式");
		frame.setLayout(new FlowLayout());
		frame.setSize(520,700);
		
		JPanel panel = new JPanel();//建立面板
		panel.setPreferredSize(new Dimension(400, 300));//設定面板的寬和高
		panel.setBackground(Color.BLUE);
		
		
		JButton btn1 = new JButton("按鈕1");
		JButton btn2 = new JButton("按鈕2");
		JButton btn3 = new JButton("按鈕3");
		JButton btn4 = new JButton("按鈕4");
		
		panel.add(btn1);
		panel.add(btn4);
		panel.add(btn2);
		panel.add(btn3);
		
		frame.add(panel);
		
		
		JPanel panel2 = new JPanel();
		JButton btn5 = new JButton("按鈕5");
		JButton btn6 = new JButton("按鈕6");
		JButton btn7 = new JButton("按鈕7");
		panel2.add(btn5);
		panel2.add(btn6);
		panel2.add(btn7);
		
		frame.add(panel2);
		
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

}

執行結果如下:

3、其他控制元件

以上都用到了一些具體的,比如JButton等控制元件。

4、事件監聽

事件監聽:
    事件源:就是awt包或者swing包中的那些圖形介面元件
    事件:每一個事件源都有自己特有的對應事件和共性事件。
  監聽器:將可以出發某一個事件的動作都已經封裝到了監聽器中。
  
    以上這些,在java中都已經定義好了,直接獲取物件來用就可以了。我們要做的就是,對產生的動作進行處理

Interface MouseListener

void mouseClicked(MouseEvent e) 
在元件上單擊(按下並釋放)滑鼠按鈕時呼叫。  
void mouseEntered(MouseEvent e) 
當滑鼠進入元件時呼叫。  
void mouseExited(MouseEvent e) 
當滑鼠退出元件時呼叫。  
void mousePressed(MouseEvent e) 
在元件上按下滑鼠按鈕時呼叫。  
void mouseReleased(MouseEvent e) 
在元件上釋放滑鼠按鈕時呼叫。  
​

Interface MouseMotionListener,滑鼠動作監聽

void mouseDragged(MouseEvent e) 
在元件上按下滑鼠按鈕然後拖動時呼叫。  
void mouseMoved(MouseEvent e) 
當滑鼠遊標移動到元件上但沒有按鈕被按下時呼叫。  
​

範例程式碼:

package qf.day04.demo04;

import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;

import javax.swing.JButton;
import javax.swing.JFrame;

public class Demo03_EventListener {

	public static void main(String[] args) {
		JFrame frame = new JFrame("事件監聽");//直接設定標題
		//對frame進行基本的設定
		frame.setSize(400, 500);
		frame.setLocation(300, 100);
		frame.setLayout(new FlowLayout());
		
		JButton button = new JButton("按鈕");
		frame.add(button);
		
		//給元件新增監聽:
		button.addMouseListener(new MouseAdapter() {
			
			int count = 0;
			
			@Override
			public void mouseClicked(MouseEvent e) {
				System.out.println("滑鼠點選。。"+(count++));
			}
		});
		
		frame.addMouseMotionListener(new MouseMotionAdapter() {
			@Override
			public void mouseDragged(MouseEvent e) {
				System.out.println("表單上的滑鼠被拖拽了。。"+e.getX()+","+e.getY());
			}
		});
		
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

}
//自己實現,需要實現MouseListener介面中的所有的方法,而MouseAdapter類已經實現了該介面中的所有方法,我們可以直接繼承自MouseAdapter,然後只重寫自己需要的方法即可。
//class MyButtonImple implements MouseListener{
//
//	@Override
//	public void mouseClicked(MouseEvent e) {
//		System.out.println("滑鼠被點選餓了。。");
//	}
//
//	@Override
//	public void mouseEntered(MouseEvent e) {
//		// TODO Auto-generated method stub
//		
//	}
//
//	@Override
//	public void mouseExited(MouseEvent e) {
//		// TODO Auto-generated method stub
//		
//	}
//
//	@Override
//	public void mousePressed(MouseEvent e) {
//		// TODO Auto-generated method stub
//		
//	}
//
//	@Override
//	public void mouseReleased(MouseEvent e) {
//		// TODO Auto-generated method stub
//		
//	}
//	
//}

執行結果:

每當滑鼠點選一次,count的值就累加1。

二、專案中的繪圖介面

好了,通過以上的學習,我們已經大致掌握瞭如果使用java的swing介面,來建立圖解介面程式,我們需要建立出以下的程式介面,並且能夠通過事件監聽,實現繪畫功能。

好了,接下來我們實現專案的第三步程式碼,新建一個java檔案,D3_DrawPic.java,範例程式碼如下:

package demo;

import java.awt.AWTException;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.RenderableImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import org.bytedeco.javacpp.indexer.IntRawIndexer;

public class D3_DrawPic {

	public static void main(String[] args) {
		//1.建立視窗
		JFrame frame = new JFrame();
		frame.setLayout(new FlowLayout());
		frame.setSize(520,700);
		frame.setTitle("王二狗的畫板");
		
		//2.建立面板:按鈕區面板,繪畫區面板
		
		DrawPanel drawPanel = new DrawPanel();
		drawPanel.setLayout(null);
		drawPanel.setPreferredSize(new Dimension(520, 660));
		drawPanel.setBackground(Color.WHITE);
		
		
		ButtonPanel buttonPanel = new ButtonPanel(drawPanel,frame);
		buttonPanel.setLayout(new FlowLayout());
		buttonPanel.setPreferredSize(new Dimension(520,40));
//		buttonPanel.setBackground(Color.BLUE);
		

		
		//3.將面板,新增到視窗上
		frame.add(buttonPanel);
		frame.add(drawPanel);
		
		//4.視窗新增監聽
		frame.addMouseListener(drawPanel);
		frame.addMouseMotionListener(drawPanel);
		
		
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		
		
		
	}

}
//按鈕區的畫板
class ButtonPanel extends JPanel{
	//設定按鈕面板的一堆按鈕
	JButton pencil = new JButton("畫筆");
	JButton circle = new JButton("圓");
	JButton line = new JButton("直線");
	JButton eraser = new JButton("橡皮擦");
	JButton next = new JButton("下一幀");
	JButton retry = new JButton("重畫");
	JButton save = new JButton("儲存");
	
	private DrawPanel drawPanel;
	private JFrame frame;
	
	
	int picture = 0;//圖片的編號
	public ButtonPanel(DrawPanel drawPanel,JFrame frame){
		this.drawPanel = drawPanel;
		this.frame = frame;
		this.add(pencil);
		this.add(circle);
		this.add(line);
		this.add(eraser);
		this.add(next);
		this.add(retry);
		this.add(save);
		
		
		//給這些按鈕,新增監聽。。
		pencil.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				System.out.println("普通的繪畫。。。");
				drawPanel.setType(0);//0表示普通的繪畫。。
			}
		});
		
		//畫圓按鈕的
		circle.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				System.out.println("要畫圓了。。");
				drawPanel.setType(1);//1表示畫圓
			}
		});
		
		//畫直線的按鈕
		line.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				System.out.println("按鈕被點選,表示要畫直線了。。");
				drawPanel.setType(2);//2表示畫直線
			}
		});
		
		//橡皮擦
		eraser.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				System.out.println("擦除。。。");
				drawPanel.setType(3);//3表示擦除
			}
		});
		
		//重畫
		retry.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				System.out.println("重畫這張圖。。"+picture);
				//思路:開啓圖片的目錄,讀取對應的圖片數據--->顯示到視窗,繪圖工具,繪畫這張圖片
				String imagepath = "C:\\Ruby\\薩日朗\\換色\\"+picture+".jpg";
				System.out.println(imagepath);
				File file = new File(imagepath);
				System.out.println(file.exists());
				try {
					//讀取圖片到記憶體中-->BufferedImage
					BufferedImage image = ImageIO.read(file);//原始圖片的畫素:112*184
					//放大圖片物件
					Image image2=image.getScaledInstance(400, 600, Image.SCALE_DEFAULT);//使用預設的影象縮放演算法。 
					//將放大的圖片,繪畫到面板上
					Graphics2D g2 = (Graphics2D) drawPanel.getGraphics();
					g2.drawImage(image2, 60, 0, null);
					
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				
			}
		});
		//下一幀
		next.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				picture++;
				System.out.println("下一張。。"+picture);
				//思路:開啓圖片的目錄,讀取對應的圖片數據--->顯示到視窗,繪圖工具,繪畫這張圖片
				String imagepath = "C:\\Ruby\\薩日朗\\換色\\"+picture+".jpg";
				System.out.println(imagepath);
				File file = new File(imagepath);
				System.out.println(file.exists());
				if(!file.exists()){
					System.out.println("圖片不存在。。"+picture);
					return;//結束方法
				}
				try {
					//讀取圖片到記憶體中-->BufferedImage
					BufferedImage image = ImageIO.read(file);//原始圖片的畫素:112*184
					//放大圖片物件
					Image image2=image.getScaledInstance(400, 600, Image.SCALE_DEFAULT);//使用預設的影象縮放演算法。 
					//將放大的圖片,繪畫到面板上
					Graphics2D g2 = (Graphics2D) drawPanel.getGraphics();
					g2.drawImage(image2, 60, 0, null);
					
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		});
	//繪畫後,進行圖片儲存
		save.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				System.out.println("儲存圖片。。"+picture);
				//捕捉螢幕的某塊區域-->儲存成圖片
				//1.圖片要儲存的路徑
				String imagePath = "C:\\Ruby\\薩日朗\\繪圖\\"+picture+".jpg";
				
				System.out.println(frame.getX()+","+frame.getY());
				
				
				//從螢幕繪畫的區域:4個參數:
				//前的兩個表示位置,後的兩個表示寬度和高度
				Rectangle rectangle = new Rectangle(frame.getX()+10,frame.getY()+82,500,600);
				
				//建立包含從螢幕讀取的畫素的影象。
				try {
					BufferedImage image = new Robot().createScreenCapture(rectangle);
					File file = new File(imagePath);
					ImageIO.write(image, "jpg", file);
					System.out.println("儲存了。。"+file.getAbsolutePath());
				} catch (AWTException e1) {
					e1.printStackTrace();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		});
		
	}
	
}

//繪畫區面板
class DrawPanel extends JPanel implements MouseListener,MouseMotionListener{
	//繪畫的型別:0代表普通的的的繪畫,1表示畫圓,2表示畫直線,3表示擦除
	private int type;
	private int xd = 5;//偏移量
	private int yd = 80;
	
	int x1, y1; //要繪畫的起點
	int x2,y2;//要繪畫終點

	public int getType() {
		return type;
	}

	public void setType(int type) {
		this.type = type;
	}

	int r = 30;//橢圓的半徑
	@Override
	public void mouseClicked(MouseEvent e) {
		//當滑鼠在繪畫區點選一下,就產生一個圓圈
		System.out.println(e.getX()+","+e.getY());
		Graphics2D g2 = (Graphics2D) this.getGraphics();
		if(type == 1){//畫圓圈
			g2.setColor(Color.RED);//設定繪畫工具的顏色
			//詞義,橢圓
			Ellipse2D circle = new Ellipse2D.Double();
			int x = e.getX();
			int y = e.getY();
			circle.setFrameFromCenter(x-xd, y-yd, x+r-xd, y+r-yd);
			g2.fill(circle);
			
			
		}
	}

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

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

	@Override
	public void mousePressed(MouseEvent e) {
		System.out.println("滑鼠按鈕的位置。。");
		x1 = e.getX();
		y1 = e.getY();
//		System.out.println(x1+","+y1);
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		x2 = e.getX();
		y2 = e.getY();
		if(type == 2){//繪畫直線
			Graphics2D g2= (Graphics2D) this.getGraphics();
			g2.setColor(Color.RED);//顏色,紅色
			g2.setStroke(new BasicStroke(15.0f));//畫筆的粗細
			x1 -= xd;
			y1 -= yd;
			x2 -= xd;
			y2 -= yd;
			
			Ellipse2D circle =new Ellipse2D.Double();
			circle.setFrameFromCenter(x1, y1,x1+7.5,y1+7.5);
			g2.fill(circle);
			g2.drawLine(x1, y1, x2, y2);
			circle.setFrameFromCenter(x2, y2,x2+7.5,y2+7.5);
			g2.fill(circle);
			System.out.println("畫直線。。。");
			
		}
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		x2 = e.getX();
		y2 = e.getY();
		Graphics2D g2 = (Graphics2D) this.getGraphics();//繪圖工具
		if(type == 0 || type == 3){
			System.out.println("普通的繪畫。。或者擦除");
			g2.setStroke(new BasicStroke(10));//設定畫筆
			if(type == 3){//擦除
				System.out.println("擦除。。。");
				g2.setColor(Color.WHITE);
			}else{
				g2.setColor(Color.RED);//設定畫筆顏色
			}
			
			//使用抗鋸齒來進行繪畫
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			g2.drawLine(x1-xd, y1-yd, x2-xd, y2-yd);
			
			x1 = x2;
			y1 = y2;
			
		}
	}

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

執行結果如下:

三、繪畫每一張圖片

上面的程式執行起來,就可以進行每一張圖片的繪畫了。

 

Java實現手繪火柴人

 

我們需要將C:\Ruby\薩日朗\換色,目錄下的所有圖片,一張一張繪畫,儲存到指定的C:\Ruby\薩日朗\繪圖,這個目錄下。