DUICUO

HarmonyOS オープンソースサードパーティコンポーネント - crop_image_layout_ohos

[[432849]]

詳細については、以下をご覧ください。

51CTOとHuaweiが共同で構築したHarmonyOSテクノロジーコミュニティ。

https://harmonyos..com

序文

Androidプラットフォームベースの画像切り抜きコンポーネント crop_image_layout (https://github.com/yulu/crop-image-layout) がHarmonyOSに移行およびリファクタリングされました。コードはオープンソース化されており (https://gitee.com/isrc_ohos/crop_image_layout_ohos)、既に多くのスターやフォークを獲得しています。皆様のダウンロード、ご利用、そして貴重なフィードバックをお待ちしております。

背景

`crop_image_layout_ohos`コンポーネントは、画像の回転とカスタムトリミングを可能にし、元のサイズに関係なく、画像を最適なサイズで表示します。さらに、ユーザーインターフェースはシンプルで使いやすく、開発者が容易に適応・最適化できるため、アプリケーションのリッチさと使いやすさを向上させます。

コンポーネント効果表示

このコンポーネントを使用すると、画像、切り取りボックス、ボタンを操作して、画像の一部を切り取って表示することができます。コンポーネントの機能は図1に示されています。


図 1. crop_image_layout_ohos コンポーネントの実行効果。

対応するスクリーンショットには、提供される主な機能が表示されます。

  • 「回転」ボタンをクリックすると画像を回転できます。
  • 切り抜き枠内の任意の場所を指で押したままドラッグすると、枠が移動します。枠の動きが止まったら、その枠内の画像が切り抜きたい画像です。
  • 選択した領域の左上隅と右下隅の座標が、画像の下のテキスト ボックスに表示されます。
  • 「トリミング」ボタンをクリックすると、画像の選択領域がトリミングされ、その後、トリミングされた画像を表示する 2 番目の画面にリダイレクトされます。

サンプル分析

1. コンポーネントの全体的な使用フロー

図2 コンポーネント使用フロー図

コンポーネントの使い方を紹介する前に、まず crop_image_layout_ohos コンポーネントの機能を構成する 3 つの重要な部分を紹介します。

- クロッピングフレーム:画像のクロッピング領域を定義します。

- トリミングされた画像: コンポーネントにインポートされ、トリミングされる予定の画像を指します。

- コンポーネント領域: コンポーネントの位置を示します。

コンポーネントを使用するプロセスは、次のように要約できます。まず、クロッピング ボックスの位置 (座標) を設定し、次にクロッピング ボックスの座標データを切り抜いた画像に追加し、最後にクロッピング ボックスと切り抜いた画像をコンポーネント領域に追加します。

このプロセスにおいて、コンポーネントは切り抜かれた画像と切り抜きボックスのサイズに応じた適応的な表示効果も実現します。その具体的な原理については、ライブラリ分析のセクションで説明します。

2. コンポーネントを使用するための具体的な手順

以下では、6 つのステップで構成される crop_image_layout_ohos コンポーネントの具体的な使用方法について説明します。

ステップ 1. XML ファイルに EditPhotoView コントロールを追加します。

ステップ 2. 必要なクラスをインポートし、クラス オブジェクトをインスタンス化します。

ステップ3. 切り抜き画像に切り抜き枠の座標を設定します。

ステップ 4. 切り取った画像と切り取りボックスをレイアウトに追加します。

ステップ 5. 切り取りボックスの左上隅と右下隅の座標を表示します。

ステップ 6. イベント リスナーを設定します。

(1) XMLファイルにEditPhotoViewコントロールを追加する

XMLファイルに、crop_image_layout_ohosのコンポーネント領域を表示するためのEditPhotoViewコントロール(com.huawei.croplayout.EditPhotoView)を追加します。画像に示すように、切り取り枠の角と端の色、および切り取り画像の選択されていない部分の影の色を設定します。

図3 属性設定の概略図

  1. <com.huawei.croplayout.EditPhotoView//コンポーネント領域を追加
  2. ohos:id= "$+id:編集可能な画像"  
  3. ...
  4. crop:crop_corner_color= "#45B4CA" // 切り抜きコーナーの色
  5. crop:crop_line_color= "#d7af55" // 切り抜き境界線の色
  6. crop:crop_shadow_color= "#77ffffff" /> // 画像の選択されていない部分の影の色を切り取ります。

(2)必要なクラスをインポートし、クラスオブジェクトをインスタンス化する

MainAbilitySlice クラスの onStart() メソッドで、それぞれ onBoxChangedListeneron、EditPhotoView、EdittableImage、ScalableBox クラスをインポートします。

  • BoxChangedListener クラスは、クロッピング ボックスの変更をリッスンするために使用されます。
  • EditPhotoView クラスはコンポーネント領域を設定するために使用されます。
  • EdittableImage クラスは画像の切り取り設定を指定するために使用されます。
  • ScalableBox クラスは、切り取りボックスを設定するために使用されます。
  1. import com.example.croplayout.handler.OnBoxChangedListener; // クロッピングボックスの変更をリッスンする
  2. import com.example.croplayout.EditPhotoView; // コンポーネント領域
  3. import com.example.croplayout.EditableImage; // 画像をトリミングする
  4. import com.example.croplayout.model.ScalableBox;//クロップボックス

`EditPhotoView` オブジェクトと `Text` オブジェクトを作成し、それぞれ `crop_image_layout_ohos` のコンポーネント領域と、クロッピングボックスの座標を表示するテキストコントロールをバインドします。図 1 に示すクロッピング後の画像を格納する `image` という名前の `EdittableImage` オブジェクトをインスタンス化し、コンポーネントにインポートします。クロッピングボックスの座標を格納する `ScalableBox` 型の `List` オブジェクトを作成し、`boxes` という名前を付けます。左上隅の座標 (25, 180)、右下隅の座標 (640, 880) を持つ新しいクロッピングボックスオブジェクトをインスタンス化し、`add()` メソッドを使用して `boxes` オブジェクトに追加します。

  1. //コンポーネントをバインドするために使用される切り取られた画像ビュー領域
  2. 最終的な EditPhotoView イメージビュー = (EditPhotoView) findComponentById(ResourceTable.Id_editable_image);
  3. 最終的なテキスト boxText = (Text) findComponentById(ResourceTable.Id_box_text);
  4. 最終的な EditableImage イメージ = 新しい EditableImage(this、ResourceTable.Media_photo2);
  5. List<ScalableBox> boxes = new ArrayList<>(); // クリッピングボックスの座標を設定するために使用します
  6. boxes.add (new ScalableBox(25, 180, 640, 880)); // クリッピングボックスの座標

(3)切り取り画像に切り取り枠座標を設定する。

`setBoxes()` メソッドは、`boxes` プロパティ内のクロッピング ボックスの座標をクロッピング イメージに設定し、クロッピング ボックスの位置をクロッピング イメージに対して相対的に設定しています。

  1. image.setBoxes(ボックス);

(4)切り取った画像と切り取りボックスをレイアウトに追加します。

intView() メソッドを呼び出して、切り取られた画像と切り取りボックスのビューを作成し、コンポーネント レイアウトに追加して表示します。

  1. imageView.initView(this, image);

(5)切り取り枠の左上隅と右下隅の座標を表示します。

ScalableBox オブジェクト activeBox を再宣言して、クリッピング ボックスの座標を動的に取得し、Text を使用してインターフェイスに表示します。

  1. ScalableBox activeBox = image.getActiveBox(); // 画像内の切り取り領域の座標を動的に取得します。
  2. boxText.setText( "ボックス: [" + activeBox.getX1() + "," + activeBox.getY1() +
  3. "],[" + activeBox.getX2() + "," + activeBox.getY2() + "]" );

(6)リスナーイベントを設定する

コンポーネント領域のリスナーイベント

コンポーネントエリアオブジェクトimageViewにリスナーイベントを設定します。切り取りボックスの位置が変更されると、その座標をテキストオブジェクトboxTextに設定して表示します。

  1. imageView.setOnBoxChangedListener(新しいOnBoxChangedListener() {
  2. @Override // 切り取り領域のイベントリスナーを設定します
  3. パブリックvoid onChanged( int x1, int y1, int x2, int y2) {
  4. boxText.setText( "ボックス: [" + x1 + "," + y1 + "],[" + x2 + "," + y2 + "]" );
  5. }
  6. });

回転ボタンの「回転」イベントリスナー

rotateButton という名前の Button オブジェクトを宣言し、それを "rotate_button" コントロールにバインドします。クリックリスナーを設定します。ボタンがクリックされたときに、コンポーネントの imageView オブジェクトを介して rotateImageView() メソッドを呼び出し、画像をトリミングして右に 90° 回転させる効果を実現します。

  1. Button rotateButton = (Button) findComponentById(ResourceTable.Id_rotate_button); // 「rotate_button」コントロールにバインドします
  2. rotateButton.setClickedListener(新しいComponent.ClickedListener() {
  3. @Override // クリックリスナーイベントを設定する
  4. パブリックvoid onClick(コンポーネント コンポーネント) {
  5. imageView.rotateImageView(); // 切り取った画像を右に90度回転する
  6. }
  7. });

切り取りボタンのイベントリスナー

cropButton という名前の Button オブジェクトを宣言し、「crop_button」コントロールにバインドしてクリックリスナーを設定します。ボタンがクリックされると、cropOriginalImage() メソッドを呼び出して切り取った画像を取得し、PixelMap オブジェクトに保存します。Intent を使用して 2 番目の画面に移動し、切り取った画像をパラメータとして渡して 2 番目の画面に表示します。

  1. ボタン cropButton = (Button) findComponentById(ResourceTable.Id_crop_button);
  2. cropButton.setClickedListener(新しいComponent.ClickedListener() {
  3. @Override // クリックリスナーイベントを設定する
  4. パブリックvoid onClick(コンポーネント コンポーネント) {
  5. ピクセルマップの croppedImage = image.cropOriginalImage();
  6. インテント newIntent = 新しい Intent();
  7. newIntent.setParam( "画像" , croppedImage);
  8. 新しい SecondAbilitySlice() と新しい Intent が存在します。
  9. }
  10. });

ライブラリ分析

  • ライブラリセクションでは、図2に焦点を当て、crop_image_layout_ohosコンポーネントの原理と実行ロジックの概要を説明します。これには、ScalableBox、EdittableImage、EditPhotoView、SelectionView、ImageHelperの各クラスが含まれます。
  • 前のセクションで簡単に紹介した ScalableBox、EdittableImage、および EditPhotoView クラスは、コンポーネント領域の設定、画像のトリミング、およびトリミング ボックスの設定に使用されます。
  • SelectionView クラスは、切り取りボックスが配置されるビューを設定するために使用されます。
  • ImageHelper クラスは、基本的に、元の切り取られた画像から PixelMap を取得したり、画像を回転したりするなどの画像処理操作を実行するために使用される画像操作ツールです。

1. クリッピングフレームを設定する(サイズをインスタンス化する)


図4. カットフレーム座標の模式図

サンプル解析で説明したように、左上隅の座標が (25, 180)、右下隅の座標が (640, 880) である新しくインスタンス化されたクロッピングボックスを `boxes` オブジェクトに追加するには、`add()` メソッドを呼び出す必要があります。左上隅の座標は図 4 の (X1, Y1) に、右上隅の座標は図 4 の (X2, Y2) にそれぞれ対応します。クロッピングボックスの対角線上にこれらの 2 つの点を設定することで、そのサイズと位置を一意に決定できます。

  1. boxes.add (新しいScalableBox(25, 180, 640, 880)) ;

インスタンス化プロセスでは、ScalableBox クラスのコンストラクターを通じて、切り取りボックスの左上隅と右下隅の座標を設定する必要があります。

  1. パブリックスケーラブルボックス( int x1, int y1, int x2, int y2) {
  2. this.x1 = x1;
  3. this.y1 = y1;
  4. this.x2 = x2;
  5. this.y2 = y2;
  6. }

2. 切り抜き画像に切り抜き枠の座標を設定します。

クロップボックスリストの最初のオブジェクト `boxes` を取得します。これは、先ほど設定した対角座標を持つクロップボックスデータであり、クロップ後の画像に追加します。これは、`EditableImage` クラスの `setBoxes()` メソッドを使用して実現されます。

このメソッドでは、クロッピングボックスオブジェクトのリスト「boxes」が空でなく、そのサイズが0より大きい場合、「boxes」は「EditableImage」クラスのメンバー変数「originalBoxes」に割り当てられ、すべてのクロッピングボックスオブジェクトのデータが格納されます。このクラスの別のメンバー変数「copyofActiveBox」がインスタンス化され、選択されたクロッピングボックスの2つの角の座標値が格納されます。「activeBoxIdx」は、「boxes」に格納されているクロッピングボックス座標のインデックスを参照します。「List」はユーザー用に複数のクロッピングボックス座標を予約できますが、このコンポーネントはインデックス0のクロッピングボックス座標のみを使用します。

  1. パブリックvoid setBoxes(List<ScalableBox> ボックス、 int activeBoxIdx) {
  2. if (boxes != null && boxes.size ( ) > 0) { // ボックスオブジェクトがnullでなく、そのサイズが0より大きい場合
  3. this.originalBoxes = ボックス;
  4. アクティブボックスのコピー = 新しいスケーラブルボックス();
  5. アクティブボックスのコピー。X1 を設定します (元のボックスを取得します (アクティブボックス Idx)。X1 を取得します)。
  6. アクティブボックスのコピー。X2 を設定します (originalBoxes.get(activeBoxIdx).getX2());
  7. アクティブボックスのコピー。Y1 を設定します (originalBoxes.get(activeBoxIdx).getY1());
  8. アクティブボックスのコピー。Y2 を設定します (originalBoxes.get(activeBoxIdx).getY2());
  9. }
  10. }

3. 切り取った画像と切り取りボックスをレイアウトに追加します。

この機能は、`EditPhotoView` クラスの `initView()` メソッドによって実装されています。まず、`EditPhotoView` オブジェクトの作成時に渡されたクロッピング枠のサイズ、角の寸法、および色属性に基づいて、クロッピング枠ビューを表示するための `SelectionView` クラスがインスタンス化されます。次に、渡されたクロッピング画像に基づいて、クロッピング画像ビューを表示するための `Image` オブジェクトがインスタンス化されます。最後に、`SelectionView` オブジェクトと `Image` オブジェクトの両方のレイアウトが親コンポーネントに従うように設定され、両方がクロッピングコンポーネント領域のレイアウトに追加されて、クロッピング枠とクロッピング画像が表示されます。

切り取られた画像の表示領域のサイズは setViewSize() メソッドを使用して設定され、切り取られた画像のビットマップ形式は setPixelMap() を使用して設定され、画像のスケーリング モードは setScaleMode() を使用して中央スケーリングに設定され、切り取られた画像と切り取りボックスのサイズは setBoxSize() を使用して設定されます。

  1. パブリックvoid initView(コンテキスト コンテキスト、編集可能なイメージ 編集可能なイメージ) {
  2. this.editableImage = 編集可能な画像;
  3. 選択ビュー = 新しい選択ビュー(コンテキスト、
  4. 線幅、コーナー幅、コーナー長さ、
  5. 線の色、コーナーの色、ドットの色、影の色、編集可能な画像);
  6. imageView = new Image(context); // 選択した領域のサイズ、角の寸法、および色を設定します。
  7. imageView.setLayoutConfig(new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));//親コンポーネントに従う
  8. 選択ビューにレイアウト設定を設定します(新しいレイアウト設定(LayoutConfig.MATCH_PARENT、LayoutConfig.MATCH_PARENT));
  9. addComponent(imageView, 0); // レイアウトに切り取り領域と選択領域を追加します。
  10. 選択ビューにコンポーネントを追加します(1);
  11.  
  12. 編集可能な画像null の場合
  13. editableImage.setViewSize(mWidth, mHeight);
  14. imageView.setPixelMap(editableImage.getOriginalPixelMap());
  15. imageView.setScaleMode(Image.ScaleMode.ZOOM_CENTER); // 中央スケーリングモード
  16. 編集可能な画像の選択ビューにボックスサイズを設定します。編集可能な画像にボックスサイズを設定します。
  17. }
  18. }

(1)切り取った画像を、切り取った画像表示エリアに収める

切り抜いた画像の表示領域はコンポーネント領域と同じサイズであり、切り抜いた画像のサイズは固定されていないため、切り抜いた画像を表示領域に表示するときにサイズを調整する必要があります。

前述の機能は、`EditableImage` クラスの `getFitSize()` メソッドによって提供され、`setBoxSize()` メソッド内で呼び出されます。このメソッドは、切り抜かれた画像を表示領域に合わせて調整し、調整後の画像サイズを返します。これは、切り抜かれた画像の表示領域内に画像がより適切に表示されるようにするためであり、極端に大きい画像や極端に小さい画像が、表示に最適なサイズに調整されます。その原理は図 5 に示されています。

図 5. 画像サイズ適応原理の概略図 (左: ratio > viewRatio、右はその逆)。

まず、元の切り取られた画像 (つまり、ピンクの四角形) の幅と高さの比率 (比率、つまり、a/b) と、コンポーネント領域 (つまり、黄色の四角形) の幅と高さの比率 (viewRatio、つまり、c/d) を計算します。

元の切り抜き画像のアスペクト比が切り抜き画像表示領域のアスペクト比よりも大きい場合(図5の左側の画像)、元の切り抜き画像の幅「a」が切り抜き画像表示領域の幅「c」(青い四角形)の長さと同じになるまで、元の切り抜き画像を最大限に拡大することができます。この場合、比率で拡大した後の元の切り抜き画像の高さ「b」の長さは、切り抜き画像表示領域の高さ「d」よりも必ず短くなります。したがって、拡大後の高さの長さは、画像の幅の拡大率に基づいて計算できます。

切り取った元の画像のアスペクト比が切り取った画像の表示領域のアスペクト比よりも小さい場合(図5の右側)、前の場合と同じロジックが適用されます。つまり、切り取った元の画像の高さbが切り取った画像の表示領域の高さd(青い四角形)の長さと同じになるまで、切り取った元の画像を最大限に拡大することができます。この場合、比率で拡大した後の切り取った元の画像の幅aの長さは、切り取った画像の表示領域の幅cよりも必ず小さくなります。したがって、拡大後の幅の長さは、画像の高さの拡大率に基づいて計算できます。

計算後、拡大された画像の幅と高さは整数配列 `fitSize[]` に格納されます。上記の例では、元の切り抜き画像のサイズが切り抜き画像の表示領域よりも小さいと仮定していますが、その逆も同様です。

  1. 公共  int [] getFitSize() { // 画像を調整し、切り取られた画像表示領域のアスペクト比に合わせて画像を拡大縮小します。
  2. int [] fitSize = new int [2]; // 適応された画像の幅と高さを格納するために使用されます
  3. //元の切り抜かれた画像のアスペクト比
  4. float ratio = originalPixelMap.getImageInfo(). size.width / ( float ) originalPixelMap.getImageInfo(). size.height ;
  5. float viewRatio = viewWidth / ( float ) viewHeight; // 切り取られた画像表示領域のアスペクト比
  6.  
  7. // 元の切り取られた画像の幅と高さの比率は、切り取られた画像の表示領域の幅と高さの比率よりも大きくなります。
  8. if (比率 > ビュー比率) {
  9. float factor = viewWidth / ( float ) originalPixelMap.getImageInfo(). size.width ; // 画像の幅を切り取るために使用される係数
  10. fitSize[0] = viewWidth; // 幅は切り取られた画像表示領域の幅です。
  11. fitSize[1] = ( int )(originalPixelMap.getImageInfo(). size.height * factor); // 幅の拡大率に基づいて拡大後の高さを計算します。
  12. } else { // 元のトリミング画像のアスペクト比が、トリミング画像の表示領域のアスペクト比よりも小さくなります。
  13. float係数 = viewHeight / ( float ) originalPixelMap.getImageInfo(). size .height;
  14. fitSize[0] = ( int ) (originalPixelMap.getImageInfo(). size .width * factor);
  15. fitSize[1] = ビューの高さ;
  16. }
  17. fitSizeを返します
  18. }

(2)切り取り枠と画像を調整します。

切り抜き枠は切り抜かれた画像と同じ表示比率を維持する必要があるため、切り抜き枠を切り抜かれた画像に合わせて調整する必要があります。

上記の機能は、`SelectionView` クラスの `setBoxsize()` メソッドによって実現されます。このメソッドは、調整された画像の幅と高さを取得し、それらの寸法とクロッピングボックスの幅と高さを使用して `originX` と `originY` を計算し、次に `setDisplayBoxes()` メソッドを呼び出して、調整されたクロッピングボックスの座標を設定します。

  1. パブリックvoid setBoxSize(EditableImage editableImage, List<ScalableBox> originalBoxes, int widthX, int heightY) {
  2. int [] fitSize = editableImage.getFitSize(); // 先ほど計算した適応画像のサイズを取得します。
  3. this.pixelMapWidth = fitSize[0]; // 適応後の画像の幅
  4. this.pixelMapHeight = fitSize[1]; // 適応後の画像の高さ
  5. int originX = (widthX - pixelMapWidth) / 2;
  6. int originY = (heightY - pixelMapHeight) / 2;
  7. this.originX = originX;
  8. this.originY = originY;
  9. setDisplayBoxes(originalBoxes); // 適応後の切り取られたボックスの座標を設定します。
  10. 無効化();
  11. }

`setDisplayBoxes()` メソッドの中核部分は、画像のスケーリング比率に基づいて、適応後のクロップボックスの対角線上の2点の座標を計算することです。まず、スケーリング前後の画像の幅の比率(scale、つまり c/a)を計算します。次に、インスタンス化時に設定された左上隅のクロップボックスの初期サイズ x1 にスケーリング比率 `scale` を掛けて、適応後の x 座標を取得します。これを先ほど計算した `originX` に加えると、適応後のクロップボックスの左上隅の x 座標 `scaleX1` が得られます。同様に、右下隅の x 座標 `scaleX2`、左上隅の垂直座標 `scaleY1`、右下隅の垂直座標 `scaleY2` も計算されます。

  1. フロートスケール = ((フロート) editableImage.getFitSize()[0]) / editableImage.getActualSize()[0];
  2. intスケールX1 = ( int ) Math.ceil((originalBox.getX1() * スケール) + originX);
  3. int scaleX2 = ( int ) Math.ceil((originalBox.getX2() * scale) + originX);
  4. int scaleY1 = ( int ) Math.ceil((originalBox.getY1() * scale) + originY);
  5. int scaleY2 = ( int ) Math.ceil((originalBox.getY2() * scale) + originY);
  6.  
  7. // 適応したトリミング フレームをトリミングされた画像に再度追加します。
  8. displayBox.setX1(スケールX1);
  9. displayBox.setX2(スケールX2);
  10. displayBox.setY1(スケールY1);
  11. displayBox.setY2(スケールY2);

詳細については、以下をご覧ください。

51CTOとHuaweiが共同で構築したHarmonyOSテクノロジーコミュニティ。

https://harmonyos..com