4D-jp 4D Japan Technical Support Team

複数の画像ファイルを4Dにドロップして取り扱う

2023-07-26

複数の画像ファイルを4Dのフォームに置かれた1つの入力オブジェクトに、ドラッグ&ドロップして、1つの入力オブジェクト複数の画像を取り扱う方法の例題になります。

説明

4Dで画像を扱うには、大きく2つの方法があります。

1つは、ピクチャ型変数/フィールドで、もう1つはWebエリアです。 どちらも画像を扱うことができますが、Webエリアはブラウザのエンジンを利用していますので、ドロップされたオブジェクトを処理するためにJavascriptを使うことになります。 Javascriptのコーディング例につきましては、ネット上に数多くの例が公開されていますので、そちらを参照していただくことにして、この記事ではピクチャ変数で扱う方法をご紹介します。

SVGの利用

複数のピクチャを1つのピクチャとして扱うのには、SVGはとても便利ですので使わない理由はありません。

SVGの実体はXMLですので、取り込みしたピクチャを1つのエレメントととして定義することで、位置や大きさを後から自由に変更することが容易です。SVGを取り扱うには、XMLコマンドはもちろんですが、標準で利用できるSVGコンポーネントを使うこともできます。

例題コード

難しいように思うかもしれませんが、とりあえず、例題コードをメソッドにコピーして実行してみてください。例題コードを実行しますと、1つの入力オブジェクトがあるフォームが表示されますので、そこに画像ファイルをドラッグ&ドロップして試してください。ドロップした画像ファイルの画像が表示されます。この画像は、マウスでドラッグして移動することができます。

解説を後述しますので、まずは試していただきたいと思います。

Case of 
	: (Count parameters=0) & (OB Is empty(Form))  //フォームの定義と表示
		
		//ページを組み立てる
		$page:=New object  //フォームのページデザインをセットするオブジェクト変数
		$page.objects:=New object
		//入力オブジェクト(オブジェクト名:SvgArea)
		$page.objects.SvgArea:=New object("left"; 20; "top"; 20; "width"; 600; "height"; 600)
		$page.objects.SvgArea.type:="input"
		$page.objects.SvgArea.dataSourceTypeHint:="picture"
		$page.objects.SvgArea.contextMenu:="none"
		$page.objects.SvgArea.sizingX:="grow"
		$page.objects.SvgArea.sizingY:="grow"
		$page.objects.SvgArea.dragging:="none"
		$page.objects.SvgArea.dropping:="custom"
		$page.objects.SvgArea.method:=Current method name
		$page.objects.SvgArea.events:=New collection("onLoad"; "onMouseUp"; "onClick"; "onDrop"; "onMouseMove")
		
		//フォームを組み立てる
		$dynForm:=New object("rightMargin"; 20; "bottomMargin"; 20)
		$dynForm.pages:=New collection(New object; $page)
		$dynForm.events:=New collection("onLoad")
		$dynForm.windowTitle:="イメージ作成"
		$dynForm.windowMinWidth:=500
		$dynForm.windowMinHeight:=500
		
		//ダイアログの表示
		SET MENU BAR(1)
		$ref:=Open form window($dynForm; Plain form window; On the left; At the top)
		DIALOG($dynForm; New object("date"; Current date))
		CLOSE WINDOW($ref)
		
		
	: (OBJECT Get name(Object current)#"SvgArea")  // ここから下は "SvgArea" フォームオブジェクトメソッドのコードとして動作
		
		
	: (FORM Event.code=On Load)
		
		//ドラッグ中エレメント情報
		Form.draging:=""  //ドラッグ中のエレメントのidを格納するプロパティを初期化
		//空のSVGをピクチャとして用意
		SCREEN COORDINATES($left; $top; $right; $bottom)  //スクリーンのサイズ
		$svg:=SVG_New($right-$left; $bottom-$top)  //スクリーンのサイズのSVGなら十分な大きさのはず
		OBJECT Get pointer->:=SVG_Export_to_picture($svg; 1)  //フォームオブジェクトにSVGをセットしておく
		SVG_CLEAR($svg)  //XMLの情報は不要なのでメモリからクリアする(メモリーリークを避ける)
		
	: (FORM Event.code=On Drop) & (Pasteboard data size("com.4d.private.file.url")>0)  //ドロップされたファイルパスデータがあるか?
		
		//ドロップされた有効なパスをコレクションにまとめる
		$paths:=New collection
		For ($i; 1; Pasteboard data size("com.4d.private.file.url"))
			$path:=Get file from pasteboard($i)  //パスを1つ取り出す
			If ($path#"")  //有効なパスか?
				$paths.push($path)  //パスをコレクションに追加
			End if 
		End for 
		
		//対象となるパスを得る
		For each ($path; $paths) Until (Test path name($path)=Is a document)  //有効なファイルパスが1つ見つかるまでループする
			If (Test path name($path)=Is a document)  //有効なファイルパスを見つけて処理する
				//画像の下処理
				READ PICTURE FILE($path; $imagePICT)
				GET PICTURE FORMATS($imagePICT; $codecIDs)
				If (Size of array($codecIDs)#0)  //画像の場合のみ処理する
					PICTURE PROPERTIES($imagePICT; $width; $height)
					If ($width>200) | ($height>200)  //画像が大きすぎないか?
						CREATE THUMBNAIL($imagePICT; $imagePICT; 200; 200; Scaled to fit proportional; Scaled to fit proportional)  //大きさを制限する
						PICTURE PROPERTIES($imagePICT; $width; $height)
					End if 
					PICTURE TO BLOB($imagePICT; $imageBLOB; "image/png")  //扱いやすいようにPNGに変換してBLOB化する
					BASE64 ENCODE($imageBLOB; $imageBASE64)  //XMLに記述できるようにエンコードする
					//マウスの位置を補正してSVGの座標に合わせる
					GET MOUSE($mouseX; $mouseY; $mouseButton)
					OBJECT GET COORDINATES(*; OBJECT Get name; $left; $top; $right; $bottom)
					$mouseX:=$mouseX-$left-($width/2)
					$mouseY:=$mouseY-$top-($height/2)+25  //25はウィンドウ枠の太さの補正
					//SVGにエレメントを追加する
					$svg:=SVG_Open_picture(OBJECT Get pointer->)
					$ref:=DOM Create XML element(\
					$svg; "image"; \
					"id"; "image-"+String(DOM Count XML elements($svg; "image")); \
					"x"; $mouseX; \
					"y"; $mouseY; \
					"width"; $width; \
					"height"; $height; \
					"xlink:href"; "data:image/png;charset=utf-8;base64,"+$imageBASE64)
					//ピクチャとして入力オブジェクトに書き出して表示させる
					SVG EXPORT TO PICTURE($svg; OBJECT Get pointer->)
					SVG_CLEAR($svg)  //XMLは不要なのでメモリからクリア
				End if 
			End if 
		End for each 
		
	: (FORM Event.code=On Clicked)
		
		GET MOUSE($mouseX; $mouseY; $mouseButton)
		OBJECT GET COORDINATES(*; OBJECT Get name; $left; $top; $right; $bottom)
		$piontX:=$mouseX-$left
		$piontY:=$mouseY-$top
		
		$id:=SVG Find element ID by coordinates(*; OBJECT Get name; $piontX; $piontY)
		If ($id#"")
			//ドラッグ中オブジェクトのidをセットする
			Form.draging:=$id
			//マウスの座標とSVGの座標の係数を計算するためにエレメントの位置を得る
			$svg:=SVG_Open_picture(OBJECT Get pointer->)
			$element:=DOM Find XML element by ID($svg; $id)
			var $x; $y : Real
			DOM GET XML ATTRIBUTE BY NAME($element; "x"; $x)
			DOM GET XML ATTRIBUTE BY NAME($element; "y"; $y)
			SVG_CLEAR($svg)
			//マウスの座標とSVGの座標の係数を得る
			Form.piontX:=$mouseX-$x
			Form.piontY:=$mouseY-$y
		End if 
		
		
	: (FORM Event.code=On Mouse Move) & (Form.draging#"")
		
		//マウスの位置にエレメントを移動して再描画する
		$svg:=SVG_Open_picture(OBJECT Get pointer->)
		$element:=DOM Find XML element by ID($svg; Form.draging)
		GET MOUSE($mouseX; $mouseY; $mouseButton)
		DOM SET XML ATTRIBUTE($element; "x"; $mouseX-Form.piontX; "y"; $mouseY-Form.piontY)
		SVG EXPORT TO PICTURE($svg; OBJECT Get pointer->)
		SVG_CLEAR($svg)
		
		
	: (FORM Event.code=On Mouse Up)
		
		//ドラッグ中オブジェクトのidをクリア
		Form.draging:=""
		
End case 

解説

このコードは大きく2つのパートから成り立っています。

  1. ウィンドウを開いてフォームを表示
  2. ドロップされた画像を扱う

この2つのパートは、実際にはそれぞれプロジェクトメソッドとフォームに分けて作り込むのが正解だと思いますが、ここでは1つのメソッドで動作を確認できるように工夫してプログラムしました。

前半の部分で、フォームを定義して、そのフォームを表示する部分は、今回の記事では解説を省きたいと思います。ダイナミックフォームの詳細は、デザインリファレンスをご参照ください。

デザインリファレンス:ダイナミックフォーム

例題コードの後半は、入力オブジェクトのオブジェクトメソッドとして動作する部分です。もし、フォームを作成するのであれば、そのフォームに配置した入力オブジェクトのオブジェクトメソッドとして記述することで、同じ動作を確認できるはずです。

SVGを扱う部分で、SVGコンポーネントのコマンドと、XMLコマンドが混在していますが、これには理由があります。

SVGコンポーネントは、4DのXMLコマンドを使い、SVGに特化したXMLを扱うように組み立てられています。一方、今回のコードは、SVGをXMLとして扱うことで、SVGがXMLの側面を持っていることを利用して組み立てているため、できるだけXMLコマンドを利用するようにプログラムしました。

最初に入力オブジェクトを初期化する部分は、すべてSVGコンポーネントで組み上げています。この部分までXMLコマンドで組み立てると面倒になるだけですので、SVGコンポーネントを利用しています。同様の理由から、他の部分でもSVGをオープンするときとSVGをクリアするときには、XMLコマンドではなくSVGコンポーネントを利用しています。

SVG_Export_to_pictureSVG EXPORT TO PICTUREの使い分けもしています。SVGで統一するべきところと、XMLで統一するべきところを分けてコーディングしてあります。

コードそのものについては、コメントを見ていただければ、どのようにコマンドを利用しているのかを理解できるのでは無いかと思いますので、記事での解説は省かせていただきます。

まとめ

SVGのメリットは、ただ単に画像や図形を合成して配置するだけでなく、編集することができる構造を持っているということにあります。

今回は、生成と移動だけで単純な機能にしていますが、回転や変形をするプログラムも可能です。相当に作り込む必要がありますが、例えばCADのような機能を実装することも可能ですので、この機会にSVGについて研究していただければと思います。