• Java 从零开始实现一个画图板、以及图像处理功能,代码可复现


    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 = 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 = 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 = 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<background[i].length){
                    g.setColor(new Color(background[i][j]));
                }else {
                    g.setColor(new Color(img[i][j]));
                }
                g.fillRect(i+w , j+h, 1, 1);
            }
        }
    }
    
    • 油画
    /**
     * 原理与马赛克类似,不同的是油画效果要填充随机大小的色块
     */
    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

  • 相关阅读:
    Vue3实战(1)
    搭建discuz论坛并攻破盗取数据库
    性能测试流程注意事项(亲身经历希望能帮助到你)
    TypeScript中的never应用场景
    使用jQuery获取不同元素的ID
    猿创征文|活在大二,前端的我勇往直前
    自然应急项目GNSS监测设备
    MS14-068 漏洞分析—不安全的PAC
    pytorch基础
    Android使用Coordinatorlayout以及自定义Behavior实现滑动折叠效果
  • 原文地址:https://www.cnblogs.com/classicltl/p/16145023.html