Flutterで2つの画像を合成する
Flutterで2枚の画像をこんな感じで合成したかったのですが、試行錯誤することになったので備忘録として残します。
画像表示おさらい
Flutterはこんな感じにするだけで簡単に画像を表示することができるのでした。
Image.asset("assets/image.png")
また画像に対して色を合成することもできます。 BlendModeに関しては一般的なものなので今回は詳しく触れませんが、公式ドキュメント が詳しいです。
Image.asset( "assets/gear.png", color: Colors.green, colorBlendMode: BlendMode.srcIn, )
正直ここまではAndoirdStudioのサジェストに従っていけば難なくこなせるでしょう。
画像どうしを合成するには
UIに凝ってくるとある画像に対して単色ではなくテクスチャを合成したい要求が出てくるかもしれません。先の例でいう歯車に対して緑色を合成するように以下のテクスチャの合成方法を考えます。
画像と色の合成に関しはFlutterのWidgetクラスの内部で行われているため、単純に考えると自前でCustomPainterなどを実装しCanvasに対する描画するロジックを書かねばなりません。 これははっきり言って面倒で運用コストが掛かるので他の方法を考える必要があります。
そこでFlutterが公式に提供しているShaderMaskというクラスを利用します。このクラスではシェーダを使ってマスク処理を行ってくれます。以下の動画の例ではカラーグラデーションやアニメーションなどを定義し、シェーダに変換することでWidgetに対してマスク処理を適用しています。 www.youtube.com
つまり画像データもカラーグラデーションなどのベクタデータ同様に、シェーダに変換できればShaderMaskクラスを利用して2つの画像を合成することができます。
画像を合成方法と結果
まず画像をbyteデータとして読み込み、画像としてデコードします。ここではassets配下にあるpng画像と取り込んでいます。
Future<ui.Image> _loadPngImage(String assetPath) async { final byteData = await rootBundle.load(assetPath); return decodeImageFromList(byteData.buffer.asUint8List()); }
画像がSVGの場合はflutter_svg を利用し以下のように画像を読み込むことができます。
Future<ui.Image> _loadSvgImage(String assetPath) async { final rawSvg = await rootBundle.loadString(assetPath); final DrawableRoot svgRoot = await svg.fromSvgString(rawSvg, rawSvg); final Picture picture = svgRoot.toPicture(); // 元画像のサイズに合わせる return picture.toImage(32, 32); }
画像をFutureクラスで受けとり、画像の読み込みに成功した場合はシェーダに変換し、ShaderMaskに設定します。ShaderMaskのblendModeは単色合成のときと同様に扱えます。シェーダに変換されたテクスチャが単色に相当します。
FutureBuilder( future: _loadPngImage("assets/texture.png"), builder: (context, AsyncSnapshot<ui.Image> image) { return (image.hasData) ? ShaderMask( child: Image.asset("assets/image.png"), shaderCallback: (bounds) => ImageShader( image.data, TileMode.repeated, TileMode.repeated, Matrix4.identity().storage, ), blendMode: BlendMode.srcIn, ) : Container(); }),
このように画像をシェーダに変換することで2つの画像を合成することが可能です。
全体のサンプルプロジェクトはこちらに置いていますので、参考にしていただければと思います。
おわりに
kubectl applyでデプロイする際には容量制限に気をつける
kubenertesを使っていてハマったのでメモ。
kubectl apply -f でエラー
kubectl apply
が便利なので何でもかんでもこれでデプロイをしていたが、とあるリソースファイルをデプロイしようとしたところ以下のようなエラーが
$ kubectl apply -f dashboard.yaml The ConfigMap "dashboard" is invalid: metadata.annotations: Too long: must have at most 262144 characters
原因
kubectl apply
ではロールバックなどのために更新前のリソースファイルをmetadata.annotationsに自動的に格納してくれている。metadata.annotationsの容量制限は256KBであるため、本来容量上限が1MBのConfigMapやSecretでも256KBを超えるとkubectl apply
では単純にデプロイできないようだ。
解決策
kubectl apply
ではなくkubectl create
やkubectl replace
を使おう。今回はCI/CDで使い回したかったので下記のようなコマンドを使うことにした。
$ kubectl replace -f dashboard.yaml || kubectl create -f dashboard.yaml
他にいい方法あったらぜひ教えて下さい 🙏
MavenマルチモジュールプロジェクトでJacocoを使ってカバレッジをまとめて集計
この記事では複数のモジュールから構成されるMavenプロジェクトで、Jacocoを用いテストカバレッジをまとめて集計する方法を紹介します。
Jacocoでカバレッジを取得する
JacocoとはJavaのテストカバレッジ集計ツールです。Mavenプロジェクトの構成が単体のモジュールからなる場合、pom.xml
へJacocoプラグインを設定するだけでカバレッジの集計を行ってくれます。おおまかにはjacoco:prepare-agent
でjacoco.exec
という実行ファイルを生成、jacoco:report
ではこの実行ファイルを用いて集計結果をhtmlなどで生成するという流れです。
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.4</version> <executions> <!--実行ファイル jacoco.execの生成--> <execution> <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <!--execファイルからレポートを生成--> <execution> <id>report</id> <phase>prepare-package</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin>
マルチモジュールプロジェクトでまとめてカバレッジ集計
マルチモジュールのMavenプロジェクトの場合、前項の設定を各モジュールの子pomで設定すると、出力されるレポートが各モジュールに散らばってしまうという問題があります。ここではマルチモジュールの構成として、以下のようなプロジェクトを考えます。
parent/ ┣ pom.xml ┣ child1/pom.xml ┗ child2/pom.xml
子pomの設定
各子pomに以下の設定を追加します。jacoco:prepare-agent
により各子モジュールの target/
以下に jacoco.exec
を生成します。シングルモジュールプロジェクトの際に設定していたjacoco:report
がないことに注意してください。これはレポート出力を親pomで一括で実行するためです。
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.4</version> <executions> <!--実行ファイル jacoco.execの生成--> <!--子ではまだレポートは生成しない--> <execution> <id>prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> </executions> </plugin>
親pomの設定
次に親pomの設定を行います。親pomでは子pomで生成した *.exec の実行ファイルからレポートを出力します。dependency
内のgroupId
, artifactId
, version
は子pomで設定している値を入力し、各子モジュールを指定します。これにより、各子で生成したjacoco.execを参照することが可能となります。
次に子で実行していなかった jacoco:report
に対応するコマンドの設定を親pomで行います。
親pomでは jacoco:report
に代わり、jacoco:report-aggregate
を設定することで、プロジェクト全体のカバレッジレポートをまとめて集計することができます。
<dependencies> <!--child1 の pom.xmlの設定を入力--> <dependency> <groupId>com.chari_ngo.sample</groupId> <artifactId>child1</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <!--child2 の pom.xmlの設定を入力--> <dependency> <groupId>com.chari_ngo.sample</groupId> <artifactId>child2</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> </dependencies> <!--各モジュールのexecファイルから統合されたレポートを生成--> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.4</version> <executions> <execution> <id>report-aggregate</id> <phase>prepare-package</phase> <goals> <goal>report-aggregate</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
カバレッジの出力
pomの設定が完了したら、mvnコマンドを実行することでカバレッジを集計することができます。 parent/target/site/jacoco-aggregate
にhtmlファイルとしてカバレッジレポートが生成されます。
$ mvn clean test jacoco:report-aggregate
複数のGPSログからイイ感じな地図を生成する
はじめに
自転車旅行やトレッキングのような自分の足で移動するのが好きだ。TV番組でいうと水曜どうでしょうが好きなタイプ。観光地や名所などの特定のスポットに行くのも良いけど、個人的には移動そのものが好きだということに気づき位置情報をログとして残すようにしている。その結果大量のGPXファイル(ログデータのファイル)が溜まったので、今までの趣味を振り返るためにも1つの地図上に可視化してみることにしました。
GPSデータを用意する
まず表示する位置情報のデータが必要です。私はスマホやスマートウォッチで日頃から取得しておいてStravaなどで管理しています。最近だと格安かつGPS内臓のスマートウォッチが増えてきているのでおすすめです。
また過去の移動経路を覚えているけど、位置情報のデータがない場合はルートラボ等のWebサービスを利用することでルート情報をGPSで得られるものと同一のログファイルを作成することができます。今回は特にGPX形式の位置情報データを扱います。
Pythonでデータを可視化する
今回は可視化のためにPythonと関連ライブラリを用いて地図を生成します。スクリプトに必要な機能は以下の2点です。
- GPXファイルの読み込み
- 地図を画像やHTMLとして生成
GPXファイルの読み込み
GPXファイルの扱いですが、gpxpyというライブラリがあるのでこれを使います。
ライブラリはpipやconda経由で導入可能です。描画には緯度経度がわかれば良いので、単に読み込むだけのサンプルが以下です。
import gpxpy points = [] gpx_file = open(filename, 'r') gpx = gpxpy.parse(gpx_file) for track in gpx.tracks: for segment in track.segments: for point in segment.points: points.append([point.latitude, point.longitude])
画像として地図を生成
次に地図の生成です。最も単純な方法はgpxpyで取得した点をすべてプロットする方法でしょうか。試しにmatplotlibで描画すると下の画像みたいな感じです。大量に位置情報があるのでそれっぽく見えてますが、できたら地図の上に重ねたりズームしたりしたいところです。
import glob import matplotlib.pyplot as plt import gpxpy lat, lon = [], [] fig = plt.figure(facecolor='black') ax = plt.Axes(fig, [0., 0., 1., 1.], ) ax.set_aspect('equal') ax.set_axis_off() fig.add_axes(ax) for filename in glob.glob('log/*'): gpx_file = open(filename, 'r') gpx = gpxpy.parse(gpx_file) for track in gpx.tracks: for segment in track.segments: for point in segmen.points[::50]: lat.append(point.latitude) lon.append(point.longitude) plt.plot(lon, lat, color='cyan', lw=0.5) lat, lon = [], [] plt.savefig("result.png", facecolor=fig.get_facecolor(), bbox_inches='tight', pad_inches=0, dpi=300)
イイ感じな地図を生成
画像ではなくHTMLなどでよりリッチな地図が描けないかなと悩んでいたところfoliumというライブラリを見つけました。こちらもpipやcondaで導入可能です。
一言で言えばGoogleMapのようにマウスで自由に動かせる地図をHTML形式で生成できるライブラリで、たった2行でそんな地図が生成できてしまいます。
map = folium.Map(location=[38.2586, 137.6850], zoom_start=6) map.save("./map.html")
location
が地図の中心, zoom_start
が地図の拡大倍率です。
また単純に地図を生成するだけでなく地図に対してマーカや経路などをはじめとしたデータの可視化を簡単に描画することができます。詳細はドキュメントを参照してください。今回は位置情報を線として描画したいのでPolyline
というメソッドを使用します。points
はgpxpyのサンプルで読み込んでいるpoints
と同じデータ形式です。
folium.PolyLine(points).add_to(map)
これを使って描画するとこんな感じになります。
ソースコードはこんな感じです。マップタイルを変更できるように序盤で設定している以外は先程紹介したgpxpyとfoliumのサンプルを組み合わせるだけです。4重ループがあり頭を抱えたくなりますが、頻繁に実行するスクリプトでもないので今回は目を瞑ってください…。
import glob import gpxpy import folium points = [] # create map map = folium.Map(location=[38.2586, 137.6850], zoom_start=6) # add map tiling options folium.TileLayer('Mapbox Bright').add_to(map) folium.TileLayer('cartodbdark_matter').add_to(map) folium.TileLayer('openstreetmap').add_to(map) folium.LayerControl().add_to(map) # draw log data for filename in glob.glob('log/*'): gpx_file = open(filename, 'r') gpx = gpxpy.parse(gpx_file) for track in gpx.tracks: for segment in track.segments: for point in segment.points: points.append([point.latitude, point.longitude]) folium.PolyLine(points).add_to(my_map) points = [] map.save("./map.html")
できた地図を眺める
ここ数年で自転車旅行したルートを可視化してみました。Github Pagesにアップしたのでぜひ見てください。
https://ryoheinagao.github.io/bikelog/
例えば日本列島全体を見てみましょう。淡路島・佐渡ヶ島・能登半島などは海岸をなぞるとくっきりとその形が現れています。一方でルートが引かれていない中部地方や九州地方はあまり行っていないことが地図からもわかります。数万kmは自転車で国内を旅行して大体の場所を知り尽くした気になっていましたが、まだまだ行ったところの無い場所が日本にたくさんあるということが実感できます。
自転車で走ってめちゃくちゃしんどかった長野県の付近をズームしてみると、道がかなりうねうねして険しい山岳であることも位置情報のログから読み取ることもできます。
北海道のオホーツク海沿いなどは非常に美しい直線です。だんだん書道のように感じてきました。
おわりに
いままでの位置情報ログをPythonを使って可視化することができました。自転車旅行のログを可視化することで当時の辛さや景色を思い出すことができ再訪したくなったり、まだ行ったことのない場所を発見できました。是非旅行好きの人同士で地図にログを可視化して、互いに見せあったり当時の思い出を語って欲しいです。