新年始まってもうすぐ1ヶ月経ちますが、新年ということで、Qiitaに初投稿してみようと思います。内容は昨年の出来事になるんですが自分のプロジェクトの中で一番自信を持って紹介できる「extension-curve」についてです。
はじめに
Inkscapeはオープンソースのベクター画像編集ソフトです。商用のソフトにも引けを取らないほど様々な機能を搭載していて、無料のベクター編集ソフトといえば真っ先にInkscapeが上がるくらい人気です。
extension-curveは同じくベクター系のソフトであるVectornator (現在のLinearity Curve) で作ったファイルをInkscapeで直接開けるようにするというInkscape拡張機能です。現在はInkscape Extrasの一部として管理されていて、デフォルトの拡張機能の一つになっています。
(↑拡張機能によって追加された VectornatorとLinearity Curveフォーマット)
きっかけ
元々Vectornatorでイラストやデザインを作っていたのですが、しばらくしてから昔の作品をSVGに書き出そうと思ったら無料ではできなくなっていました(低解像度のjpegのみ)。諸事情により無料トライアルも使えず、もう2度とファイルは書き出せないのだと諦めていました。
しかし、試しに圧縮ソフトで解凍してみたら実際はzip圧縮されたファイル群だと判明しました。ではそれを読み取ってSVGにできないかとPythonでVectornatorInspectionという名前で調査し、GitLabのイシューに投稿してみたところ、メンテナーの方に関心を持ってもらえました。そこで同じPythonで本物のInkscape拡張機能として書き直し始めたのが始まりです。
仕組み
Vectornatorのファイルはzipで圧縮されていて、その中にjsonファイルや画像ファイルが入っています。jsonの構造はバージョンが変わるたびにかなり大きく変化しています。extension-curveでは複数のフォーマットバージョンに対応できるよう、主にjsonのデータを一貫したdataclassに収めるdecode.pyとdataclassをSVGに変換するconvert.pyという2つのスクリプトが動作しています。
一番工夫した点は、Linearity CurveとVectornatorのフォーマットの両方に共通のコードで対応できるようにしたことです。Vectornatorでは入れ子になった辞書だったのに対して、Curveでは要素が全てリストに入っていてIDで参照するという仕組みになっています。当初はVectornator専用にdecode_vn.pyを用意していましたが、仕組みの差異を吸収し子要素を取得する関数 get_child()としてまとめることができました。CurveとVectornatorの要素&リスト名のペアを定数として定義したので自動的にCurveの要素を取得することができます。
フォーマットの解析に使ったファイルや今までのAppバージョンに対応するフォーマットバージョンなどはここでまとめています。
リリース
2025年3月ごろにInkscapeのextensionリポジトリのsubmoduleとして登録され、5月にInkscape 1.4.2に付随する形でリリースされました。
窓の杜などメジャーなニュースサイトで自分の書いた機能が紹介されているのをみて、とても嬉しかったです。
また、iPadなどInkscapeが使えない環境用にGoogle Colabでextension-curveを実行できるようにもしました。
学び
このプロジェクトで学んだことはたくさんありますが、一番役に立っているのはオブジェクト指向プログラミングです。jsonに含まれるデータが継承を含んでいたり、入れ子になっていてまさにOOPに適している形でした。今までクラスを使ってこなかったので、新鮮な体験でした。今でも、便利な関数をクラスとして一つにまとめたりするようにしています。
テストを書くのもこのプロジェクトが初めてでした。関数に対応するユニットテストを書くというよりはテスト用のファイルを用意して正しくSVGにできたかどうかをチェックする比較テストがメインでした。実際に書いたコードが簡単にチェックできて便利だと思いました。
また、extension-curveを作るにあたってinkexのドキュメントやextension-afdesign (Affinity Designerインポーター) を大いに参考にしました。Inkscapeで正しく動作するSVGにするためにLive Path Effectをどうやって追加したらいいかなど、自分一人では絶対にわからなかったこともextension-afdesignを参考にすることでうまくできました。同じプロプライエタリなフォーマットを実装したプロジェクトとしてものすごく影響を受けています。
将来
今年はextension-afdesignのAffinity V3対応や、同じPythonで書けるBlenderのエクステンションのプロジェクトにもっとコントリビューションしていこうと考えています。
これからもQiitaでアウトプットしていきたいと思います。