临近5月20的时候,想起之前做过一个用程序做出一个爱心的图案,便突发奇想给它升级一下
爱心1.0
爱心2.0
目录
- 原理
- 实现要点
- 完整代码
- 爱心1.0
- 爱心2.0
- 总结
原理
爱心1.0的原理 是根据公式 (x ^ 2 + y ^ 2 - 1) ^ 3 - x ^ 2 * y ^ 3 = 0画出来的。使用双重循环就可以实现,然后判断最后的结果和0的关系。如果结果等于0则该坐标就在爱心上,如果大于0则在爱心外,小于0则在爱心内。那么就可以画出不同样子的爱心
- 画边框、爱心留白
- 只画爱心内部
如果只是这样的话,那和爱心1.0没有多大区别,所以我打算让它动起来,画出不同的颜色
实现要点
- 怎么画出图形: 使用java的图形化界面,通过画笔画很多连续的矩形。使用到的是重写的public void paintComponent(Graphics g)方法。 要使该方法能够使用,就要在一个JPanel类中重写,并且把这个类添加到一个JFrame类中,之后在里面使用g.fillRect()方法画矩形就好了
- 怎么修改画出矩形的颜色: 在使用g.fillRect()方法画图之前可以先通过g.setColor()方法设置颜色,在此方法中要放入一个Color对象作为参数,那么就可以通过RGB三原色设置要画的颜色,之后不断改变RBG数值就可以达到不断改变颜色的效果
- 怎么画爱心: 根据上面说的公式来判断当前坐标的值是否在爱心外或爱心内来判断该点是否要画,如果要画的话就调用repaint()方法,会直接执行paintComponent(Graphics g)方法
- 怎么实现动画效果: 最开始使用的是时间事件,每隔一定时间就画一行。看似很理想但是会有一个问题,如果画一行的操作是连续的,那么在这一行中是需要循环判断每一个点是否要画,如果要画就使用repaint(),否则就继续循环。结果画出来的只会有一个点。原因是Java有一个GUI (AWT) Thread来负责GUI事件的分发,这个线程一般是和主程序的线程是绑定的,如果当前线程休眠那么画图的这个事件也会丢失。如果循环调用repaint()的话就会合并为最后一个repaint(),所以永远只会画出一个点。那么就需要使用到多线程,为面板创建一个新的线程,每次调用repaint()时就让面板线程休眠一段时间,那么就会轮到GUI (AWT) Thread,就可以执行repaint(),从而就不会丢失事件。既然使用了线程休眠,那么就不需要再额外增加一个时间事件了,只需要通过线程休眠达到时间间隔的效果就可以了
完整代码
爱心1.0
#include<stdio.h>
#include<iostream>
using namespace std ;
int main() {
for(float y = 1.5f; y >= -1.5f; y -= 0.1f){
for(float x = -1.5f; x <= 1.5f; x += 0.05f){
float a = x*x + y*y - 1;
if(a*a*a - x*x*y*y*y <= 0.0f)
printf("*");
else
printf(" ");
}
printf("\n");
}
return 0 ;
}
爱心2.0
public class Main {
public static void main(String[] args) {
Window window = new Window() ;
}
}
public class Window extends JFrame {
Panel panel;
public Window() {
panel = new Panel() ;
add(panel) ; //只有将画板添加到窗口才能画图
panel.setBounds(0, 0, 860, 750);
Thread t = new Thread(panel) ; //要使用线程才能实现动画效果
t.start();
setLayout(null); //画板要能调节大小,则窗口不能使用默认排版方式
setBounds(400, 50, 860, 750);
setVisible(true);
validate();
setDefaultCloseOperation(Window.EXIT_ON_CLOSE);
}
}
public class Panel extends JPanel implements Runnable {
int R ; //三原色red
int G ; //三原色green
int B ; //三原色blue
int tx; //画图坐标
int ty ; //画图坐标
float y ; //循环画图行数
boolean flag ; //画边框爱心还是实体爱心
boolean increaseOrDecrease = false ; //G、B增大或减小
boolean backRed = false ; //从黑色变回红色
File file = new File("my lonely soul.wav") ; //背景音乐
URL url = null;
URI uri = null ;
AudioClip clip = null;
public Panel(){
try {
uri=file.toURI();
url = uri.toURL() ;
}
catch (MalformedURLException e1) {}
clip= Applet.newAudioClip(url);
clip.loop(); //播放背景音乐
R = 255 ; //初始三原色为红色
G = 0 ;
B = 0 ;
y = 1.5f ; //初始循环位置
tx = 30 ; //每一行画图的位置
ty = 10 ; //初始画图的列的位置
flag = false ; //最开始画边框爱心
setVisible(true);
}
public void paintComponent(Graphics g) {
if(!flag) { //画边框
Color color = new Color(R,G,B) ; //根据当前的RGB画相应颜色的图形
g.setColor(color);
g.fillRect(tx, ty, 13, 13);
g.fillRect(tx, ty+11, 13, 10); //多往下画一点减小每行的间隔
}
else { //画实体爱心
super.paintComponent(g); //将之前所有的边框先清空
Color color = new Color(206, 40, 34) ; //最终的颜色
g.setColor(color);
for(float i = 1.5f; i >= -1.5f; i -= 0.1f){
for(float x = -1.5f; x <= 1.5f; x += 0.05f){
float a = x*x + i*i - 1;
if(a*a*a - x*x*i*i*i <= 0.0f) {
g.fillRect(tx, ty, 13, 13);
g.fillRect(tx, ty+11, 13, 16);
}
tx += 13 ;
}
tx = 30 ;
ty += 25 ;
}
}
}
public void run() {
while (true) {
try {
Thread.sleep(70);
}
catch(Exception e) {}
if(y>=-1.2f && !flag) { //画边框爱心
//根据公式(x^2+y^2-1)^3-x^2y^3=0画
for(float x = -1.5f; x <= 1.5f; x += 0.05f) {
float a = x*x + y*y - 1;
if(a*a*a-x*x*y*y*y>0.0f && y!=1.5f) { //大于0是爱心外侧,小于0是内侧
this.repaint();
try { //要把线程休眠一会才会轮到repaint()的线程
Thread.sleep(4); //可自定义事件,事件越小画的速度越快但太小的话可能会漏画
}
catch(InterruptedException e){}
}
tx += 13 ; //每行往右走一点
}
tx = 30 ; //画完一行后要回到最左边
ty += 25 ; //画下一行
y -= 0.1f ; //循环次数减少
}
else { //画完一个边框后判断继续画下一个边框或画实体
if(!increaseOrDecrease) { //G、B数值增加
G += 4 ;
B += 4 ;
}
else { //G、B数值减小
G -= 4 ;
B -= 4 ;
}
if(G >= 70) //G、B数值在0~70范围内
increaseOrDecrease = true ;
if(!backRed) //红变黑
R -= 10 ;
else //黑变红
R += 10 ;
if(R <= 0) { //到黑色了准备变回红色
R = 1 ; //重新初始化R
backRed = true ;
}
if(G<=0 && B<=0) {
G = 1 ;
B = 1 ;
backRed = true ;
}
if(R < 255) //R没有再次变回255说明还在画边框
y = 1.5f ;
else { //画实体爱心
flag = true ;
try {
Thread.sleep(500);
}
catch(Exception e) {}
this.repaint();
}
tx = 30 ; //每画完一次都要重新初始化画图坐标
ty = 10 ;
}
}
}
}
总结
- 这是继圣诞树之后第二个突发奇想做的东西,还是挺有意思的,也能学到一些新的东西,比如多线程实现循环repaint()。以后还会有更多突发奇想的东西做出来吧
- 做出来的只是一个模板,如果对你有用,完全可以在这个基础上实现更多有趣浪漫的操作。不准觉得这个表白程序太直男!把“硬核”打在公屏上!
如果无聊的话可以点这里听着歌看着爱心颜色慢慢变化,发呆就好了