0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

androidx.graphics:graphics-shapesのSvgPathParserにおけるベジェ曲線変換バグと回避策

0
Last updated at Posted at 2026-06-25

androidx.graphics:graphics-shapes:1.1.0 にて追加された SvgPathParser.parseFeatures() を使用した際、SVGのパスが正しくパースされず、形状が歪むバグに遭遇しました。

本記事では、その原因とコード上で解決する方法を紹介します。

なお、本件はすでにGoogle Issue Trackerに報告済みです:Issue Tracker #527243511

事象・原因

SvgPathParser.parseFeatures() を使ってSVGパス文字列から features を取得し、それを RoundedPolygon に変換して描画したところ、本来の形状より歪んでしまう現象が発生します。

SVGパスに含まれる2次ベジェ曲線(qコマンドなど)を、ライブラリ内部で3次ベジェ曲線に変換して扱う際は、2/3の係数を用いた座標計算が必要です。しかし、ソースコードを確認したところ、2次ベジェ曲線の制御点がそのまま3次ベジェ曲線の2つの制御点として単純にコピーされていました:

'q' -> addCurveWith(command.xy(0, 1), command.xy(0, 1), command.xy(2, 3))
パスが歪んでしまった形状の例 期待している形状の例
actual_distorted_curves.png expected_correct_curves.png

解決方法

SvgPathParser.parseFeatures() は、リリースされるアプリの本番環境(実行時)で動的に実行することは推奨されておらず、あくまでも開発中やビルド時のみの利用が想定されています。そのため、ここでは取得した Feature に直接パッチを当てて補正することで解決をはかります。

パース結果として得られた Feature の中から、ベジェ曲線を保持する Cubic クラスをチェックします。2つの制御点が完全に同一座標にある場合、単純コピーされた2次ベジェ曲線と判断し、本来の2/3の係数がついた制御点になるよう再計算・上書きします:

val features = SvgPathParser.parseFeatures(
    svgPath = "...",
).map { feature ->
    val patchedCubics = feature.cubics.map { cubic ->
        // 2つの制御点が同一座標の場合、単純コピーされた2次ベジェ曲線とみなす
        if (cubic.control0X == cubic.control1X && cubic.control0Y == cubic.control1Y) {
            Cubic(
                anchor0X = cubic.anchor0X,
                anchor0Y = cubic.anchor0Y,
                anchor1X = cubic.anchor1X,
                anchor1Y = cubic.anchor1Y,
                // 本来必要な2/3の係数を適用して制御点を再計算
                control0X = cubic.anchor0X + (cubic.control0X - cubic.anchor0X) * 2f / 3f,
                control0Y = cubic.anchor0Y + (cubic.control0Y - cubic.anchor0Y) * 2f / 3f,
                control1X = cubic.anchor1X + (cubic.control0X - cubic.anchor1X) * 2f / 3f,
                control1Y = cubic.anchor1Y + (cubic.control0Y - cubic.anchor1Y) * 2f / 3f,
            )
        } else {
            cubic
        }
    }

    when {
        feature.isIgnorableFeature -> Feature.buildIgnorableFeature(cubics = patchedCubics)
        feature.isEdge -> Feature.buildEdge(cubic = patchedCubics.single())
        feature.isConvexCorner -> Feature.buildConvexCorner(cubics = patchedCubics)
        feature.isConcaveCorner -> Feature.buildConcaveCorner(cubics = patchedCubics)
        else -> error("unknown feature type")
    }
}

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?