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

这是一个学习分享博客,带你从零开始实现一个画图板、图像处理的小项目,为了降低阅读难度,本博客将画图板的一步步迭代优化过程展示给读者,篇幅较长,Java初学者可放心食用。(文末有源代码)

本博客实现的功能(根据本文讲解的顺序)

  • 直线、签字笔、实时直线、谢尔宾斯基地毯、递归KLine、矩形、圆、实心矩形、实心圆、等腰三角形、三角形、多边形、改进多边形、 立方体、橡皮擦
  • 画笔的颜色更改
  • 撤回、保存、打开
  • 打开jpg图片,保存图片
  • 图片特效:马赛克、黑白照、油画、背景替换、图片融合等等;
  • 图片的放大和缩小,图片旋转
  • 图片的颜色调整

界面效果





怎么样?如果觉得还不错的话就请继续看下去吧!

首先我们要写一个界面,就要给界面添加一个监听器,对监听器不太熟悉的同学,可以看我的这篇文章 常见监听器用法

第一步:创建画布

  • 万事开头难,我们从创建一个窗体开始,并给窗体添加画笔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;
}
} }

此时,画图板可以绘制出直线,我们来看一下效果

实现直线的绘制之后,其余功能的实现也是水到渠成的,我们继续往下看。

矩形、圆、实心矩形、实心圆、等腰三角形、谢尔宾斯基地毯、递归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;
}
}

第四步:实现重绘

到这里,我们的画图板的雏形已经完成了,但是也存在以下几个问题:

  • ①当窗体发生变动(放大、窗体大小发生改变)时,已经绘制好的图形就会消失.
  • ②我们在使用实时直线的时候,绘制过程中会将其他图形擦掉。

如何解决这些问题呢?

我们可以把每个的图形看作一个类,再用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方法。

  • 界面效果:

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)

效果图片:

一点点心得总结

1、开始写代码之前,一定要明确自己要实现什么功能,达到什么效果。

2、如何实现这样的效果。

3、实现过程中:当前实现的效果是否符合预期,如果不符合要重新制定计划。

4、搜集资料,撰写博客,发现自己的不足,旧知新学。

源代码以及图片素材链接

提取码:t1gp

Java 从零开始实现一个画图板、以及图像处理功能,代码可复现的更多相关文章

  1. 使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能。并且在界面上有radio 的选择内容也要上传

    使用 jquery 的 上传文件插件 uploadify 3.1 配合 java 来做一个简单的文件上次功能.并且在界面上有radio 的选择内容也要上传 uploadify 插件的 下载和文档地址  ...

  2. 用HTML5 Canvas做一个画图板

    使用HTML5可以非常简单地在canvas上实现画图应用,用支持html5的浏览器便可在下面的区域进行绘画,要看到演示效果,请确保你的浏览器支持HTML5: 功能很简单,原理其实和拖放是类似的,主要是 ...

  3. iOS开发之画图板(贝塞尔曲线)

    贝塞尔曲线,听着挺牛气一词,不过下面我们在做画图板的时候就用到贝塞尔绘直线,没用到绘制曲线的功能.如果会点PS的小伙伴会对贝塞尔曲线有更直观的理解.这篇博文的重点不在于如何用使用贝塞尔曲线,而是利用贝 ...

  4. wxPython 画图板

    终于开始Python学习之旅了,姑且以一个“画图板”小项目开始吧.放慢脚步,一点一点地学习. 1月28日更新 第一次遇到的麻烦便是“重绘”,查了好多资料,终于重绘成功了. #-*- encoding: ...

  5. 很值得学习的java 画图板源码

    很值得学习的java 画图板源码下载地址:http://download.csdn.net/source/2371150 package minidrawpad; import java.awt.*; ...

  6. Java版简易画图板的实现

    Windows的画图板相信很多人都用过,这次我们就来讲讲Java版本的简易画板的实现. 基本的思路是这样的:画板实现大致分三部分:一是画板界面的实现,二是画板的监听以及画图的实现,三是画板的重绘.(文 ...

  7. JAVA 画图板实现(基本画图功能+界面UI)一、界面实现

    /*文章中用到的代码只是一部分,需要源码的可通过邮箱联系我 1978702969@qq.com*/ 这段时间在学JAVA的swing界面开发,试着做了个画图板.实现了直线.曲线.喷枪.矩形.圆形.文字 ...

  8. Java编写画图板程序细节-保存已画图形

    没有Java编写画图板程序细节-保存已画图形   一.为何我们要保存画图板上已画图形呢? 有很多人会问,为什么我们一定要保存画图板上已经画好了的图形呢?原因很简单.当我们在画图板上画完自己想画的图形后 ...

  9. JAVA 画图板实现(基本画图功能+界面UI)二、功能实现及重绘实现

    上篇博客中介绍了界面的实现方法,在这篇博客中将对每个按钮的功能的实现进行讲解并介绍重绘 首先肯定要添加事件监听机制了,那么问题来了,事件源对象是谁?需要添加什么方法?事件接口是什么? 1.我们需要点击 ...

随机推荐

  1. centos7使用chrony同步阿里云时间

    1.yum -y install chrony2.cp /etc/chrony.conf /etc/chrony.conf.bak3.sed -i '/^server/s/^/#/g' /etc/ch ...

  2. CSV格式的文件与EXCEL文件的区别

    CSV格式的文件与EXCEL文件的区别 Excel CSV 这是一个二进制文件,它保存有关工作簿中所有工作表的信息 CSV代表Comma Separated Values .这是一个纯文本格式,用逗号 ...

  3. ctf之GET

    题目信息如图 启动环境 根据信息只需将参数?what=flag添加到url上即可

  4. TransactionScope是什么

    TransactionScope使用说明 TransactionScope是.Net Framework 2.0滞后,新增了一个名称空间.它的用途是为数据库访问提供了一个"轻量级" ...

  5. OO第一单元

    OO第一单元总结 目录 OO第一单元总结 前言 第一次作业 HW1基本思路 UML类图 代码规模 复杂度分析 方法复杂度 分析 类复杂度 分析 优化策略 第二次作业 HW2基本思路 UML类图 代码规 ...

  6. 面试问题之C++语言:mutable关键字

    转载于:https://www.cnblogs.com/xkfz007/articles/2419540.html mutable关键字 mutable的中文意思是"可变的,易变的" ...

  7. Bean 工厂和 Application contexts 有什么区别?

    Application contexts提供一种方法处理文本消息,一个通常的做法是加载文件资源(比如镜像),它们可以向注册为监听器的bean发布事件.另外,在容器或容器内的对象上执行的那些不得不由be ...

  8. switch 是否能作用在 byte 上,是否能作用在 long 上, 是否能作用在 String 上?

    在 Java 5 以前,switch(expr)中,expr 只能是 byte.short.char.int.从 Java 5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 ...

  9. Java 中是如何支持正则表达式操作的?

    Java 中的 String 类提供了支持正则表达式操作的方法,包括:matches(). replaceAll().replaceFirst().split().此外,Java 中可以用 Patte ...

  10. 客户端回调 Watcher ?

    客户端 SendThread 线程接收事件通知,交由 EventThread 线程回调 Watcher. 客户端的 Watcher 机制同样是一次性的,一旦被触发后,该 Watcher 就失效了.