主頁 > 後端開發 > Java 從零開始實作一個畫圖板、以及影像處理功能,代碼可復現

Java 從零開始實作一個畫圖板、以及影像處理功能,代碼可復現

2022-04-15 06:28:00 後端開發

Java 從零開始實作一個畫圖板、以及影像處理功能,代碼可復現

這是一個學習分享博客,帶你從零開始實作一個畫圖板、影像處理的小專案,為了降低閱讀難度,本博客將畫圖板的一步步迭代優化程序展示給讀者,篇幅較長,Java初學者可放心食用,(文末有源代碼)

本博客實作的功能(根據本文講解的順序)

  • 直線、簽字筆、實時直線、謝爾賓斯基地毯、遞回KLine、矩形、圓、實心矩形、實心圓、等腰三角形、三角形、多邊形、改進多邊形、 立方體、橡皮擦
  • 畫筆的顏色更改
  • 撤回、保存、打開
  • 打開jpg圖片,保存圖片
  • 圖片特效:馬賽克、黑白照、油畫、背景替換、圖片融合等等;
  • 圖片的放大和縮小,圖片旋轉
  • 圖片的顏色調整

界面效果

image
image
image

image

image

image

怎么樣?如果覺得還不錯的話就請繼續看下去吧!
首先我們要寫一個界面,就要給界面添加一個監聽器,對監聽器不太熟悉的同學,可以看我的這篇文章 常見監聽器用法

第一步:創建畫布

  • 萬事開頭難,我們從創建一個表單開始,并給表單添加畫筆g,
package drawBoard_test;

import javax.swing.*;
import java.awt.*;

public class DrawUI extends JFrame {

    String[] strs = {"直線","簽字筆","實時直線", "謝爾賓斯基地毯","遞回KLine","矩形", "圓", "實心矩形", "實心圓", "等腰三角形", "三角形", "多邊形",
            "改進多邊形","立方體",  "橡皮擦", "撤回", "保存", "打開"};
    Color[] color = {Color.red,Color.white,Color.black,Color.blue};
    //添加功能和顏色按鈕
    public void addButton(){
        for(String str : strs){
            JButton btn = new JButton(str);
            add(btn);
        }
        Dimension dim = new Dimension(30,30);
        for(Color c : color){
            JButton btn = new JButton();
            btn.setBackground(c);
            btn.setPreferredSize(dim);
            add(btn);
        }
    }

    public void initUI(){
        this.setTitle("畫圖板");
        FlowLayout flow = new FlowLayout();
        this.setLayout(flow);
        this.setSize(1000,800);
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.addButton();
        this.setVisible(true);
        Graphics g = getGraphics();

    }

    public static void main(String[] args) {
        DrawUI drawUI = new DrawUI();
        drawUI.initUI();
    }
}

  • 大家可以試著運行一下,出現以下效果,第一步就算成功了

第二步:為表單和按鈕添加監聽器

  • 首先要創建一個監聽器,我們需要用到事件監聽器ActionListener,和滑鼠監聽器MouseListener,MouseMotionListener,
    所以我們選擇繼承這三個介面, 我們都知道,繼承一個介面時需要重寫介面的所有方法,但是我們又不會使用到三個介面的所有方法(滑鼠進入/離開組件),
    所以我們可以先寫一個類A繼承所有介面,然后再用監聽器類去繼承類A,
  • 監聽器的父類:
package drawBoard_test;

import java.awt.event.*;

public class DrawListenerFather implements ActionListener, MouseListener, MouseMotionListener {

    @Override
    public void actionPerformed(ActionEvent e) {

    }
    @Override
    public void mouseClicked(MouseEvent e) {

    }
    @Override
    public void mousePressed(MouseEvent e) {

    }
    @Override
    public void mouseReleased(MouseEvent e) {

    }
    @Override
    public void mouseEntered(MouseEvent e) {

    }
    @Override
    public void mouseExited(MouseEvent e) {

    }
    @Override
    public void mouseDragged(MouseEvent e) {

    }
    @Override
    public void mouseMoved(MouseEvent e) {

    }
}

接下來,創建我們需要的監聽器DrawListener,我們如果想在畫圖板上繪制的話,需要將主頁面的畫筆g傳給監聽器,
所以我們給監聽器添加成員變數Graphic g;并添加setG()方法,

package drawBoard_test;

public class DrawListener extends DrawListenerFather {
    private Graphics g;

    public void setG(Graphics g) {
        this.g = g;
    }
    @Override
    public void actionPerformed(ActionEvent e) {

    }
    @Override
    public void mouseClicked(MouseEvent e) {

    }
    @Override
    public void mousePressed(MouseEvent e) {

    }
    @Override
    public void mouseReleased(MouseEvent e) {

    }
    @Override
    public void mouseEntered(MouseEvent e) {

    }
    @Override
    public void mouseDragged(MouseEvent e) {

    }
    @Override
    public void mouseMoved(MouseEvent e) {

    }
}
  • 我們將主表單的畫筆g傳給監聽器,并為主表單以及它的所有按鈕以及加上監聽器,
  • 主表單DrawUI中的代碼更新為:
package drawBoard_test;

import javax.swing.*;
import java.awt.*;

public class DrawUI extends JFrame {
    DrawListener dl = new DrawListener();
    String[] strs = {"直線","簽字筆","實時直線", "謝爾賓斯基地毯","遞回KLine","矩形", "圓", "實心矩形", "實心圓", "等腰三角形", "三角形", "多邊形",
            "改進多邊形","立方體",  "橡皮擦", "撤回", "保存", "打開"};
    Color[] color = {Color.red,Color.white,Color.black,Color.blue};
    public void addButton(){
        for(String str : strs){
            JButton btn = new JButton(str);
            btn.addActionListener(dl); //添加事件監聽器
            add(btn);
        }
        Dimension dim = new Dimension(30,30);
        for(Color c : color){
            JButton btn = new JButton();
            btn.setBackground(c);
            btn.setPreferredSize(dim);
            btn.addActionListener(dl); //添加事件監聽器
            add(btn);
        }
    }

    public void initUI(){
        this.setTitle("畫圖板");
        FlowLayout flow = new FlowLayout();
        this.setLayout(flow);
        this.setSize(1000,800);
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        this.addButton();
        this.setVisible(true);
        this.addMouseListener (dl);
        this.addMouseMotionListener (dl);//添加滑鼠監聽器
        Graphics g = getGraphics ();
        dl.setG(g); //將表單的畫筆g傳入監聽器
    }

    public static void main(String[] args) {
        DrawUI drawUI = new DrawUI();
        drawUI.initUI();
    }
}

接下來我們就可以去實作我們的繪圖功能了!

第三步,完善監聽器的功能

我們在監聽器中創建一個字串shapeName,當點擊按鈕時,將按鈕上的字符賦給shapeName,再根據shapeName的值來決定滑鼠監聽器的具體行為

繪制直線以及更換畫筆顏色

  • 繪制直線我們只需要知道滑鼠點擊時的坐標和滑鼠釋放時的坐標,然后使用g.drawLine(x1,y1,x2,y2)即可繪制成功
  • 我們來看代碼
package drawBoard_test;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;

public class DrawListener extends DrawListenerFather {
    private Graphics g;
    String shapeName = null;  //按鈕上的圖形名稱
    String btn_action ;  //按鈕上的字串
    Color color;  //記錄當前畫筆的顏色
    int x2,y2,x3,y3; //存放坐標
    public void setG(Graphics g) {
        this.g = g;
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        /**
         *有的小伙伴可能會有疑問,為什么要用btn_action做一個中間量呢?試想一下,如果我們直接使用switch(shapeName),那么我們點擊顏色按鈕的時候
         * shapeName就會被換成空值"",我們就需要重新點擊圖形按鈕再進行繪制,
         */
        btn_action = e.getActionCommand(); 
        
        if(btn_action.equals("")){
            JButton btn = (JButton) e.getSource(); //getSource方法獲取觸發此次事件的組件物件,回傳值為Object型別
            color = btn.getBackground(); //獲取按鈕組件的背景顏色
            g.setColor(color);
            return;
        }else {
            shapeName =  btn_action;
        }

    }
    @Override
    public void mousePressed(MouseEvent e) {
        x2 = e.getX();
        y2 = e.getY();
    }
    @Override
    public void mouseReleased(MouseEvent e) {
        x3 = e.getX();
        y3 = e.getY();
        if(shapeName == null) return;
        switch(shapeName){
            case "直線":
                g.drawLine(x2, y2, x3, y3);
                break;
        }
    }

}
    

此時,畫圖板可以繪制出直線,我們來看一下效果
image

實作直線的繪制之后,其余功能的實作也是水到渠成的,我們繼續往下看,

矩形、圓、實心矩形、實心圓、等腰三角形、謝爾賓斯基地毯、遞回KLine、立方體、橡皮擦功能以及顏色按鈕的實作

  • 矩形:矩形的實作使用g.drawRext(x2,y2,x2-x3,y2-y3)繪制,需要一個坐標,和長、寬,
    • 我們可以直接使用上面的式子繪制,但是如果我們從左下往右上拖動滑鼠時,就無法繪出矩形
      所以我們左上角的坐標的x,y坐標使用兩點中較小的x,y值,長寬取差的絕對值,即
      g.drawRect(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
  • 圓:圓的引數與矩形相同 g.drawOval(x2,y2,x2-x3,y2-y3) ,畫出的圓為同樣引數畫出的矩形的內切矩形
  • 實心矩形:g.fillRect(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
  • 實心圓:g.fillOval(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
  • 等腰三角形:等腰三角形的實作是用三條直線進行連接,我們用矩形作為參考,拖動滑鼠獲得的矩形,取矩形的下邊兩個點
    和上邊線的中點進行連接,即可獲得一個等腰三角形
  • 謝爾賓斯基地毯:這是一個依靠遞回實作的圖形,將一個實心正方形劃分為的9個小正方形,去掉中間的小正方形,
    再對余下的小正方形重復這一操作便能得到謝爾賓斯基地毯,實作結果如圖所示
  • 遞回KLine:我們炒股的曲線往往是曲折蜿蜒的,我們就來模擬一下這種曲線,我們通過滑鼠的拖動可以獲得它的起始和終止的位置坐標,
    然后我們取他們的中點的x坐標,和范圍內隨機的y坐標,重復這一操作,直到兩點x坐標相鄰時就連接,
  • 立方體:使用斜二側畫法確定頂點坐標,然后進行連線
  • 橡皮擦:橡皮擦是顏色與背景顏色相同的矩形,
    根據上述的描述,我們將監聽器的代碼更新為
package drawBoard_test;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;

public class DrawListener extends DrawListenerFather {
    private Graphics g;
    String shapeName = null;
    String btn_action ;
    Color color;
    int x2,y2,x3,y3;
    public void setG(Graphics g) {
        this.g = g;
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        btn_action = e.getActionCommand(); //btn_action
        if(btn_action.equals("")){
            JButton btn = (JButton) e.getSource();
            color = btn.getBackground();
            g.setColor(color);
            return;
        }else {
            shapeName =  btn_action;
        }

    }
    @Override
    public void mousePressed(MouseEvent e) {
        x2 = e.getX();
        y2 = e.getY();
    }
    @Override
    public void mouseReleased(MouseEvent e) {
        x3 = e.getX();
        y3 = e.getY();
        if(shapeName == null) return;
        switch(shapeName){
            case "直線":
                g.drawLine(x2, y2, x3, y3);
                break;

            case "矩形":
                g.drawRect(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
                break;
            case "圓" :
                g.drawOval(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
                break;
            case "謝爾賓斯基地毯" :
                Sierpinski(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
                break;
            case "遞回KLine" :
                KLine(x2,y2,x3,y3,y3-y2);
                break;
            case "實心矩形" :

                g.fillRect(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
                break;
            case "實心圓" :

                g.fillOval(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
                break;
            case "等腰三角形" :
                g.drawLine(x2,y3,x3,y3);
                g.drawLine(x2,y3,(x2+x3)/2,y2);
                g.drawLine(x3,y3,(x2+x3)/2,y2);
                break;
            case "立方體" :
                g.drawRect(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
                g.drawLine(x2+(int)((x3-x2)*1.414/4),y2-(int)((y3-y2)*1.414/4),x2,y2);
                g.drawLine(x2+(int)((x3-x2)*1.414/4),y2-(int)((y3-y2)*1.414/4),x3+(int)((x3-x2)*1.414/4),y2-(int)((y3-y2)*1.414/4));
                g.drawLine(x3,y2,x3+(int)((x3-x2)*1.414/4),y2-(int)((y3-y2)*1.414/4));
                g.drawLine(x3+(int)((x3-x2)*1.414/4),y3-(int)((y3-y2)*1.414/4),x3+(int)((x3-x2)*1.414/4),y2-(int)((y3-y2)*1.414/4));
                g.drawLine(x3+(int)((x3-x2)*1.414/4),y3-(int)((y3-y2)*1.414/4),x3,y3);
                break;

            case "橡皮擦" :
                Color pre = g.getColor(); //記錄之前的顏色 ,用完再換回去
                g.setColor( new JButton().getBackground());
                g.fillRect(Math.min(x2,x3),Math.min(y2,y3),Math.abs(x3-x2),Math.abs(y3-y2));
                g.setColor(pre);
                break;
        }
    }
    //遞回KLine
    public void KLine(int x1 , int y1 , int x2 , int y2, int x){
      if(Math.abs(x2-x1)<= 1 || Math.abs(y2-y1) <= 1 || x < 1){
        g.drawLine(x1, y1, x2, y2);
        specialList.add(new Point(x1,y1));

        return;
      }
      Random random = new Random(0);
      int ran = random.nextInt(x);
      int mid = ((y2+y1)/2-x+ran*2);
      x = (int)(x*0.618);
      KLine(x1, y1, (x1+x2)/2, mid,x);
      KLine((x1+x2)/2, mid, x2,y2,x);

    }
    //謝爾賓斯基地毯
    public void Sierpinski(int x,int y,int w,int h){
        if(w>0&&h>0){
            g.fillRect(x+w/3,y+h/3,w/3,h/3);
            Sierpinski(x,y,w/3,h/3);
            Sierpinski(x+w/3,y,w/3,h/3);
            Sierpinski(x+2*w/3,y,w/3,h/3);
            Sierpinski(x,y+h/3,w/3,h/3);
            Sierpinski(x+2*w/3,y+h/3,w/3,h/3);
            Sierpinski(x,y+2*h/3,w/3,h/3);
            Sierpinski(x+w/3,y+2*h/3,w/3,h/3);
            Sierpinski(x+2*w/3,y+2*h/3,w/3,h/3);
        }
    }
    
}

較復雜一點的圖形功能:簽字筆、實時直線、三角形、多邊形、改進多邊形的實作

  • 簽字筆:滑鼠拖動時一直獲取坐標,并將這個坐標與上一個坐標連接
  • 實時直線:滑鼠按下時獲取一個坐標,然后拖動時獲取實時坐標連線,并將上一條線用一條背景色的直線覆寫,
  • 三角形:滑鼠點擊時獲取坐標①,再次點擊獲取坐標②,并將①②連接,再次點擊獲取坐標③,并將①③,②③連接,
  • 多邊形:第一次點擊獲取坐標①,此后每次點擊獲取坐標n,并連接坐標n和前一次點擊獲取的坐標,最后點擊右鍵,連接坐標①和最后一次左鍵點擊的坐標
  • 改進多邊形:滑鼠點擊n次,然后用這個n個點作為頂點,畫出一個多邊形,
  • 由于簽字筆、三角形、多邊形、改進多邊形的實作比較復雜,所以我們將他們作為一個獨立的類來寫,我們的代碼也更容易拓展和維護,
    此時,我們的監聽器的代碼更新為
  • 三角形類
package drawBoard_test;

import java.awt.*;
import java.awt.event.MouseEvent;

public class Triangle {
  static int x1,y1,x2,y2,x3,y3; //對應三角形的三個點
  static int num; //作為已經點了幾個點的控制信號
  public void drawTriangle(MouseEvent e , Graphics g){
    if(num == 0){
      x1 = e.getX();
      y1 = e.getY();
      num++;
    }else if(num == 1){
      x2 = e.getX();
      y2 = e.getY();
      g.drawLine(x1,y1,x2,y2);
      num++;
    }else if(num == 2){
      x3 = e.getX();
      y3 = e.getY();
      g.drawLine(x3,y3,x2,y2);
      g.drawLine(x3,y3,x1,y1);
      num=0;
    }
  }
}

  • 多邊形類
package drawBoard_test;

import java.awt.*;
import java.awt.event.MouseEvent;

public class Polygon {
    static int x1,y1,x2,y2,x3,y3;
    static int num;
    public void drawPolygon(MouseEvent e , Graphics g){
        if(num == 0){
            x1 = e.getX();
            y1 = e.getY();
            num++;
        }else if(num == 1){
            x2 = e.getX();
            y2 = e.getY();
            g.drawLine(x1,y1,x2,y2);
            num++;
        }else if (num == 2){
            if(e.getButton()==3){
                g.drawLine(x1,y1,x2,y2);
                num=0;
                return;
            }
            x3 = e.getX();
            y3 = e.getY();
            g.drawLine(x3,y3,x2,y2);
            num++;
        }else if(num == 3){
            if(e.getButton()==3){
                g.drawLine(x1,y1,x3,y3);
                num=0;
                return;
            }
            x2 = e.getX();
            y2 = e.getY();
            g.drawLine(x3,y3,x2,y2);
            num--;
        }
    }
}

  • 改進多邊形類
package drawBoard_test;

import java.awt.*;
import java.util.ArrayList;

public class PolygonPro {
    //挑選x坐標最大的點作為基準點,計算其余點與基準點的正切值,根據正切值從大到小依次連接,得到一個多邊形,
    public void drawPolygonPro(ArrayList<Point> list, Graphics g){
        if(list.size() == 0||list.size() == 1||list.size() == 2) return;
        int right = findRight(list);
        System.out.println(right);
        Point rightPoint  = new Point(list.get(right).x, list.get(right).y);

        list.remove(right);
        double[] tan = new double[list.size()];
        for (int i = 0; i < list.size(); i++) {
            tan[i] = ((double) rightPoint.y-list.get(i).y)/(rightPoint.x-list.get(i).x);
        }
        int pre;
        int cur = indexOfMax(tan);

        g.drawLine(list.get(cur).x,list.get(cur).y, rightPoint.x, rightPoint.y);
        tan[cur] = Integer.MIN_VALUE;
        for (int i = 0; i < tan.length-1; i++) {
            pre = cur;
            cur = indexOfMax(tan);

            g.drawLine(list.get(pre).x,list.get(pre).y, list.get(cur).x,list.get(cur).y);
            tan[cur] = Integer.MIN_VALUE;
        }
        g.drawLine(list.get(cur).x,list.get(cur).y, rightPoint.x, rightPoint.y);

    }

    private int findRight(ArrayList<Point> list) {
        int result = 0;
        for (int i = 1; i < list.size(); i++) {
            result = list.get(i).x>list.get(result).x?i:result;
        }
        return result ;
    }

    //回傳陣列中的最大值的下標
    private int indexOfMax(double[] tan){
        int v= 0 ;
        for(int i = 1 ; i < tan.length; i ++){
            v = tan[i]>tan[v]?i:v;
        }
        return v;
    }
}
  • 簽字筆類
package drawBoard_test;

import java.awt.*;
import java.awt.event.MouseEvent;

public class Pen {
    public static int x1,y1,x2,y2;
    public static int state = 1;

    public void draw(MouseEvent e , Graphics g) {
        switch(state){
            case 1 :
                x1 = e.getX();
                y1 = e.getY();
                state = 2;
                break;
            case 2 :
                x2 = e.getX();
                y2 = e.getY();
                g.drawLine(x2,y2,x1,y1);
                state = 3;
                break;
            case 3 :
                x1 = e.getX();
                y1 = e.getY();
                g.drawLine(x2,y2,x1,y1);
                state = 2;
                break;
        }
    }
}

  • 實時直線類
package drawBoard_test;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;

public class RealLine {
    public static int x1,y1,x2,y2,x3,y3;
    public void draw(MouseEvent e , Graphics g){
        Color pre = g.getColor();
        g.setColor( new JButton().getBackground());
        if(x2 !=0 ){
            g.drawLine(x2,y2,x1,y1);
        }
        g.setColor(pre);
        x3 = e.getX();
        y3 = e.getY();
        g.drawLine(x3,y3,x1,y1);
        x2=x3;
        y2=y3;
    }
}

監聽器DrawListener中的代碼可以參考以下代碼

ArrayList<Point> list = new ArrayList<>();//用于存放改進多邊形的所有的頂點,

@Override
    public void mouseClicked(MouseEvent e) {
        if(shapeName == null) return;
        switch(shapeName){
            case "三角形" :
                new Triangle().drawTriangle(e,g);
                break;
            case "多邊形":
                new Polygon().drawPolygon(e,g);
                break;
            case "改進多邊形":
                if(e.getButton()==3){
                    new PolygonPro().drawPolygonPro(list,g);
                    list.clear();
                    break;
                }else{
                    Point point = new Point(e.getX(),e.getY());
                    list.add(point);
                    break;
                }
            default:
                break;
        }
    }
    @Override
    public void mouseDragged(MouseEvent e) {
        if(shapeName == null) return;
        switch (shapeName){
            case "實時直線":
                new RealLine().draw(e,g);
                break;
            case "簽字筆":
                new Pen().draw(e,g);
                break;
        }
    }
    @Override
    public void mousePressed(MouseEvent e) {
        x2 = e.getX();
        y2 = e.getY();
        if(shapeName == null) return;
        switch (shapeName){
            case "實時直線":
                RealLine.x1 = e.getX();
                RealLine.y1 = e.getY();
                RealLine.x2 = 0;
                break;
        }
    }

第四步:實作重繪

到這里,我們的畫圖板的雛形已經完成了,但是也存在以下幾個問題:

  • ①當表單發生變動(放大、表單大小發生改變)時,已經繪制好的圖形就會消失.
  • ②我們在使用實時直線的時候,繪制程序中會將其他圖形擦掉,
    image

如何解決這些問題呢?

我們可以把每個的圖形看作一個類,再用List集合把它們存盤起來,然后重寫主頁面的paint方法(paint方法會在表單初
始化、拖動、改變尺寸、移出螢屏、最小化、最大化時呼叫),將List中的圖形 在這個方法中遍歷繪制出來,

  • 具體實作方法
@Override
    public void paint(Graphics g){
        super.paint(g);
        for(Shapes shape : dl.shapeList){
            shape.drawShape(g);
        }
    }

由于ArrayList只能存放一種物件,所以我們先創建一個父類shape,讓shape的子類去重寫drawShape方法,
在paint方法中遍歷ArrayList集合時,每個物件呼叫自己獨特的的drawShape方法,實作重繪,

  • 我們將具有相同屬性的圖形定義為一個相同的類,例如直線、矩形、圓、謝爾賓斯基地毯、實心矩形、 實心圓、等腰三角形、
    立方體、橡皮擦等圖形,只需要兩個點的坐標,即可繪制成功,所以我們定義一個BasicShape類,然后重寫drawShape
    方法來繪制它們
  • shapes類(父類)
package drawBoard_test2;

import java.awt.Color;
import java.awt.Graphics;
public class Shapes {
    public String shapeName; // 圖形的名稱(要根據圖形的名稱,判斷重繪的方法)
    public Color color;  //畫筆顏色(每個圖形都有自己的顏色,重繪的時候圖形的顏色也一樣要保留)

    public void drawShape (Graphics g){
        g.setColor(color);

    }
}
  • BasicShape類
package drawBoard_test2;

import javax.swing.*;
import java.awt.*;

public class BasicShape extends Shapes {
    private int x1,y1,x2,y2;

    public BasicShape(String shapeName, Color color,int x1, int y1, int x2, int y2) {
        this.shapeName = shapeName;
        this.color = color;
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }
    @Override
    public void drawShape (Graphics g){
        super.drawShape(g);
        switch (shapeName){
            case "直線":
                g.setColor(color);
                g.drawLine(x1,y1,x2,y2);
                break;
            case "矩形":
                g.drawRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x2-x1),Math.abs(y2-y1));
                break;
            case "圓" :
                g.drawOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x2-x1),Math.abs(y2-y1));
                break;
            case "謝爾賓斯基地毯" :
                Sierpinski(g,Math.min(x1,x2),Math.min(y1,y2),Math.abs(x2-x1),Math.abs(y2-y1));
                break;

            case "實心矩形" :
                g.fillRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x2-x1),Math.abs(y2-y1));
                break;

            case "實心圓" :
                g.fillOval(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x2-x1),Math.abs(y2-y1));
                break;

            case "等腰三角形" :
                g.drawLine(x1,y2,x2,y2);
                g.drawLine(x1,y2,(x1+x2)/2,y1);
                g.drawLine(x2,y2,(x1+x2)/2,y1);
                break;
            case "立方體" :
                g.drawRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x2-x1),Math.abs(y2-y1));
                g.drawLine(x1+(int)((x2-x1)*1.414/4),y1-(int)((y2-y1)*1.414/4),x1,y1);
                g.drawLine(x1+(int)((x2-x1)*1.414/4),y1-(int)((y2-y1)*1.414/4),x2+(int)((x2-x1)*1.414/4),y1-(int)((y2-y1)*1.414/4));
                g.drawLine(x2,y1,x2+(int)((x2-x1)*1.414/4),y1-(int)((y2-y1)*1.414/4));
                g.drawLine(x2+(int)((x2-x1)*1.414/4),y2-(int)((y2-y1)*1.414/4),x2+(int)((x2-x1)*1.414/4),y1-(int)((y2-y1)*1.414/4));
                g.drawLine(x2+(int)((x2-x1)*1.414/4),y2-(int)((y2-y1)*1.414/4),x2,y2);
                break;

            case "橡皮擦" :
                Color pre = g.getColor(); //記錄之前的顏色 ,用完再換回去
                g.setColor( new JButton().getBackground());
                g.fillRect(Math.min(x1,x2),Math.min(y1,y2),Math.abs(x2-x1),Math.abs(y2-y1));
                g.setColor(pre);
                break;

            default:
                break;
        }
    }
    public void Sierpinski(Graphics g,int x,int y,int w,int h){
        if(w>0&&h>0){
            g.fillRect(x+w/3,y+h/3,w/3,h/3);
            Sierpinski(g,x,y,w/3,h/3);
            Sierpinski(g,x+w/3,y,w/3,h/3);
            Sierpinski(g,x+2*w/3,y,w/3,h/3);
            Sierpinski(g,x,y+h/3,w/3,h/3);
            Sierpinski(g,x+2*w/3,y+h/3,w/3,h/3);
            Sierpinski(g,x,y+2*h/3,w/3,h/3);
            Sierpinski(g,x+w/3,y+2*h/3,w/3,h/3);
            Sierpinski(g,x+2*w/3,y+2*h/3,w/3,h/3);
        }

    }
}

當繪制出一個圖形時,要將該圖形加入到List集合中,所以監聽器中的代碼參考以下代碼

@Override
    public void mouseReleased(MouseEvent e) {
        x3 = e.getX();
        y3 = e.getY();
        if(shapeName == null) return;
        switch(shapeName){
            case "直線":
            case "矩形":
            case "圓" :
            case "謝爾賓斯基地毯":
            case "實心矩形" :
            case "實心圓" :
            case "等腰三角形" :
            case "立方體" :
            case "橡皮擦" :
                BasicShape basicShape = new BasicShape(shapeName, new Color(color.getRGB()), x2, y2, x3, y3);
                basicShape.drawShape(g);
                shapeList.add(basicShape);
                break;
        }
    }

至此,我們就完成了簡單圖形的重繪,
我們還剩簽字筆、實時直線、遞回KLine、三角形、多邊形、改進多邊形等圖形需要繪制,

這些圖形有什么共同的屬性可以提取嗎?他們的共同點是坐標點都比較多,數量不能確定,我們可以設定一個List屬性,把每個圖形的點
都存在這個集合里, 然后重繪時,呼叫drawShape方法把集合里的點取出來,再繪制出來,

說做就做,我們創建一個specialShape類,主要屬性為一個ArrayList集合,其余屬性根據繪制的需要來定,

package drawBoard_test2;

import java.awt.*;
import java.util.ArrayList;

public class SpecialShape extends Shapes {
    public ArrayList<Point> specialList = new ArrayList<>();
    private Point first;
    private Point pre;
    private Point cur;

    public SpecialShape(String shapeName, Color color, ArrayList<Point> specialList) {
        this.shapeName = shapeName;
        this.color = color;
        for (Point p : specialList) {
            this.specialList.add(p);
        }
    }

    @Override
    public void drawShape(Graphics g) {
        super.drawShape(g);
        switch (shapeName) {
            case "三角形":
            case "多邊形":
            case "改進多邊形":
                if (specialList.isEmpty()) break;
                int i = 0;
                first = specialList.get(i++);
                cur = first;
                while (i < specialList.size()) {
                    pre = cur;
                    cur = specialList.get(i++);
                    g.drawLine(pre.x, pre.y, cur.x, cur.y);
                }
                g.drawLine(first.x, first.y, cur.x, cur.y);
                break;
            case "簽字筆":
            case "遞回KLine":
            case "實時直線":
                if (specialList.isEmpty()) break;
                int j = 0;
                while (j < specialList.size()-1) {
                    g.drawLine(specialList.get(j).x, specialList.get(j).y, specialList.get(j+1).x, specialList.get(++j).y);
                }
                break;
        }
    }
}

接下來,我們需要做的就是將每個圖形的點按順序添加進specialList中,點都收集完之后,將一個新建的specialShape物件放入我們的圖形集合ShapeList中,所以我們修改每個圖形中的代碼:

  • 三角形類的代碼參考:
/**
 * @param specialList 三角形的頂點存入SpecialShape的集合,存入的順序應該為順次連接的點的順序
 * @param shapeList 重繪時使用的圖形集合
 */
    public void drawTriangle(MouseEvent e , Graphics g, ArrayList<Point> specialList, Color color, ArrayList<Shapes> shapeList){
        if(num == 0){
        specialList.clear();
        x1 = e.getX();
        y1 = e.getY();
        num++;
        specialList.add(new Point(x1,y1));
        }else if(num == 1){
        x2 = e.getX();
        y2 = e.getY();
        g.drawLine(x1,y1,x2,y2);
        num++;
        specialList.add(new Point(x2,y2));
        }else if(num == 2){
        x3 = e.getX();
        y3 = e.getY();
        g.drawLine(x3,y3,x2,y2);
        g.drawLine(x3,y3,x1,y1);
        num=0;
        specialList.add(new Point(x3,y3));
        SpecialShape specialShape = new SpecialShape("三角形", new Color(color.getRGB()), specialList);
        shapeList.add(specialShape);
        }
    }

相應的監聽器中的代碼,做出相應的修改,

/**
 * 創建一個specialList集合用來存放每個圖形的點,將它傳入圖形的繪制方法中,
 * 當收集到所有的點時,將以集合作為成員變數創建的specialShape物件存入shape集合中,
 */

ArrayList<Point> specialList = new ArrayList<>();

case "三角形" :
    new Triangle().drawTriangle(e,g,specialList,color,shapeList);
    break;

其他的類的方法也是如出一轍,大家在寫出來之后,可以和鄙人的代碼進行比對,這里給出其余代碼:

  • 多邊形
public class Polygon {
  static int x1,y1,x2,y2,x3,y3;
  static int num;

  /**
   * 
    * @param e
   * @param g 
   * @param specialList 多邊形的頂點存入SpecialShape的集合,存入的順序應該為順次連接的點的順序
   * @param color 
   * @param shapeList 重繪時使用的圖形集合
   */  
  public void drawPolygon(MouseEvent e , Graphics g, ArrayList<Point> specialList, Color color, ArrayList<Shapes> shapeList){
    if(num == 0){  //第一個點
      x1 = e.getX();
      y1 = e.getY();
      num++;
      specialList.clear();
      specialList.add(new Point(x1,y1));
    }else if(num == 1){ //
      x2 = e.getX();
      y2 = e.getY();
      g.drawLine(x1,y1,x2,y2);
      num++;
      specialList.add(new Point(x2,y2));
    }else if (num == 2){
      if(e.getButton()==3){ //右鍵結束時,所有的點已經確定,我們新建一個specialShape物件存入specialList集合中,
        g.drawLine(x1,y1,x2,y2);
        num=0;
        SpecialShape specialShape = new SpecialShape("多邊形", new Color(color.getRGB()), specialList);
        shapeList.add(specialShape);
        specialList.clear();
        return;
      }
      x3 = e.getX();
      y3 = e.getY();
      g.drawLine(x3,y3,x2,y2);
      specialList.add(new Point(x3,y3));
      num++;
    }else if(num == 3){
      if(e.getButton()==3){
        g.drawLine(x1,y1,x3,y3);
        num=0;
        SpecialShape specialShape = new SpecialShape("多邊形", new Color(color.getRGB()), specialList);
        shapeList.add(specialShape);
        specialList.clear();
        return;
      }
      x2 = e.getX();
      y2 = e.getY();
      g.drawLine(x3,y3,x2,y2);
      specialList.add(new Point(x2,y2));
      num--;
    }
  }
}
/**
 * 多邊形對應監聽器中的方法
 * mouseClicked方法
 */
case "多邊形":
        new Polygon().drawPolygon(e,g,specialList,color,shapeList);
        break;

  • 改進多邊形
/**
 * 改進多邊形類的draw方法
 * @param ArrayList<Point> list 多邊形頂點的集合,順序為滑鼠繪制時 點擊的順序
 * @param ArrayList<Point> specialList 多邊形的頂點存入SpecialShape的集合,存入的順序應該為順次連接的點的順序
 * @param ArrayList<Shapes> shapeList 重繪時使用的圖形集合
 */
public void drawPolygonPro(ArrayList<Point> list, Graphics g, ArrayList<Point> specialList, Color color, ArrayList<Shapes> shapeList){
        if(list.size() == 0||list.size() == 1||list.size() == 2) return;
        int right = findRight(list);
        System.out.println(right);
        Point rightPoint  = new Point(list.get(right).x, list.get(right).y);
        specialList.add(rightPoint);
        list.remove(right);
        double[] tan = new double[list.size()];
        for (int i = 0; i < list.size(); i++) {
        tan[i] = ((double) rightPoint.y-list.get(i).y)/(rightPoint.x-list.get(i).x);
        }
        int pre;
        int cur = indexOfMax(tan);
        specialList.add(list.get(cur));
        g.drawLine(list.get(cur).x,list.get(cur).y, rightPoint.x, rightPoint.y);
        tan[cur] = Integer.MIN_VALUE;
        for (int i = 0; i < tan.length-1; i++) {
        pre = cur;
        cur = indexOfMax(tan);
        specialList.add(list.get(cur));
        g.drawLine(list.get(pre).x,list.get(pre).y, list.get(cur).x,list.get(cur).y);
        tan[cur] = Integer.MIN_VALUE;
        }
        g.drawLine(list.get(cur).x,list.get(cur).y, rightPoint.x, rightPoint.y);

        }
/**
 * 改進多邊形對應監聽器中的方法
 */
    case "改進多邊形":
        if(e.getButton()==3){

        new PolygonPro().drawPolygonPro(list,g,specialList,color,shapeList);
        SpecialShape specialShape = new SpecialShape("改進多邊形",color,specialList);
        shapeList.add(specialShape);
        specialList.clear();
        list.clear();
        break;
        }else{
        Point point = new Point(e.getX(),e.getY());
        list.add(point);
        break;
        }

  • 遞回KLine曲線
case "遞回KLine":
        KLine(x2,y2,x3,y3,Math.abs(y3-y2));
        specialList.add(new Point(x3,y3));
        SpecialShape specialShape = new SpecialShape(shapeName, new Color(color.getRGB()), specialList);
        shapeList.add(specialShape);
        specialList.clear();
        break;
/**
 * 遞回Kline實作方法
 */
public void KLine(int x1 , int y1 , int x2 , int y2, int x){
        if(Math.abs(x2-x1)<= 1 || Math.abs(y2-y1) <= 1 || x < 1){
        g.drawLine(x1, y1, x2, y2);
        specialList.add(new Point(x1,y1));

        return;
        }
        Random random = new Random(0);
        int ran = random.nextInt(x);
        int mid = ((y2+y1)/2-x+ran*2);
        x = (int)(x*0.618);
        KLine(x1, y1, (x1+x2)/2, mid,x);
        KLine((x1+x2)/2, mid, x2,y2,x);

        }

  • 簽字筆類
/**
 * 簽字筆類的代碼修改
 */
public void draw(MouseEvent e , Graphics g, ArrayList<Point> specialList, Color color, ArrayList<Shapes> shapeList) {
        switch(state){
        case 1 :
        x1 = e.getX();
        y1 = e.getY();
        specialList.add(new Point(x1,y1));
        state = 2;
        break;
        case 2 :
        x2 = e.getX();
        y2 = e.getY();
        specialList.add(new Point(x2,y2));
        g.drawLine(x2,y2,x1,y1);
        state = 3;
        break;
        case 3 :
        x1 = e.getX();
        y1 = e.getY();
        specialList.add(new Point(x1,y1));
        g.drawLine(x2,y2,x1,y1);
        state = 2;
        break;
        }

        }

/**
 * mouseDragged
  */
case "簽字筆":
        new Pen().draw(e,g,specialList,color,shapeList);
        break;

/**
 * mouseReleased
 */
case "簽字筆" :
        SpecialShape specialShape2 = new SpecialShape(shapeName, new Color(color.getRGB()), specialList);
        shapeList.add(specialShape2);
        specialList.clear();
        Pen.state=1;
        break;

  • 實時直線類
public class RealLine {
  public static int x1,y1,x2,y2,x3,y3;
  public void draw(MouseEvent e , Graphics g, ArrayList<Point> specialList, Color color, ArrayList<Shapes> shapeList){
    Color pre = g.getColor();
    g.setColor( new JButton().getBackground());
    if(x2 !=0 ){
      g.drawLine(x2,y2,x1,y1);
    }
    g.setColor(pre);
    x3 = e.getX();
    y3 = e.getY();
    g.drawLine(x3,y3,x1,y1);
    x2=x3;
    y2=y3;
  }
}
/**
 * mousePressed
 */
    case "實時直線":
        specialList.add(new Point(x2,y2));
        RealLine.x1 = e.getX();
        RealLine.y1 = e.getY();
        RealLine.x2 = 0;
        break;

/**
 * mouseReleased
  */
    case "實時直線":
        specialList.add(new Point(x3,y3));
        SpecialShape specialShape3 = new SpecialShape(shapeName, new Color(color.getRGB()), specialList);
        shapeList.add(specialShape3);
        specialList.clear();
        break;

接下來,我們發現,圖形確實可以實作重繪了,但是每次呼叫paint方法時,繪制的速度總是很慢,尤其是重繪謝爾賓斯基地毯時,是肉眼可見的慢,這是什么原因導致的呢?

我們知道,繪制的內容要顯示到螢屏上,需要把 記憶體資料 提交 給顯卡 ,通過顯卡再渲染計算 顯示到螢屏,
計算機的計算速度是非常快的,但是我們每計算出幾個像素點,就直接輸出到螢屏上,以至于 要畫的次數很多,這導致了計算機IO 與 計算不匹配,

我們如何解決這種問題?

計算快,但IO很慢,我們就讓計算機先計算好,再輸出到螢屏上,
我們使用 快取(BufferedImage類),把下一幀需要顯示的畫面上所有的圖形內容都計算好并存起來,然后再一次性繪出 ,

BufferedImage 快取圖片 屬性:寬、高 格式為像素存盤格式 使用Graphics類作為畫筆

  • 來看迭代后的paint的代碼實作
    public void paint(Graphics g){
        super.paint(g);
        BufferedImage bufferedImage = new BufferedImage(1000,800,BufferedImage.TYPE_INT_ARGB);
        Graphics buffg = bufferedImage.getGraphics();
        for(Shapes shape : dl.shapeList){
            shape.drawShape(buffg);

        }
        g.drawImage(bufferedImage,0,0,null);
    }

此時再來試試重繪的功能,是不是感覺很神奇,

  • 我們還有一個未解決的問題,就是實時直線拖動時會擦掉畫板上其他圖形,這如何解決呢?
  • 解決方法:在實時直線的繪制程序中,不斷地進行重繪,把被擦掉的像素點補回來,
public class RealLine {
    public static int x1,y1,x2,y2,x3,y3;
    public void draw(MouseEvent e , Graphics g, ArrayList<Point> specialList, Color color, ArrayList<Shapes> shapeList){
        Color pre = g.getColor();
        g.setColor( new JButton().getBackground());
        if(x2 !=0 ){
            g.drawLine(x2,y2,x1,y1);
        }
        g.setColor(pre);
        x3 = e.getX();
        y3 = e.getY();
        g.drawLine(x3,y3,x1,y1);
        x2=x3;
        y2=y3;
        BufferedImage bufferedImage = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
        Graphics buffs = bufferedImage.getGraphics();
        for(Shapes shape : shapeList){
            shape.drawShape(buffs);
        }
        g.drawImage(bufferedImage,0,0,null);
    }
}

第五步:實作撤回,清空功能

  • 我們已經實作了重繪功能,撤回就很簡單了,我們只需要把shapeList中最近添加進去的圖形刪掉,然后重繪就可以了,
  • 清空就是把shapeList中所有的圖形刪掉,然后重繪,
  • 代碼實作:
/**
 * actionPerformed
 */

switch(shapeName) {
    case "撤回":
        if (!shapeList.isEmpty()) {
        shapeList.remove(shapeList.size() - 1);
        drawUI.paint(g);
        }
        break;
    case "清空" :
        shapeList.clear();
        drawJPanel.paint(g);
        break;
}

/**
 * 然后我們需要涉及到傳值的問題,我們在監聽器頁面添加一個drawUI物件成員,然后把DrawUI類中的main函式中的drawUI物件傳給監聽器
  */

第六步:打開與保存操作

  • 兩點需要注意:
  • ①為了加快打開圖片的速度,我們把圖片需要顯示的畫面都畫在BufferedImage中,然后再一次性繪出,
  • ②我們繪制的圖形可以實作撤回功能,那么我們打開的圖片能不能也實作撤回功能呢?
  • 當然可以,我們只需要把打開的圖片也存入ShapeList集合中,所以我們創建一個ImageShape類(繼承Shape類),用來存盤圖片,
  • ImageShape類
package drawBoard_test2;

import java.awt.*;
import java.awt.image.BufferedImage;

public class ImageShape extends Shapes {
    BufferedImage bufferedImage;
    
    @Override //重繪方法
    public void drawShape(Graphics g){
        g.drawImage(bufferedImage,0,0,null);
    }
    //封裝 BufferedImage的set方法
    public void setBufferedImage(BufferedImage bufferedImage) {
        this.bufferedImage = bufferedImage;
    }
}
  • 打開
String fileName;

/**
 * 打開操作步驟:將圖片轉化為二維陣列,遍歷每個點在畫圖板上畫出
 * JFileChooser 檔案選擇器
 * FileNameExtensionFilter 檔案過濾器,構造方法的引數JPG & GIF Images為篩選檔案的選項, "jpg", "gif"為篩選檔案的型別
 * 
 */
    //actionPerformed
    case "打開" :
        JFileChooser chooser = new JFileChooser(); 
        FileNameExtensionFilter filter = new FileNameExtensionFilter (
        "JPG & GIF Images", "jpg", "gif");
        chooser.setFileFilter(filter);
        int returnVal = chooser.showOpenDialog(null);
        if(returnVal == JFileChooser.APPROVE_OPTION) { //JFileChooser.APPROVE_OPTION 批準選項
        System.out.println("You chose to open this file: " +
        chooser.getSelectedFile().getPath());
        fileName = chooser.getSelectedFile().getPath(); //獲取檔案的本地路徑
        }
        BufferedImage bufferedImage = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
        Graphics buffg = bufferedImage.getGraphics();
        int[][] img = getImagePixel(fileName);
        drawImage(buffg,img);
        ImageShape imageShape = new ImageShape();
        imageShape.setBufferedImage(bufferedImage);
        g.drawImage(bufferedImage,0,0,null);
        shapeList.add(imageShape);
        break;
/**
 * drawImage將圖形畫在畫圖板上
  */
public  void drawImage(Graphics g ,int[][] img){

    for (int i = 0; i < img.length; i++) {
        for (int j = 0; j < img[i].length; j++) {
            Color c = new Color(img[i][j]);
            g.setColor(c);
            g.drawOval(i , j, 1, 1);
        }
    }
}
/**
 * getImagePixel 回傳圖片的二維陣列

 */
public static int[][] getImagePixel(String filePath) {

    File file = new File(filePath); //filePath為檔案路徑
    BufferedImage bi = null;
    try{
        bi = ImageIO.read(file);
    } catch (Exception e) {
        e.printStackTrace();
    }
    int w = bi.getWidth();
    int h = bi.getHeight();
    int[][] imIndex = new int[w][h];
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            int pixel = bi.getRGB(i,j);
            imIndex[i][j] = pixel;
        }
    }
    return imIndex;
}

}

return imIndex;

}
  • 保存
/**
 * 保存為的檔案名的后綴應為png
 */
case "保存":
    JFileChooser chooser2 = new JFileChooser();
    FileNameExtensionFilter filter2 = new FileNameExtensionFilter(
            "JPG & GIF Images", "jpg","gif"
        );
    chooser2.setFileFilter(filter2);
    int returnVal2 = chooser2.showSaveDialog(null);
    if(returnVal2 == JFileChooser.APPROVE_OPTION){
        System.out.println("You choose to save this file:" +
        chooser2.getSelectedFile().getPath());
    }
    //把所有的圖形重繪到bufferedImage上,再把bufferedImage存入圖片檔案中
    BufferedImage bufferedImage2 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
    Graphics buffg2 = bufferedImage2.getGraphics();
    for(Shape shape : shapeList ){
        shape.drawShape(buffg2);
    }
    File file2 = new File(chooser2.getSelectedFile().getPath());
    try {
        ImageIO.write(bufferedImage2,"png",file2);
    } catch (IOException ex) {
        ex.printStackTrace();
    }
    break;

第七步:美化界面,并添加圖片處理功能按鈕

此時我們的畫布、圖形按鈕、顏色按鈕放在一起,如果我們后面再加入圖形處理按鈕,界面將會變得很不整潔,
所以我們使用邊框布局來將表單磁區管理,

我們要將畫板從整個表單改成了一個JPanel,但是我們的重繪功能還需要重寫過的paint方法,所以我們新建一個DrawJPanel類
來繼承JPanel類,去重寫paint方法,

  • 界面效果:
    image
public class DrawUI extends JFrame {
    DrawListener dl = new DrawListener();

    String[] strs = {"直線","簽字筆","實時直線", "謝爾賓斯基地毯","遞回KLine","矩形", "圓", "實心矩形", "實心圓", "等腰三角形", "三角形", "多邊形",
            "改進多邊形","立方體",  "橡皮擦", "撤回", "保存", "打開"};
    Color[] color = {Color.red,Color.yellow,Color.black,Color.blue};
    public void addShapeButton(JComponent component){
        for(String str : strs){
            JButton btn = new JButton(str);
            btn.addActionListener(dl);
            component.add(btn);
        }
    }
    public void addColorButton(JComponent component){
        Dimension dim = new Dimension(30,30);
        for(Color c : color){
            JButton btn = new JButton();
            btn.setBackground(c);
            btn.setPreferredSize(dim);
            btn.addActionListener(dl);
            component.add(btn);
        }
        Dimension dim2 = new Dimension(95,30);
        JButton btn = new JButton("選擇顏色...");
        btn.setPreferredSize(dim2);
        btn.addActionListener(dl);
        component.add(btn);
    }
    public void addBeautyButton(JComponent component){
        String[] str = {"原圖","馬賽克","灰度","二值化","背景替換","油畫","圖片融合","磨皮"};
        for(String s : str){
            JButton btn = new JButton(s);
            btn.addActionListener(dl);
            component.add(btn);
        }
    }

    public void initUI(){
        JFrame jf = new JFrame("畫圖板");
        jf.setTitle("畫圖板");
        jf.setLayout(new BorderLayout());
        jf.setSize(1000,800);
        jf.setLocationRelativeTo(null);
        jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        //選單欄
        JMenuBar jMenuBar = new JMenuBar();
        JMenu jMenu = new JMenu("選單",true);
        String[] Menu = {"撤回","打開","保存","清空"};
        for(String s : Menu){
            JMenuItem jMenuItem = new JMenuItem(s);
            jMenu.add(jMenuItem);
            jMenuItem.addActionListener(dl);
        }
        jMenuBar.add(jMenu);
        jf.setJMenuBar(jMenuBar);

        JPanel shapeChooserPanel = new JPanel();
        DrawJPanel drawPanel = new DrawJPanel(); 
        JPanel ChooserPanel = new JPanel();
        JPanel ColorChooserPanel = new JPanel();
        ChooserPanel.setLayout(new BorderLayout());
        dl.drawJPanel = drawPanel;
        JPanel RightPanel = new JPanel();
        //大小
        Dimension dim = new Dimension(150,80);
        shapeChooserPanel.setPreferredSize(dim);
        ChooserPanel.setPreferredSize(dim);
        Dimension dim2 = new Dimension(150,330);
        RightPanel.setPreferredSize(dim2);
        ColorChooserPanel.setPreferredSize(dim2);
        ChooserPanel.setPreferredSize(dim2);
        //背景顏色
        Color color1 = new Color(-3355444);
        shapeChooserPanel.setBackground(color1);
        Color color2 = new Color(-6710887);
        ColorChooserPanel.setBackground(color2);
        ChooserPanel.setBackground(color2);
        RightPanel.setBackground(color1);
        //方位
        jf.add(shapeChooserPanel,BorderLayout.NORTH);
        jf.add(ChooserPanel,BorderLayout.EAST);
        jf.add(drawPanel,BorderLayout.CENTER);
        ChooserPanel.add(RightPanel,BorderLayout.SOUTH);
        ChooserPanel.add(ColorChooserPanel,BorderLayout.NORTH);

        //添加按鈕
        addShapeButton(shapeChooserPanel);
        addColorButton(ColorChooserPanel);
        addBeautyButton(RightPanel);
        jf.setVisible(true);
        Graphics g = drawPanel.getGraphics ();
        drawPanel.addMouseMotionListener(dl);
        drawPanel.addMouseListener(dl);
        drawPanel.setDl(dl);
        dl.setG(g);

    }

    public static void main(String[] args) {
        new DrawUI().initUI();
    }
}

  • 選擇顏色
/**
 * 監聽器中的actionPerformed方法
 */
if(btn_action.equals("選擇顏色...")){
            color = JColorChooser.showDialog(drawJPanel, "選擇顏色", Color.red);
            System.out.println(color.getRGB());
            g.setColor(color);
            return;
        }

第八步:影像處理功能

深入理解color類:

  • rgb數字構成顏色 Color c = new Color(200,50,100);其值在0~255之間,
  • rgb的三個數字分別對應red,green,blue
  • int數字構成顏色 Color c = new Color(-3355444),其值為int型別,
  • 馬賽克
/**
 * 馬賽克
 * 把像素點放大
 */
case "馬賽克":
        BufferedImage bufferedImage3 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
        Graphics buffg3 = bufferedImage3.getGraphics();
        int[][] img3 = getImagePixel(fileName);
        drawImage_MSK(buffg3,img3);

        ImageShape imageShape3 = new ImageShape();
        imageShape3.setBufferedImage(bufferedImage3);
        g.drawImage(bufferedImage3,0,0,null);
        shapeList.add(imageShape3);
        break;

public  void drawImage_MSK(Graphics g ,int[][] img){
    int w = (drawJPanel.getWidth()- img.length)/2;
    int h = (drawJPanel.getHeight()- img[0].length)/2;
    for (int i = 0; i < img.length; i+=8) {
        for (int j = 0; j < img[i].length; j+=8) {
            Color c = new Color(img[i][j]);
            g.setColor(c);
            g.fillRect(i+w , j+h, 8, 8);
        }
    }
}   

  • 灰度
/**
 * 灰度影像
 * rgb三個分量都相同,一般可以取其平均值
 * 這里使用的是灰度值的浮點法計算,讀者可以參考該網址,嘗試一下Gamma校正演算法
 * https://baike.baidu.com/item/%E7%81%B0%E5%BA%A6%E5%80%BC/10259111?fr=aladdin
 */
case "灰度":
    BufferedImage bufferedImage6 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
    Graphics buffg6 = bufferedImage6.getGraphics();
    int[][] img6 = getImagePixel(fileName);
    drawImage_gray(buffg6,img6);
    ImageShape imageShape6 = new ImageShape();
    imageShape6.setBufferedImage(bufferedImage6);
    g.drawImage(bufferedImage6,0,0,null);
    shapeList.add(imageShape6);
    break;

public  void drawImage_gray(Graphics g ,int[][] img){
    int w = (drawJPanel.getWidth()- img.length)/2;
    int h = (drawJPanel.getHeight()- img[0].length)/2;
    for (int i = 0; i < img.length; i++) {
        for (int j = 0; j < img[i].length; j++) {
            int value = https://www.cnblogs.com/classicltl/p/img[i][j];
            int red = (value>>16) & 0xff;
            int green = (value>>8) & 0xff;
            int blue = value & 0xff;
            int gray = (int) (0.3 * red + 0.59 * green + 0.11 * blue);
            Color c = new Color(gray,gray,gray);
            g.setColor(c);
            g.fillRect(i+w , j+h, 1, 1);
        }
    }
}
  • 二值化
/**
 * 二值影像
 * 指僅有黑白兩色的影像(大于某值的畫白,小于某值的畫黑)
 */
case "二值化":
    BufferedImage bufferedImage7 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
    Graphics buffg7 = bufferedImage7.getGraphics();
    int[][] img7 = getImagePixel(fileName);
    drawImage_binary(buffg7,img7);
    ImageShape imageShape7 = new ImageShape();
    imageShape7.setBufferedImage(bufferedImage7);
    g.drawImage(bufferedImage7,0,0,null);
    shapeList.add(imageShape7);
    break;

public  void drawImage_binary(Graphics g ,int[][] img){
    int w = (drawJPanel.getWidth()- img.length)/2;
    int h = (drawJPanel.getHeight()- img[0].length)/2;
    for (int i = 0; i < img.length; i++) {
        for (int j = 0; j < img[i].length; j++) {
            int value = https://www.cnblogs.com/classicltl/p/img[i][j];
            int red = (value>>16) & 0xff;
            int green = (value>>8) & 0xff;
            int blue = value & 0xff;
            int gray = (int) (0.3 * red + 0.59 * green + 0.11 * blue);
            if(gray < 150){
                g.setColor(Color.black);
            }else {
                g.setColor(Color.white);
            }
            g.fillRect(i+w , j+h, 1, 1);
        }
    }
}
  • 背景替換
/**
 * 背景替換影像
 * 當圖片的背景為白色時,我們將大于某一值的像素點,替換為另一張圖片的像素點
 */

case "背景替換":
    BufferedImage bufferedImage8 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
    Graphics buffg8 = bufferedImage8.getGraphics();
    int[][] img8 = getImagePixel(fileName);
    int[][] background = getImagePixel("C:\\Users\\13630\\Desktop\\背景.jpg"); 
    drawImage_replaceBackground(buffg8,img8,background);
    ImageShape imageShape8 = new ImageShape();
    imageShape8.setBufferedImage(bufferedImage8);
    g.drawImage(bufferedImage8,0,0,null);
    shapeList.add(imageShape8);
    break;

public  void drawImage_replaceBackground(Graphics g ,int[][] img,int[][] background){
    int w = (drawJPanel.getWidth()- img.length)/2;
    int h = (drawJPanel.getHeight()- img[0].length)/2;
    for (int i = 0; i < img.length; i++) {
        for (int j = 0; j < img[i].length; j++) {
            int value = https://www.cnblogs.com/classicltl/p/img[i][j];
            int red = (value>>16) & 0xff;
            int green = (value>>8) & 0xff;
            int blue = value & 0xff;
            int gray = (int) (0.3 * red + 0.59 * green + 0.11 * blue);
            if(gray > 240&&i< background.length&&j
  • 油畫
/**
 * 原理與馬賽克類似,不同的是油畫效果要填充隨機大小的色塊
 */
case "油畫":
    BufferedImage bufferedImage9 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
    Graphics buffg9 = bufferedImage9.getGraphics();
    int[][] img9 = getImagePixel(fileName);
    drawImage_OilPainting(buffg9,img9);
    ImageShape imageShape9 = new ImageShape();
    imageShape9.setBufferedImage(bufferedImage9);
    g.drawImage(bufferedImage9,0,0,null);
    shapeList.add(imageShape9);
    break;

public  void drawImage_OilPainting(Graphics g ,int[][] img){
    int w = (drawJPanel.getWidth()- img.length)/2;
    int h = (drawJPanel.getHeight()- img[0].length)/2;
    for (int i = 0; i < img.length; i+=5) {
        for (int j = 0; j < img[i].length; j+=5) {
            g.setColor(new Color(img[i][j]));
            Random random = new Random();
            int ran = random.nextInt(20)+5;
            g.fillOval(i+w , j+h, ran, ran);
        }
    }
}
  • 圖片融合
/**
 * 需要兩張照片
 * 融合后圖片像素點的顏色 為融合前的兩張照片像素點顏色以不同比例融合
 */
case "圖片融合":
    BufferedImage bufferedImage10 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
    Graphics buffg10 = bufferedImage10.getGraphics();
    int[][] img10 = getImagePixel(fileName);
    int[][] background2 = getImagePixel("C:\\Users\\13630\\Desktop\\背景.jpg");
    drawImage_fusion(buffg10,img10,background2);
    ImageShape imageShape10 = new ImageShape();
    imageShape10.setBufferedImage(bufferedImage10);
    g.drawImage(bufferedImage10,0,0,null);
    shapeList.add(imageShape10);
    break;

public  void drawImage_fusion(Graphics g ,int[][] img,int[][] background){
    int w = Math.min(img.length, background.length);
    int h = Math.min(img[0].length, background[0].length);
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            Color ca = new Color(img[i][j]);
            Color cb = new Color(background[i][j]);
            int red = (int) (ca.getRed()*0.7+ cb.getRed()*0.3);
            int green = (int)(ca.getGreen()* 0.3+cb.getGreen()*0.7);
            int blue = (int)(ca.getBlue()*0.3+ cb.getBlue()*0.7);
            Color c = new Color(red,green,blue);
            g.setColor(c);
            g.fillRect(i , j, 1, 1);
        }
    }
}
  • 原圖
case "原圖":
    BufferedImage bufferedImage5 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
    Graphics buffg5 = bufferedImage5.getGraphics();
    int[][] img5 = getImagePixel(fileName);
    drawImage(buffg5,img5);
    ImageShape imageShape5 = new ImageShape();
    imageShape5.setBufferedImage(bufferedImage5);
    g.drawImage(bufferedImage5,0,0,null);
    shapeList.add(imageShape5);
    break;
 //畫在畫圖區域的中央
public  void drawImage(Graphics g ,int[][] img){
    int w = (drawPanel.getWidth()- img.length)/2;
    int h = (drawPanel.getHeight()- img[0].length)/2;
    for (int i = 0; i < img.length; i++) {
        for (int j = 0; j < img[i].length; j++) {
            Color c = new Color(img[i][j]);
            g.setColor(c);
            g.drawOval(w+i , h+j, 1, 1);
        }
    }
}
  • 磨皮
    磨皮是為了把有瑕疵的地方覆寫住,所以我們用一種和周圍相同顏色的粗畫筆去覆寫圖片上的瑕疵,
  • 我們實時獲取滑鼠所在位置的顏色,然后畫出與此顏色相同的顏色,實作方式與簽字筆相同
  • 磨皮類
package drawBoard_test2;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

public class SkinGrinding {
  public static int x1,y1,x2,y2;
  public static int state = 1;

  public void draw(MouseEvent e , Graphics2D g, ArrayList<Point> specialList, int[][] img, ArrayList<Shapes> shapeList,int w,int h) {
    switch(state){
      case 1 :
        x1 = e.getX();
        y1 = e.getY();
        specialList.add(new Point(x1,y1));
        state = 2;
        break;
      case 2 :
        x2 = e.getX();
        y2 = e.getY();
        g.setColor(new Color(img[x2-w][y2-h]));
        specialList.add(new Point(x2,y2));
        g.drawLine(x2,y2,x1,y1);
        state = 3;
        break;
      case 3 :
        x1 = e.getX();
        y1 = e.getY();
        specialList.add(new Point(x1,y1));
        g.setColor(new Color(img[x1-w][y1-h]));
        g.drawLine(x2,y2,x1,y1);
        state = 2;
        break;
    }

  }
}

監聽器中添加的代碼

監聽器中加一個img11[][],用來存放當然處理的照片的像素點
/**
 * actionPerformed
 */
case "磨皮":
    img11 = getImagePixel(fileName);
    break;
/**
 * mousePressed
  */
case "磨皮":
    g2D = (Graphics2D)g;
    g2D.setStroke (new BasicStroke (3));
    specialList.add(new Point(x2,y2));
    break;
/**
 * mouseReleased
  */
case "磨皮":
    SpecialShape specialShape4 = new SpecialShape(shapeName, new Color(color.getRGB()), specialList);
    shapeList.add(specialShape4);
    specialList.clear();
    SkinGrinding.state=1;
    break;
/**
 * mouseDragged
  */
case "磨皮":
    int w = (drawJPanel.getWidth()- img11.length)/2;
    int h = (drawJPanel.getHeight()- img11[0].length)/2;
    new SkinGrinding().draw(e,g2D,specialList,img11,shapeList,w,h);
    break;

第九步:”更多操作“界面的繪制

  • 先看效果圖:
    22.cnblogs.com/blog/2555328/202204/2555328-20220414151122093-1753505041.png)
package drawBoard_test2;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;

public class ButtonUI extends JFrame {
    public static DrawUI drawUI;
    public void init (){
        JFrame jf = new JFrame();
        jf.setTitle("更多操作");
        jf.setSize(380,500);
        jf.setLocationRelativeTo(drawUI);
        jf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        jf.setLayout(new FlowLayout());
        addJSlider(jf);
        addButton(jf);
        addJSlider2(jf);
        jf.setVisible(true);

    }
    public void addButton (JFrame component){
        String[] strings = {"放大130%","縮小50%","向左旋轉","向右旋轉"};
        for(String s : strings){
            JButton btn = new JButton(s);
            component.add(btn);
            btn.addActionListener(DrawUI.dl);
        }

    }
    public void addJSlider(JFrame component){
        JLabel jl = new JLabel("縮放比例(%):");
        JSlider jSlider = new JSlider(0,200);
        jSlider.setToolTipText("縮放比例");
        jSlider.setMajorTickSpacing(30);
        jSlider.setMinorTickSpacing(10);
        jSlider.setPaintLabels(true);
        jSlider.setPaintTicks(true);
        jSlider.addChangeListener(DrawUI.dl);
        component.add(jl);
        component.add(jSlider);

    }
    public void addJSlider2(JFrame component){

        JLabel jl1 = new JLabel("紅色亮度(%):");
        JSlider jSlider1 = new JSlider(0,0,200,100);
        jSlider1.setToolTipText("紅色");
        jSlider1.setMajorTickSpacing(30);
        jSlider1.setMinorTickSpacing(10);
        jSlider1.setPaintLabels(true);
        jSlider1.setPaintTicks(true);
        jSlider1.addChangeListener(DrawUI.dl);
        component.add(jl1);
        component.add(jSlider1);

        JLabel jl2 = new JLabel("綠色亮度(%):");
        JSlider jSlider2 = new JSlider(0,0,200,100);
        jSlider2.setToolTipText("綠色");
        jSlider2.setMajorTickSpacing(30);
        jSlider2.setMinorTickSpacing(10);
        jSlider2.setPaintLabels(true);
        jSlider2.setPaintTicks(true);
        jSlider2.addChangeListener(DrawUI.dl);
        component.add(jl2);
        component.add(jSlider2);

        JLabel jl3 = new JLabel("藍色亮度(%):");
        JSlider jSlider3 = new JSlider(0,0,200,100);
        jSlider3.setToolTipText("藍色");
        jSlider3.setMajorTickSpacing(30);
        jSlider3.setMinorTickSpacing(10);
        jSlider3.setPaintLabels(true);
        jSlider3.setPaintTicks(true);
        jSlider3.addChangeListener(DrawUI.dl);
        component.add(jl3);
        component.add(jSlider3);

        //確認和取消按鈕;
        JButton btn1 = new JButton("確認");
        btn1.addActionListener(DrawUI.dl);
        component.add(btn1);

        JButton btn2 = new JButton("取消");
        btn2.addActionListener(DrawUI.dl);
        component.add(btn2);

    }

    public static void main(String[] args) {
        new ButtonUI().init();
    }
}

第十步:放大、縮小功能

  • 放大縮小的方法:
  • 獲取原圖形像素點的二維陣列,用最鄰近元法計算出待求像素點,再利用BufferedImage作為緩沖,畫到畫布上,
  • 最鄰近元法參考這個網站:影像插值_百度百科

@Override
public void stateChanged(ChangeEvent e) {
    JSlider jSlider = (JSlider)e.getSource();
    String s = jSlider.getToolTipText();
    switch (s){
        case "縮放比例":
            multiple =  jSlider.getValue();
            int[][] img = getImagePixel(fileName);
            BufferedImage bufferedImage = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
            Graphics buffg = bufferedImage.getGraphics();
            drawImage_multiple(buffg,img);
            g.drawImage(bufferedImage,0,0,null);
            break;
    }
}

public void drawImage_multiple(Graphics g , int[][] img){
    int w = (int)((drawJPanel.getWidth()- img.length*1.0*(multiple)/100)/2);
    int h = (int)((drawJPanel.getHeight()- img[0].length*1.0*multiple/100)/2);
    for (int i = 0; i < img.length; i++) {
        for (int j = 0; j < img[i].length; j++) {
            g.setColor(new Color(img[i][j]));
            for (int k = (int)(i*1.0*multiple/100); k < (int)((i+1)*1.0*multiple/100) ; k++) {
                for (int l = (int)(1.0*j*multiple/100); l < (int)((j+1)*1.0*multiple/100); l++) {
                    g.drawRect(k+w,l+h,1,1);
                }
            }
        }
    }
}

第十一步:圖片的顏色調整

  • 要實作的功能:通過滑動條,分別用來改變紅綠藍三種顏色的數值大小,來達到調整整個圖片顏色的效果
  • 實作途徑:自己撰寫一個存盤圖片的動態陣列類,將red,green,blue分別用一個矩陣陣列存盤起來,
package drawBoard_test2;

import javax.swing.text.Segment;
import java.awt.image.BufferedImage;
/**
 * 這是一個用來存盤圖片的動態陣列類 /可以實作陣列自動擴容
 * 存盤的圖片物件型別是: BufferedImage
 * 目前實作了:
 * add方法
 * get方法
 * remove方法
 * size方法
 */
public class ImageArray {

    private BufferedImage[] imgArray = {};

    /**
     * 陣列默認初始化容量
     */
    private static final int defaultLength = 10;
    
    private int  size;

    /**
     * 陣列當前的空間容量
     */
    private int length;
    
    // 每張存入進來圖片的三 通道矩陣陣列
    public ColorArray[] redArray = {};
    public ColorArray[] greenArray = {};
    public ColorArray[] blueArray = {};

    public int getSize(){
        return size;
    }

    //放大或縮小redArray的數值
    public int[][] multiple(int multiple , ColorArray colorArray){
        int w = colorArray.array.length;
        int h = colorArray.array[0].length;
        int[][] res = new int[w][h];
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                res[i][j] = Math.min(255,(int)(colorArray.array[i][j]*1.0*multiple/100));
            }
        }
        return res;
    }

    /**
     * 圖片動態陣列的初始化構造方法
     */
    public ImageArray(int initSize){
        if(initSize < defaultLength){
            length = defaultLength;
            imgArray = new BufferedImage[length];
            redArray = new ColorArray[length];
            greenArray = new ColorArray[length];
            blueArray = new ColorArray[length];
            size = 0;
        }else{
            length = initSize;
            imgArray = new BufferedImage[length];
            redArray = new ColorArray[length];
            greenArray = new ColorArray[length];
            blueArray = new ColorArray[length];
            size = 0;
        }
    }

    public void add(BufferedImage img){
        if(size >= length){
            int oldlength = length;
            length = oldlength + oldlength>>1;
            BufferedImage[] newArray = new BufferedImage[length];
            for (int i = 0; i < oldlength; i++) {
                newArray[i] = imgArray[i];
            }
            imgArray = newArray;
            newArray = null;
        }
        imgArray[size] = img ;
        redArray[size] = new  ColorArray(img,ColorArray.TYPE_RED);
        greenArray[size] = new  ColorArray(img,ColorArray.TYPE_GREEN);
        blueArray[size] = new  ColorArray(img,ColorArray.TYPE_BLUE);
        size++;
    }

    public void remove(int index) {
        imgArray[index] = null;
        size--;
    }
    //注意index的合法性
    public BufferedImage get(int index) {
        return imgArray[index] ;
    }

}

ColorArray 二維陣列類,存放并處理顏色矩陣

package drawBoard_test2;

import java.awt.image.BufferedImage;

public class ColorArray{
    static final int TYPE_RED = 0;
    static final int TYPE_GREEN = 1;
    static final int TYPE_BLUE = 2;
    public int[][] array = {};
    ColorArray(BufferedImage img , int type){
        if(type == TYPE_RED){
            array = new int[img.getWidth()][img.getHeight()];
            for (int i = 0; i < img.getWidth(); i++) {
                for (int j = 0; j < img.getHeight(); j++) {
                    array[i][j] = (img.getRGB(i,j)>>16) & 0xff;
                }
            }
        }else if(type == TYPE_GREEN){
            array = new int[img.getWidth()][img.getHeight()];
            for (int i = 0; i < img.getWidth(); i++) {
                for (int j = 0; j < img.getHeight(); j++) {
                    array[i][j] = (img.getRGB(i,j)>>8) & 0xff;
                }
            }
        }else if(type == TYPE_BLUE){
            array = new int[img.getWidth()][img.getHeight()];
            for (int i = 0; i < img.getWidth(); i++) {
                for (int j = 0; j < img.getHeight(); j++) {
                    array[i][j] = img.getRGB(i,j) & 0xff;
                }
            }

        }
    }
}
  • 通過滑動條調整顏色,并繪制出來
@Override
    public void stateChanged(ChangeEvent e) {

        JSlider jSlider = (JSlider)e.getSource();
        String s = jSlider.getToolTipText();
        switch (s){
            case "縮放比例":
                multiple =  jSlider.getValue();
                int[][] img = getImagePixel(fileName);
                BufferedImage bufferedImage = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
                Graphics buffg = bufferedImage.getGraphics();
                drawImage_multiple(buffg,img);
                g.drawImage(bufferedImage,0,0,null);
                break;
            case "紅色":
                multipleRed =  jSlider.getValue();
                BufferedImage bufferedImage1 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
                Graphics buffg1 = bufferedImage1.getGraphics();
                drawImage_multiple_color(buffg1,imageArray);

                g.drawImage(bufferedImage1,0,0,null);
                break;
            case "綠色":
                multipleGreen =  jSlider.getValue();
                BufferedImage bufferedImage2 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
                Graphics buffg2 = bufferedImage2.getGraphics();
                drawImage_multiple_color(buffg2,imageArray );
                g.drawImage(bufferedImage2,0,0,null);
                break;
            case "藍色":
                multipleBlue =  jSlider.getValue();
                BufferedImage bufferedImage3 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
                Graphics buffg3 = bufferedImage3.getGraphics();
                drawImage_multiple_color(buffg3,imageArray);
                g.drawImage(bufferedImage3,0,0,null);
                break;
        }

    }

//注意:在圖片打開的時候將從圖片提取出來的BufferedImage放入imageArray中
public void drawImage_multiple_color(Graphics g , ImageArray imageArray){
    int index = imageArray.getSize()-1;
    int w = (drawJPanel.getWidth()- imageArray.get(index).getWidth())/2;
    int h = (drawJPanel.getHeight()- imageArray.get(index).getHeight())/2;
    int[][] red  ;
    int[][] green;
    int[][] blue ;
    red =   imageArray.multiple(multipleRed,imageArray.redArray[index]);
    green = imageArray.multiple(multipleGreen,imageArray.greenArray[index]);
    blue =  imageArray.multiple(multipleBlue,imageArray.blueArray[index]);
    for (int i = 0; i < imageArray.get(index).getWidth(); i++) {
        for (int j = 0; j < imageArray.get(index).getHeight() ; j++) {
            g.setColor(new Color(red[i][j],green[i][j],blue[i][j]));
            g.drawRect(i+w,j+h,1,1);
        }
    }
}    

第十二步:旋轉

  • 拿向右旋轉來舉例,我們要把陣列向右旋轉變成一個新陣列,再輸出到螢屏上,
 case "向左旋轉":
     BufferedImage bufferedImage15 = new BufferedImage(800,800,BufferedImage.TYPE_INT_ARGB);
     int img15[][] = getImagePixel(fileName);
     img15 = RotateRight(img15);
     Graphics buffg15 = bufferedImage15.getGraphics();
     drawImage(buffg15,img15);
     g.drawImage(bufferedImage15,0,0,null);
     ImageShape imageShape15 = new ImageShape();
     imageShape15.setBufferedImage(bufferedImage15);
     shapeList.add(imageShape15);
     break;

public  int[][] RotateRight(int[][] img){
    int w = img.length;
    int h = img[0].length;
    int[][] newImg = new int[h][w];
    for (int i = 0; i < w; i++) {
        for (int j = 0; j < h; j++) {
            newImg[h-j-1][w-i-1] = img[i][j];
        }
    }
    return newImg;
}![image](https://img2022.cnblogs.com/blog/2555328/202204/2555328-20220414151025986-1397523916.png)

效果圖片:
image

一點點心得總結

1、開始寫代碼之前,一定要明確自己要實作什么功能,達到什么效果,
2、如何實作這樣的效果,
3、實作程序中:當前實作的效果是否符合預期,如果不符合要重新制定計劃,
4、搜集資料,撰寫博客,發現自己的不足,舊知新學,

源代碼以及圖片素材鏈接
提取碼:t1gp

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/458202.html

標籤:Java

上一篇:redis續集

下一篇:spring cloud zuul 500 錯誤

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more