Home About
Kotlin , Java2D , Matrix , Image Manipulation

ベジェ曲線を使って 円を描画する Java2D

取り急ぎベジェを使った円の描画を復習したので書き残す。

circle

circle.main.kts にコードを書く。 kotlin circle.main.kts で実行すると circle.png が生成される。

import java.io.File
import java.awt.Graphics2D
import java.awt.Color
import java.awt.geom.Point2D
import java.awt.geom.GeneralPath
import java.awt.image.BufferedImage
import javax.imageio.ImageIO

System.setProperty("java.awt.headless", "true")


object MatrixUtils {
    fun times(a: FloatArray, b: FloatArray): FloatArray {
        return floatArrayOf(
            a[0]*b[0] + a[1]*b[3] + a[2]*b[6],
            a[0]*b[1] + a[1]*b[4] + a[2]*b[7],
            a[0]*b[2] + a[1]*b[5] + a[2]*b[8],

            a[0+3]*b[0] + a[1+3]*b[3] + a[2+3]*b[6],
            a[0+3]*b[1] + a[1+3]*b[4] + a[2+3]*b[7],
            a[0+3]*b[2] + a[1+3]*b[5] + a[2+3]*b[8],

            a[0+3*2]*b[0] + a[1+3*2]*b[3] + a[2+3*2]*b[6],
            a[0+3*2]*b[1] + a[1+3*2]*b[4] + a[2+3*2]*b[7],
            a[0+3*2]*b[2] + a[1+3*2]*b[5] + a[2+3*2]*b[8]
        )
    }

    fun times(a: FloatArray, b: FloatArray, c: FloatArray): FloatArray = times( a, times(b, c) )
    fun times(a: FloatArray, b: FloatArray, c: FloatArray, d: FloatArray): FloatArray = times( a, times(b, times(c, d)) )

    fun transform(a: FloatArray, x: Float, y: Float): FloatArray {
        val b: FloatArray = floatArrayOf(x, y, 1f)

        val c00 = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
        val c10 = a[0 + 3] * b[0] + a[1 + 3] * b[1] + a[2 + 3] * b[2]
        val c20 = a[0 + 6] * b[0] + a[1 + 6] * b[1] + a[2 + 6] * b[2]

        return floatArrayOf(c00, c10, c20)
    }

    fun transform(a: FloatArray, pt: Point2D.Float): Point2D.Float {
        val array = transform(a, pt.x, pt.y)
        return Point2D.Float(array[0], array[1])
    }
}


val toRadian: (Double) -> Double = { degree-> degree * Math.PI/180f }


// 原点に並行移動して、degree度分回転、元の位置へ並行移動する matrixValues を生成:
val toMatrixValues: (Double) -> FloatArray = { degree->
    val radian = toRadian(degree)
    
    val matrixValues0 = floatArrayOf(
        1f, 0f, -50f,
        0f, 1f, -50f,
        0f, 0f, 1f)
    
    val matrixValues1 = floatArrayOf(
        Math.cos(radian).toFloat(), -Math.sin(radian).toFloat(), 0.toFloat(),
        Math.sin(radian).toFloat(),  Math.cos(radian).toFloat(), 0.toFloat(),
        0f, 0f, 1f)
    
    val matrixValues2 = floatArrayOf(
        1f, 0f, 50f,
        0f, 1f, 50f,
        0f, 0f, 1f)
    
    MatrixUtils.times(matrixValues2, matrixValues1, matrixValues0)
}

// 回転して並行移動する matrixValues を生成:
val toMatrixValuesWithTranslation: (Double, Float, Float) -> FloatArray = { degree, dx, dy->
    val matrixValues = floatArrayOf(
        1f, 0f, dx,
        0f, 1f, dy,
        0f, 0f, 1f)
    
    MatrixUtils.times( matrixValues, toMatrixValues(degree) )
}


// 200x200 のキャンバスを作成:
val w = 200
val h = 200

val img = BufferedImage(w, h, BufferedImage.TYPE_INT_RGB)

val g = img.getGraphics() as Graphics2D
g.setColor(Color.WHITE)
g.fillRect(0, 0, w, h)


// 中心が (100,100) 半径 R=100 の円弧 (左上1/4) を描画するための点の計算:
val M = (4f / 3f * (Math.sqrt(2.toDouble())-1f)).toFloat() /// 円をベジェで描くためのマジックナンバー
val R = 100f
val RM = R*M

val startPt    = Point2D.Float(0f, R)
val controlPt1 = Point2D.Float(0f, R-RM)
val controlPt2 = Point2D.Float(R-RM, 0f)
val stopPt     = Point2D.Float(R, 0f)

val ptList: List<Point2D.Float> = listOf(startPt, controlPt1, controlPt2, stopPt)

// 残りの 3つの円弧は、今作成した点をベースに回転と並行移動を使って作成:

val matrixValues1 = toMatrixValuesWithTranslation((90*1).toDouble(), 100f, 0f)
val matrixValues2 = toMatrixValuesWithTranslation((90*2).toDouble(), 100f, 100f)
val matrixValues3 = toMatrixValuesWithTranslation((90*3).toDouble(), 0f,   100f)

val ptList1 = ptList.map { MatrixUtils.transform(matrixValues1, it) }
val ptList2 = ptList.map { MatrixUtils.transform(matrixValues2, it) }
val ptList3 = ptList.map { MatrixUtils.transform(matrixValues3, it) }


// パスを使って円を描画:
val path = GeneralPath()

listOf(ptList, ptList1, ptList2, ptList3).forEach {
    path.moveTo(it[0].x, it[0].y)
    path.curveTo(
        it[1].x, it[1].y,
        it[2].x, it[2].y,
        it[3].x, it[3].y)
}

g.color = Color.BLACK
g.draw(path)

// PNGとしてファイルに保存:
val pngFile = File("circle.png")
ImageIO.write(img, "PNG", pngFile)

Liked some of this entry? Buy me a coffee, please.