情人节程序员的浪漫

  搜索了一下笛卡尔和瑞典公主的浪漫爱情故事,其中提到了一个心形函数,但是看起来像极坐标系的函数。以下借助大佬的数学公式和其他大佬的代码实现,移植java Swing和android平台进行实现。马上就要2.14号了,祝有情人终成眷属,举案齐眉,相敬如宾,白头偕老。
https://mathworld.wolfram.com/HeartCurve.html

1.心形曲线的绘制

x = 16   sin ⁡ 3 t x=16\,\sin^{3}t x=16sin3t
y = 13   cos ⁡ t − 5 cos ⁡ ( 2 t ) − 2 cos ⁡ ( 3 t ) − cos ⁡ ( 4 t ) y=13\,\cos t - 5\cos(2t)-2\cos(3t)-\cos(4t) y=13cost5cos(2t)2cos(3t)cos(4t)

效果图如下:
情人节程序员的浪漫_第1张图片  可惜swing的graphics画图方法参数只支持int类型,导致曲线不太平滑,可以在android里面绘制更平滑的曲线,android的画线方法支持浮点数。此曲线的绘制参考了不少大佬的博客代码,在android和swing里面均相应转化成功。后面的坐标轴可以去掉,加上只是为了参考。

2.心形曲线动画

2.1效果1

如果只是个静态图也有些乏味,尝试让曲线绘制过程变的可见,加个延时。
情人节程序员的浪漫_第2张图片

2.2效果2

从顶部中间同时向下绘制
情人节程序员的浪漫_第3张图片

2.3效果3把画线改为绘制图片

情人节程序员的浪漫_第4张图片

import java.awt.*
import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JPanel
// 动画版的画心,从上边顺时针绘制
class HeartFrameKt2 : JFrame() {
    init {
        val panel = HeartPanel()
        val jButton = JButton("refresh")
        add(panel, BorderLayout.CENTER)
        add(jButton, BorderLayout.SOUTH)
        var thread = Thread {
            while (true) {
                var refresh = true
                EventQueue.invokeLater {
                    refresh = panel.refresh()
                }
                Thread.sleep(25)
            }
        }
        thread.start()
        jButton.addActionListener {
            panel.clear()
        }
    }

    inner class HeartPanel : JPanel() {
        var i = 0.0
        var list = mutableListOf<OkPoint>()
        override fun paintComponent(g: Graphics?) {
            super.paintComponent(g)
            var g2: Graphics2D = g as Graphics2D
            g2.translate(width / 2, height / 2)
            drawAxis(g)
            //修线条粗细
            g2.stroke = BasicStroke(3.0f)
            g2.color = Color.RED

            val inc = Math.PI / 90
            if (i <= 2 * Math.PI) {
                var x = getX(20, i.toFloat())
                var y = getY(20, i.toFloat())
                var p = OkPoint(x, y)
                list.add(p)
                i += inc
            }

            for (i in list.indices) {
                if (i < list.size - 1) {
                    var p0 = list.get(i)
                    var p1 = list.get(i + 1)
                    g2.drawLine(p0.x.toInt(), p0.y.toInt(), p1.x.toInt(), p1.y.toInt())
                }
            }

            g2.translate(-width / 2, -height / 2)
        }

        fun getX(zoom: Int, theta: Float): Double {
            return zoom * (16 * Math.pow(Math.sin(theta.toDouble()), 3.0))
        }

        fun getY(zoom: Int, theta: Float): Double {
            return (-zoom
                    * (13 * Math.cos(theta.toDouble()) - 5 * Math.cos((2 * theta).toDouble()) - (2
                    * Math.cos((3 * theta).toDouble())) - Math.cos((4 * theta).toDouble())))
        }

        fun drawAxis(g: Graphics?) {
            var g2: Graphics2D = g as Graphics2D
            g2.stroke = BasicStroke(1.0f)
            g2.color = Color.BLACK
            g2.drawLine(-width / 2, 0, width / 2, 0)
            g2.drawLine(0, -height / 2, 0, height / 2)
            //unit=10,vertical line,x1,y1,x2,y2
            // short line, long line
            val sl = 5
            val ll = 10
            //x axis
            for (i in 0..width / 2 step 10) {
                if (i % 50 == 0) {
                    g2.drawLine(i, 0, i, -ll)
                    g2.drawLine(-i, 0, -i, -ll)
                } else {
                    g2.drawLine(i, 0, i, -sl)
                    g2.drawLine(-i, 0, -i, -sl)
                }
            }
            //y axis
            for (i in 0..height / 2 step 10) {
                if (i % 50 == 0) {
                    g2.drawLine(0, i, ll, i)
                    g2.drawLine(0, -i, ll, -i)
                } else {
                    g2.drawLine(0, i, sl, i)
                    g2.drawLine(0, -i, sl, -i)
                }
            }
        }

        fun refresh():Boolean {
            return if (i <= 2 * Math.PI) {
                repaint()
                true
            } else {
                false
            }
        }

        fun clear() {
            i = 0.0
            list.clear()
        }
    }

    inner class OkPoint(var x: Double, var y: Double)
}

fun main(args: Array<String>) {
    var frame = HeartFrameKt2()
    frame.apply {
        setSize(1500, 800)
        title = "Kotlin heart"
        setLocationRelativeTo(null) // Center the frame

        defaultCloseOperation = JFrame.EXIT_ON_CLOSE
        isVisible = true
    }
}

  Android版本

public class ValentineView extends View {

    Paint paint;
    int w, h;
    Bitmap flower;
    Canvas hearCanvas;
    Bitmap bmpHeart;
    float leftAngle = (float)( 2*Math.PI);
    float rightAngle = 0;
    double inc = Math.PI / 45;
    int zoom = 1;
    float finalTxSize;

    public ValentineView(Context context, AttributeSet attrs) {
        super(context, attrs);
        float density = getResources().getDisplayMetrics().density;
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.parseColor("#FFDB9C"));
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setTextSize(/*density*22*/0);
        paint.setStrokeWidth(2);
        paint.setDither(true);

        flower = BitmapFactory.decodeResource(getResources(), R.mipmap.heart);

        finalTxSize = density * 22;
    }

    public ValentineView(Context context) {
        this(context, null);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int wM = MeasureSpec.getMode(widthMeasureSpec);
        int hM = MeasureSpec.getMode(heightMeasureSpec);
        if (wM == MeasureSpec.EXACTLY) {
            w = MeasureSpec.getSize(widthMeasureSpec);
        }

        if (hM == MeasureSpec.EXACTLY) {
            h = MeasureSpec.getSize(heightMeasureSpec);
        }

        setMeasuredDimension(w, h);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        hearCanvas = new Canvas();
        bmpHeart = Bitmap.createBitmap(w, h, Config.ARGB_8888);
        hearCanvas.setBitmap(bmpHeart);
        // 计算需要放大的倍数,铺满屏幕宽度
        zoom = (int) Math.ceil(w / 2.0 / 16.0 - 4);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawHeart(canvas);
        canvas.drawBitmap(bmpHeart, 0, 0, paint);

        if (rightAngle > Math.PI || leftAngle < Math.PI){
            Log.d("xxx", "draw hear ok");
            paint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText("情人节快乐", w/2, h/2, paint);
            paint.setTextAlign(Paint.Align.LEFT);

            if (paint.getTextSize() <= finalTxSize) {
                paint.setTextSize(paint.getTextSize()+1);
                postInvalidateDelayed(25);
            }else{
                Log.d("xxx", "draw txt ok");
            }
        }
    }

    private void drawHeart(Canvas canvas) {
        hearCanvas.save();
        hearCanvas.translate(w / 2, h / 2);//
        
        float leftX = (float) getX(zoom, leftAngle);
        float leftY = (float) getY(zoom, leftAngle);
        float rightX = (float) getX(zoom, rightAngle);
        float rightY = (float) getY(zoom, rightAngle);
        //Log.d("xxx", String.format("x:%s, y:%s", x, y));
        hearCanvas.save();
        hearCanvas.translate(-flower.getWidth()/2, -flower.getHeight()/2);
        hearCanvas.drawBitmap(flower, leftX, leftY, paint);
        hearCanvas.drawBitmap(flower, rightX, rightY, paint);
        hearCanvas.restore();
        hearCanvas.restore();
        

        // right half
        if(rightAngle <= Math.PI){
            rightAngle += inc;
            //postInvalidateDelayed(50);
        }

        // left half
        if(leftAngle >= Math.PI){
            leftAngle -= inc;
            postInvalidateDelayed(50);
        }
    }

    double getX(int zoom, float theta) {
        return zoom * (16 * Math.pow(Math.sin(theta), 3));
    }

    double getY(int zoom, float theta) {
        return -zoom
                * (13 * Math.cos(theta) - 5 * Math.cos(2 * theta) - 2
                * Math.cos(3 * theta) - Math.cos(4 * theta));
    }

}

  更多代码,请参考github:https://github.com/ximen502/SwingLearn,Swing中用kotlin还挺不错,kotlin确实简洁而强大,和java形成很好的互补。

  Swing中的JPanel有2个作用一个是作为子面板容器,另一个是可以用于自定义组件(view),重写方法实现自定义绘制。

参考了用户qq_32250025的心形曲线公式博客,感谢。

你可能感兴趣的:(Java,Android,java,kotlin)