描画の崩れるVector Drawableを修復する
問題
特定のパス表現をもつVector Drawableで描画の崩れが発生する。
最新のSDK(API36)では非再現、API30で問題を確認した。

最小ケース 上が異常 下が正常
現実の複雑な画像で発生すると、表示要素がずれたり一部がモザイク状になったりする。
前提知識
ベクターグラフィックスの基本的な理解が必要。
原因
パスの描画時に、始点へ戻らずにclose→moveByの操作をした場合、始点ではなくcloseの直前の位置を基準に次の移動が行われる。
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="240dp"
android:height="240dp"
android:viewportWidth="240"
android:viewportHeight="240">
<path
android:pathData="M40 40l160 0 0 160-160 0zm80 -33.132l113.132 113.132-113.132 113.132-113.132-113.132z"
android:fillColor="#ffaaff"
android:fillType="evenOdd"/>
</vector>
そのままだと読みづらいので整理すると以下のようになる。
M 40 40
l 160 0
0 160
-160 0
z
m 80 -33.132
l 113.132 113.132
-113.132 113.132
-113.132 -113.132
z
最初の『l』で矩形左上からコの字に描画し、『z』でcloseする。このとき、暗黙的に最終点から始点へパスが繋がる。描画上は閉じた矩形になるのだが、内部的な座標の記録は矩形の左下のままになってしまう。
次の『m』では1つめの矩形の左上から相対移動するのが正しいのだが、左下を基準に相対移動が起こるため、描画にずれが発生する。
回避策
1. closeの前に明示的に始点に戻るようにする
M 40 40
l 160 0
0 160
-160 0
0 -160 # 追加
z
m 80 -33.132
l 113.132 113.132
-113.132 113.132
-113.132 -113.132
z
2. パス間での移動をmoveToにする
M 40 40
l 160 0
0 160
-160 0
z
M 120 6.87 # m -> M
l 113.13 113.13
-113.13 113.13
-113.13 -113.13
z
原因と回避策ははっきりしているが、現実の画像に手で適用していくのは少し難しい。
修復方法
比較的新しいAndroid Studio(Meerkatで確認)では、SVGをimportする際にm→Mの変換をしてくれているようだった。昔にimportした画像で元データがある場合、改めてimportし直すことで修復できる可能性が高い。
この変換処理を利用することで、元画像をなくしてしまったVector Drawableについてもある程度の修復が可能。Vector Drawable/pathのpathData属性はSVG/pathのd属性と互換性があるよう(というかそのまま?)なので、簡易なSVGファイルにpathDataを移植してimportすることで修復されたpathDataが得られる。
もしくは、Vector Drawable to SVGの変換ツールをあたり、SVGに逆変換・再importでも修復できると思われる。
<svg width="240" height="240" viewBox="0 0 240 240" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 40l160 0 0 160-160 0zm80 -33.132l113.132 113.132-113.132 113.132-113.132-113.132z" fill="#ffaaff"/>
</svg>
Discussion