java画图板之平面山水画(一):https://blog.csdn.net/qq_43348021/article/details/104346805
上次的博客中已经将山脉的轮廓画出来了,这次我们对它进行修饰。
填充
刚开始,我以为将区域填充需要将其中每个点都画到,其实不然。我们知道画出的山脉轮廓其实是有若干个点连线组成的,我们只需要从这些点开始,垂直向下划线即可,当点的数量足够多的时候,显示的就是全部被填充的情况。
public void showmt(double startx, double starty, double endx, double endy, Color mtcolor, double range, double timescounter) {
double midx, midy;
//计算中点坐标
midx = (startx + endx) / 2;
midy = (starty + endy) / 2 + (Math.random() * 2 - 1) * range;
if (timescounter++ == times) {
//画山脉轮廓
g.drawLine((int) startx, (int) starty, (int) midx, (int) midy);
g.drawLine((int) midx, (int) midy, (int) endx, (int) endy);
//填充山脉
g.setColor(mtcolor);
g.drawLine((int) midx, (int) midy, (int) midx, (int) 600/*画布底部*/);
} else {
//随着相邻两个点的横坐标距离减少,纵坐标随机起伏范围也要减小
range *= rate;
//递归
showmt(startx, starty, midx, midy, mtcolor, range, timescounter);
showmt(midx, midy, endx, endy, mtcolor, range, timescounter);
}
}
- 这是递归次数(times)为10时画出的效果:
- 这是递归次数(times)为11时画出的效果:
群山
只有一座山肯定是不够的,我们用循环来画多做山脉,同时对远近山脉的高低、颜色进行调整,这部分放在监听器中。
for (int i = 0; i < 4; i++) {
int distance = 100;// 每座山脉间距离
mountain mountain = new mountain();
mountain.setGraphics(bufferg);// 先在缓存中绘制
Color mtcolor = new Color(i * 50, i * 50, i * 50);// 设置每座山脉颜色
mountain.showmt(0, 50 + i * distance, 800, 50 + i * distance, mtcolor, 100, 1);// showmt(double startx, double starty, double endx, double endy, Color mtcolor, double range, double timescounter)
}
提速
实际运行过上面的代码就会知道,递归11次会使得画出图像需要1秒左右的过程,有没有什么办法能使图像在运行的瞬间就显示出来呢?这就需要用到计算机的缓存了,我们可以事先在缓存中画出图形并保存,然后将缓存中的图像显示出来(CPU与缓存间传输信息的速度要大于CPU与输入输出设备的)。
这样做还有一个好处,由于我们的图像绘制时使用了随机函数,导致我们每次调用函数都会改变图像,当其他构件发生变化时,山脉的图像也在跟着变化。运用缓存后,当其他构件发生变化影响到山脉的图像时,我们只需要将缓存中保存的图片重新显示出来就行了,而不需要重新绘制。
java提供了BufferedImage类供我们使用。
//绘制山脉
// 设置缓存
BufferedImage buffer = new BufferedImage(800, 400, BufferedImage.TYPE_INT_RGB);
// 设置缓存画布
Graphics bufferg = buffer.getGraphics();
// 设置画布背景颜色
Color bufferbgcolor = new Color(255,255,255);
bufferg.setColor(bufferbgcolor);
bufferg.fillRect(0, 0, 800, 400);
// 在缓存中绘制山脉
for (int i = 0; i < 4; i++) {
int distance = 100;// 每座山脉间距离
mountain mountain = new mountain();
mountain.setGraphics(bufferg);// 先在缓存中绘制
Color mtcolor = new Color(i * 50, i * 50, i * 50);// 设置每座山脉颜色
mountain.showmt(0, 50 + i * distance, 800, 50 + i * distance, mtcolor, 100, 1);// showmt(double startx, double starty, double endx, double endy, Color mtcolor, double range, double timescounter)
}
太阳
有了山脉,我总觉得还少了点什么,为了让图片更加有意境,我加入了太阳这个元素。但是仅仅是画一个太阳很死板,我想让它动起来。
- 首先,先确定太阳的画法,使用fillOval()函数,需要注意的是里面的参数是圆外接矩形左上角的点的坐标。在画下一个太阳前,需要将前一个太阳擦除,准确的说是覆盖:用与背景相同的颜色,在前一个太阳处画圆。
- 然后,就是太阳的轨迹:
我们将轨迹设置为以O为圆心,OA为半径的圆弧,A点的位置可以修改。接着我们设定太阳移动的移动速度sunspeed = 0.5 弧度/次,运动开始相对时间starttime = 35(因为从0开始可能会被挡住,每次运行完+1),我们可以通过三角函数,算出在t时刻P点的坐标(OB - OA* Math.cos(Math.asin(AB/OA) + starttime * sunspeed * PI / 180),600 - 500 * Math.sin(Math.asin(AB/OA) + starttime * sunspeed * PI / 180))
public void showsun() {
// 绘制太阳
// 清除上一个太阳
Color bgcolor = new Color(255, 128, 0);
g.setColor(bgcolor);
g.fillOval((int) (sunx - sunr), (int) (suny - sunr), (int) sunr, (int) sunr);
// 计数
starttime++;
// 计算新太阳的位置
sunx = 400 - 500 * Math.cos(Math.asin(0.6) + starttime * sunspeed * PI / 180);
suny = 600 - 500 * Math.sin(Math.asin(0.6) + starttime * sunspeed * PI / 180);
// 画出太阳
Color suncolor = new Color(255, 0, 0);
g.setColor(suncolor);
g.fillOval((int) (sunx - sunr), (int) (suny - sunr), (int) sunr, (int) sunr);
// 在主画布上画出缓存画布
g.drawImage(buffer, 0, 200, null);
}
- 最后,我们加入线程休眠,让这段程序每100ms运行一次
while (sun.sunx <= 700) {// 设置截止条件
// 计时
try {
Thread.sleep(100); // 设置精度
} catch (InterruptedException ex) {
Logger.getLogger(sun.class.getName()).log(Level.SEVERE, null, ex);
}
// 画出太阳和山脉
sun.showsun();
}
但是,这里面还有一个问题,就是太阳没有运行完,程序就不能执行其他的任务,这需要用到多线程和高并发的相关知识了,我会将这一部分放到聊天室搭建的博客中。
运行结果
结果是一个动图,但是我不知道怎么做gif。虽然可能没有想象中的美观但是关键是技术与思想的掌握和今后的运用。
附录(源代码)
background类
package plain;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JFrame;
public class background {
public void showbg() {
JFrame bg = new JFrame();
// 设置窗体基本属性
bg.setTitle("地球平原");
bg.setSize(800, 600);
bg.setLocationRelativeTo(null);
// 窗体关闭时结束程序
bg.setDefaultCloseOperation(bg.EXIT_ON_CLOSE);
// 设置背景颜色,直接设置没有用,需要用Panel组件
Color bgcolor = new Color(255, 128, 0);
//Color bgcolor = new Color(255, 255, 255);
bg.getContentPane().setBackground(bgcolor);
// 设置布局,放在可见前才显示
FlowLayout flow = new FlowLayout();
bg.setLayout(flow);
// 设置可见,并获取画布对象
bg.setVisible(true);
Graphics g = bg.getGraphics();
// 设置鼠标监听器
MouseListener mouselistener = new MouseListener();
bg.addMouseListener(mouselistener);
mouselistener.setGraphics(g);
}
public static void main(String args[]) {
background bg = new background();
bg.showbg();
}
}
MouseListener类
package plain;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import three_dimention.Pad;
public class MouseListener implements java.awt.event.MouseListener, ActionListener {
Graphics g;
public void setGraphics(Graphics g) {
this.g = g;
}
public void mouseClicked(MouseEvent e) {
//绘制山脉
// 设置缓存
BufferedImage buffer = new BufferedImage(800, 400, BufferedImage.TYPE_INT_RGB);
// 设置缓存画布
Graphics bufferg = buffer.getGraphics();
// 设置画布背景颜色
Color bufferbgcolor = new Color(255, 128, 0);
bufferg.setColor(bufferbgcolor);
bufferg.fillRect(0, 0, 800, 400);
// 在缓存中绘制山脉
for (int i = 0; i < 4; i++) {
int distance = 100;// 每座山脉间距离
mountain mountain = new mountain();
mountain.setGraphics(bufferg);// 先在缓存中绘制
Color mtcolor = new Color(i * 50, i * 50, i * 50);// 设置每座山脉颜色
mountain.showmt(0, 50 + i * distance, 800, 50 + i * distance, mtcolor, 100, 1);// showmt(double startx, double starty, double endx, double endy, Color mtcolor, double range, double timescounter)
}
//绘制太阳
sun sun = new sun();
sun.setGraphics(g);
sun.setBuffer(buffer);
while (sun.sunx <= 700) {// 设置截止条件
// 计时
try {
Thread.sleep(100); // 设置精度
} catch (InterruptedException ex) {
Logger.getLogger(sun.class.getName()).log(Level.SEVERE, null, ex);
}
// 画出太阳和山脉
sun.showsun();
}
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void actionPerformed(ActionEvent e) {
}
}
mountain类
package plain;
import java.awt.Color;
import java.awt.Graphics;
public class mountain {
double rate = 0.5, times = 11;//times>10
Boolean whe_left = true;
Graphics g;
public void setGraphics(Graphics bufferg) {
this.g = bufferg;
}
public void showmt(double startx, double starty, double endx, double endy, Color mtcolor, double range, double timescounter) {
double midx, midy;
//计算中点坐标
midx = (startx + endx) / 2;
midy = (starty + endy) / 2 + (Math.random() * 2 - 1) * range;
if (timescounter++ == times) {
//画山脉轮廓
g.drawLine((int) startx, (int) starty, (int) midx, (int) midy);
g.drawLine((int) midx, (int) midy, (int) endx, (int) endy);
//填充山脉
g.setColor(mtcolor);
g.drawLine((int) midx, (int) midy, (int) midx, (int) 600/*画布底部*/);
} else {
//随着相邻两个点的横坐标距离减少,纵坐标随机起伏范围也要减小
range *= rate;
//递归
showmt(startx, starty, midx, midy, mtcolor, range, timescounter);
showmt(midx, midy, endx, endy, mtcolor, range, timescounter);
}
}
}
sun类
package plain;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
public class sun {
Graphics g;// 主画布
double sunspeed = 0.5;// 太阳移动速度
double PI = 3.14;// π
double sunx = 0, suny = 300, sunr = 80;// 起始位置、太阳直径
double starttime = 35;// 运动开始的时间
BufferedImage buffer;
public void setGraphics(Graphics g) {
this.g = g;
}
public void setBuffer(BufferedImage buffer) {
this.buffer = buffer;
}
public void showsun() {
// 绘制太阳
// 清除上一个太阳
Color bgcolor = new Color(255, 128, 0);
g.setColor(bgcolor);
g.fillOval((int) (sunx - sunr), (int) (suny - sunr), (int) sunr, (int) sunr);
// 计数
starttime++;
// 计算新太阳的位置
sunx = 400 - 500 * Math.cos(Math.asin(0.6) + starttime * sunspeed * PI / 180);
suny = 600 - 500 * Math.sin(Math.asin(0.6) + starttime * sunspeed * PI / 180);
// 画出太阳
Color suncolor = new Color(255, 0, 0);
g.setColor(suncolor);
g.fillOval((int) (sunx - sunr), (int) (suny - sunr), (int) sunr, (int) sunr);
// 在主画布上画出缓存画布
g.drawImage(buffer, 0, 200, null);
}
}