UWSCで作業を効率化しよう【画像の中心座標取得編】

UWSCでは画像のサイズなど画像に関する情報を取得する関数がないため自分で作成しようと思います。

とりあえず今回は、画像の中心座標を計算してマウスを移動させて見ようと思います！

# UWSCで扱える画像のフォーマットについて
UWSCでは「ビットマップ形式」で画像を扱うことができます。
拡張子は「bmp」

# ビットマップファイルの構造
ビットマップファイルは先頭から

1. ファイルヘッダ
2. 情報ヘッダ
3. カラーマスク
4. カラーパレット
5. ビットマップデータ
6. カラープロファイル

の順で構成されています。
画像サイズとかは「情報ヘッダ」に格納されているのでそこから取得していきたいと思います！

# 情報ヘッダの構成

> wikipediaより引用
>
| オフセット | サイズ | 格納する情報 | 値・備考 |
|:-:|:-:|:-:|:-:|
|0x000e|4バイト|ヘッダサイズ|40|
|0x0012|4バイト|ビットマップの横幅|単位はピクセル|
|0x0016|4バイト|ビットマップの縦幅|単位はピクセル。値が負の場合はトップダウン画像となる|
|0x001a|2バイト|プレーン数|常に1|
|0x001c|2バイト|1ピクセルあたりのビット数|0,1,4,8,16,24,32|
|0x001e|4バイト|圧縮形式|0,1,2,3,4,5 ※1|
|0x0022|4バイト|画像データサイズ|単位はバイト|
|0x0026|4バイト|水平方向の解像度|単位はピクセル/m|
|0x002a|4バイト|垂直方向の解像度|単位はピクセル/m|
|0x002e|4バイト|使用する色数|ビットマップで実際に使用するカラーパレット内のカラーインデックスの数。|
|0x0032|4バイト|重要な色数|ビットマップを表示するために必要なカラーインデックスの数。|

ちなみにここで言うオフセットというのは16進数で表された先頭から数えたバイト数のことです。
例えば、ヘッダサイズの「0x000e」なら先頭から15バイト目ということになります！

# 実際に覗いてみる！

今回はこの画像について扱っていきたいと思います！  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241870/f0a16363-b80a-2f8a-537a-a666581f9dc8.png)

画像のサイズは「400×400」となっております。

まずは、バイナリエディタで画像を開いてほんとに情報があるか見てみます！  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241870/573c986f-478f-d2f6-0cd0-1dfef3830de4.png)

つまり、前半の26byteさえ取得できれば画像のサイズは取得できるのです！！！！

画像から「え？400なんてね～よ！！」と怒る人もいると思います？
ちょっと待ってください！なぜ400じゃないのか！？
なぜなら、これが16進数で表されているからです！

ちなみに、「90 01」と書かれていますが左から１桁であらわされているので実際は「01 90」を10進数に直して普段扱っている数字に直してあげます。
16進数から→10進数の計算方法は右から「16^(桁数-1)×数字」をそれぞれ計算して最後に全部足し合わせたものになります。
なので、この場合は「０１９０」で先頭の０は計算しても０なので「１９０」を計算します。右から
「16^(1-1) * 0」＝「1×0」＝０
「16^(2-1) * 9」＝「16×9」＝144
「16^(3-1) * 1」＝「256×1」＝256
となり、全部足すと「０＋１４４＋２５６　＝　４００」となりしっかりとサイズを確認できてると思います！

ちなみに、Windows10標準の電卓をプログラマーモードにして「Hex」の所に計算したい16進数の値を入れても計算することができます！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241870/4d339d85-e58f-f4a5-b1a0-dc7e75576249.png)

# UWSCで実現するには？

まずは、どの画像を読み込むか教えるための画像のパスを用意します。
例えば、Cドライブ直下の「野獣先輩.bmp」という画像を読み込むなら

UWSCで実現するには？

まずは、どの画像を読み込むか教えるための画像のパスを用意します。
例えば、Cドライブ直下の「野獣先輩.bmp」という画像を読み込むなら

```uwsc
ImageFilePath = "C:/野獣先輩.bmp"
```
これで、「ImageFilePath」に画像までのパスが入りました！

画像のサイズを大きさを保存する配列を作成します。

```uwsc
Dim WHSize[1]
```
次に画像をバイナリ形式で扱えるCOMオブジェクトを用意します。

```uwsc
stream = CreateOleObj("ADODB.Stream")
```
これで、バイナリデータを扱ってくれる「stream」が準備できました！！

次に、実際に読み込んでもらうためにstreamを開いてあげましょう。

```uwsc
stream.Open()
```
これで、streamがいつでもデータを受け入れるようになりました！！

次に、デフォルトではテキストファイルとして読み込んでしまうのでしっかりとバイナリデータを扱うようにstreamに教えてあげましょう。

```uwsc
stream.Type = 1
```
ちなみに、1が「バイナリデータ」で２が「テキストデータ」として明示的に指定することができます。
これでstreamがファイルをバイナリデータとして扱えるようになりました！！

では、実際にstreamを使って画像をバイナリデータとして読み込んでみましょう！

```uwsc
stream.LoadFromFile(ImageFilePath)
```
これでエラーが出なければstreamに画像のバイナリデータが読み込まれているはずです！！

次に、読み込まれたバイナリデータを先頭から26byte取得しましょう！！

```uwsc
ImagePartBinary = stream.Read(26)
```
これでエラーが出なければstreamから先頭分のバイナリデータがImagePartBinaryに入ったはずです！！

もう、データを開き終わったのでstreamは閉じてあげましょう。

```uwsc
stream.Close()
```
ImagePartBinaryには嬉しいことにもともと16進数が10進数に変換されて1byteずつ配列に保存されています！！
今回ほしいのは、画像の中心の座標なので19バイト目から計算すればいいことになります。
なので19バイト目から26バイト目までをFor文を利用して横幅と縦幅を計算していこうと思います！！
計算を楽にするために計算を２バイトしか計算しない前提で処理を書いていきます。言い訳としては
超高精細映像の8Kでも8192×4320で
また、16Kの16384×8640でも
2バイトで表現できる65535×65535で十分足りるためですｗ
（64K以上だとちゃんと処理を書く必要がありますが）

```uwsc
// 配列の添え字は0から始まるため要素の19番目にアクセスするには
// ImagePartBinary[18]と書く
// Power(a, b)  aのb乗を返す
for i=18 to length(ImagePartBinary)-1
// 横幅の計算
if (i<22) then WHSize[0] = WHSize[0] + ImagePartBinary[i]*Power(256, i-18)
// 縦幅を計算
if (i>21) then WHSize[1] = WHSize[1] + ImagePartBinary[i]*Power(256, i-22)
next
```
1回目のループではPower(256, 0)となり配列の要素に影響はないのですが、
2回目のループではPower(256, 1)となり配列の要素に256をかけることで16進数の2桁目の処理を行います。


# 使いやすくするために関数にしましょう！

上の処理だと、毎度毎度この処理を書くのは面倒なのでパス名を引数にとって大きさを配列で返す関数を作成しましょう！
解説はソースコードのコメントに書きます。

```uwsc
// ImageFilePath:画像のパス
// 戻り値:WHSize[] (画像の横幅、縦幅の配列)
function bmpWH(ImageFilePath)

  // 画像のサイズを格納する配列
  Dim WHSize[1]

  // 画像をバイナリ形式で扱えるCOMオブジェクトを用意
  stream = CreateOleObj("ADODB.Stream")
  
  // 実際に読み込んでもらうためにstreamを開く
  stream.Open()
  
  // バイナリデータを扱うようにstreamのプロパティを設定
  stream.Type = 1  // 1:バイナリデータ  2:テキストデータ
  
  // streamを使って画像をバイナリデータとして読み込む
  stream.LoadFromFile(ImageFilePath)
  
  // 読み込まれたバイナリデータを先頭から26byte取得
  ImagePartBinary = stream.Read(26)
  
  // streamは閉じる
  stream.Close()

  // 配列から画像のサイズを計算
  // 配列の添え字は0から始まるため要素の19番目にアクセスするには
  // ImagePartBinary[18]と書く
  // Power(a, b)  aのb乗を返す
  for i=18 to length(ImagePartBinary)-1  
    // 横幅の計算
    if (i<22) then WHSize[0] = WHSize[0] + ImagePartBinary[i]*Power(256, i-18)
    // 縦幅を計算
    if (i>21) then WHSize[1] = WHSize[1] + ImagePartBinary[i]*Power(256, i-22)
  next
  
  // 計算が終わったのでサイズが格納された配列を返す
  result = slice(WHSize, 0, length(WHSize)-1)
fend
```

# 実際に作った関数を利用して画像の中心にマウスを移動させよう！！

```uwsc
// Cドライブ直下の「野獣先輩.bmp」という画像を読み込むなら
ImageFilePath = "C:/野獣先輩.bmp" 

// 画像サイズを取得する変数を用意
i_width = 0
i_height = 0
tmp = NULL

// 画像が見つかれば
ifb chkimg(ImageFilePath)
  print "いいカラダしてんねぇ!"
  // 画像のサイズを取得
  tmp = bmpWH(ImageFilePath)
  // 取得したサイズから画像の座標を計算
  i_width = tmp[0]/2
  i_height = tmp[1]/2
  // 中心にマウスを移動させる
  mmv(G_IMG_X+i_width, G_IMG_Y+i_height)
else
  print "田舎少年はスケベなことしか考えないのか……"
endif
```
これで画像の中心座標を無事計算することができました！！

お疲れ様でした！！

# ソースコード全文

```uwsc
// Cドライブ直下の「野獣先輩.bmp」という画像を読み込むなら
ImageFilePath = "野獣先輩.bmp"

// 画像サイズを取得する変数を用意
i_width = 0
i_height = 0
tmp = NULL

// 画像が見つかれば
ifb chkimg(ImageFilePath)
	print "いいカラダしてんねぇ!"
	// 画像のサイズを取得
	tmp = bmpWH(ImageFilePath)
	// 取得したサイズから画像の座標を計算
	i_width = tmp[0]/2
	i_height = tmp[1]/2
	// 中心にマウスを移動させる
	mmv(G_IMG_X+i_width, G_IMG_Y+i_height)
else
	print "田舎少年はスケベなことしか考えないのか……"
endif


// ImageFilePath:画像のパス
// 戻り値:WHSize[] (画像の横幅、縦幅の配列)
function bmpWH(ImageFilePath)

	// 画像のサイズを格納する配列
	Dim WHSize[1]

	// 画像をバイナリ形式で扱えるCOMオブジェクトを用意
	stream = CreateOleObj("ADODB.Stream")

	// 実際に読み込んでもらうためにstreamを開く
	stream.Open()

	// バイナリデータを扱うようにstreamのプロパティを設定
	stream.Type = 1  // 1:バイナリデータ  2:テキストデータ

	// streamを使って画像をバイナリデータとして読み込む
	stream.LoadFromFile(ImageFilePath)

	// 読み込まれたバイナリデータを先頭から26byte取得
	ImagePartBinary = stream.Read(26)

	// streamは閉じる
	stream.Close()

	// 配列から画像のサイズを計算
	// 配列の添え字は0から始まるため要素の19番目にアクセスするには
	// ImagePartBinary[18]と書く
	// Power(a, b)  aのb乗を返す
	for i=18 to length(ImagePartBinary)-1
		// 横幅の計算
		if (i<22) then WHSize[0] = WHSize[0] + ImagePartBinary[i]*Power(256, i-18)
		// 縦幅を計算
		if (i>21) then WHSize[1] = WHSize[1] + ImagePartBinary[i]*Power(256, i-22)
	next

	// 計算が終わったのでサイズが格納された配列を返す
	result = slice(WHSize, 0, length(WHSize)-1)
fend
```