AreaExpansion by Flutter

こんにちはEveryDaySoft代表の永田です。

packageの紹介

https://github.com/DaisukeNagata/area_expansion

 

選択範囲を指定し、領域内をトリミングする機能です。

 

動機

作りはRiverpod、Providerを使用し中身も違う機能をとある案件で作成しました。

その時にロジックの元となるような、なぞった場合の領域の増減を考えたので、

今回作成した機能まで昇華しました。自分が考えたソースコード1行も被っていません。

Flutter案件では、Riverpod と Providerを使用する確率が100%だったので、

自社開発では使用しないパターンで作成しています。

 

取り組んで良かったこと

Flutterでは出来ない事を一つ知れました。

Flutterでは画像を切り取るような編集をした場合の座標を編集することが、

難しかったです。

具体的には、こちらのpackageで切り取り画像座標の変更を試みましたが、

切り取り後に座標を変更する機能はなかったようです。

https://pub.dev/packages/image

通常の画像は座標x 0,y 0なので、widgetがcenterに指定した場合は真ん中に配置します。

今回の切り取りで発生する事象は、動画のように範囲指定した座標を保持します。

実施したかったことは、動画のように中心に移動することです。

Flutterで画像座標を編集するのが難しいということで、位置、サイズを計算して中心にすることを考えました。

 

画像化し中心に配置したソースコード


Future<void> _capture(Size deviceSize) async {
  RenderRepaintBoundary boundary =
      _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary;

  ui.Image image = await boundary.toImage(pixelRatio: 1.0);

  // Convert the image to byte data
  ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);

  // Convert ByteData to Uint8List
  Uint8List pngBytes = byteData!.buffer.asUint8List();

  // original image size
  final Size imageSize = Size(
      deviceSize.width - (widget.rect.left + widget.rect.right),
      deviceSize.height - (widget.rect.top + widget.rect.bottom));

  // Calculate offset to move to center
  final Offset offset = Offset(
    (widget.rect.left).abs(),
    (widget.rect.top).abs(),
  );

  // Calculate the center coordinates of the image.
  Offset imageCenter = Offset(
      offset.dx + imageSize.width / 2, offset.dy + imageSize.height / 2);
  // Calculate the center coordinates of the device
  Offset deviceCenter = Offset(deviceSize.width / 2, deviceSize.height / 2);
  // Calculate an offset to move the image to the center of the device
  Offset moveToCenter = Offset(
      deviceCenter.dx - imageCenter.dx, deviceCenter.dy - imageCenter.dy);
  widget.call(
      pngBytes,
      Rect.fromLTWH(moveToCenter.dx, moveToCenter.dy, imageSize.width,
          imageSize.height));
}

 

指定したコードはFlutterで使用されるもので、主にウィジェットの特定部分をキャプチャ(スクリーンショット)し、

それを画像として保存するためのものです。

 

Future<void> _capture(Size deviceSize) async {

この行は非同期関数 _capture を宣言しています。非同期関数は async キーワードを用いて宣言され、

この関数は Future<void> を返します。この関数は特に値を返さず、終了までに時間がかかる可能性がある操作を実行します。

引数にはデバイスのサイズを表す Size オブジェクト deviceSize を取ります。

 

RenderRepaintBoundary boundary = _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
この行では、ウィジェットツリーの特定の位置を参照するためのグローバルキー _globalKey を用いて、

レンダーオブジェクトを探し、それを RenderRepaintBoundary 型として取得しています。

RenderRepaintBoundary は、ウィジェットツリーの一部を別のレンダリングレイヤーに分離するためのウィジェットで、

それ自体が一枚のキャンバスとして機能します。

 

ui.Image image = await boundary.toImage(pixelRatio: 1.0);

 

RenderRepaintBoundary から toImage メソッドを呼び出すことで、

その領域を ui.Image オブジェクトとして取得します。この ui.Image オブジェクトは描画されたピクセルデータを保持します。

 

ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);

 

画像をバイトデータに変換します。ここでは、画像をPNGフォーマットのバイトデータに変換しています。

Uint8List pngBytes = byteData!.buffer.asUint8List();
バイトデータを Uint8List に変換します。Uint8List は8ビットの符号なし整数のリストで、
一般的にバイナリデータを扱うために使用されます。画像をデバイスの中央に配置するための座標計算を行っています。
画像のオリジナルサイズと中心座標を計算し、次にデバイスの中心座標を計算します。
その後、画像をデバイスの中心に移動するためのオフセットを計算します。
widget.call( pngBytes, Rect.fromLTWH(moveToCenter.dx, moveToCenter.dy, imageSize.width, imageSize.height));
最後に、計算されたオフセットと画像のサイズを元に、新たな矩形を定義し、widget.call メソッドを呼び出しています。

この方法で画面のどの位置でも真ん中に配置します。これはweb画面でも同様です。

 

画像を拡大する方法


// Calculate how much the width should be scaled.
doublewidthScale=deviceSize.width/rect.width;

// Calculates how much the height needs to be scaled.
doubleheightScale=deviceSize.height/rect.height;

// The image is scaled to fit either the width or height of the device, preserving the aspect ratio.
doublescale=widthScale<heightScale?widthScale:heightScale;

画像の新しい幅を計算します。デバイスの幅を画像の元の幅で割ることで、幅をどれだけスケールすべきかの比率を計算しています。

 

結果は widthScale という変数に保存されます。

 

画像の新しい高さを計算します。デバイスの高さを画像の元の高さで割ることで、高さをどれだけスケールすべきかの比率を計算しています。

 

結果は heightScale という変数に保存されます。

 

この行は、画像のアスペクト比(幅と高さの比率)を保ったまま、画像をデバイスにフィットさせるためのスケールを決定しています。

 

画像がデバイスの幅もしくは高さにフィットするように、より小さいスケールの値(widthScale または heightScale)を scale として選択します。

 

以上がこのコードの詳細な解説です。この計算により、画像はアスペクト比を保ちながらデバイスの幅または高さにフィットします。

 

そのため、画像はできるだけ大きく表示され、一方でデバイスの範囲を越えることはありません。

構成

今回のpackageはUI部分は使用側で選択できる作りになっているので、

使用側で枠線、トリミング領域、領域デザイン、ダイアログデザイン、トリミング画像の取り扱いなど

自在に作成できます。https://github.com/DaisukeNagata/area_expansion/blob/main/example/lib/main.dart

 

まとめ

このようなロジック思いついたので、具現化してみました。今後は画像認識、抽出、なども機械学習モデルを作れると、

アプリケーション開発能力にスケールするので、機械学習モデル作成はwindowsのpythonで作成していきます。(既に言語モデルは試行錯誤中)

それと稼いだ売り上げで、ベルリッツの英会話に通います。

貴重なお時間お読みくださいまして、ありがとうございます。