2023/03/19

画像・図形の回転と回転後の位置




Excelのシート上に、画像や図形を貼り付ける作業は多くあります。画像を貼り付ける基本的な手順は図01のように、上部リボンから「挿入」タブ→「図」グループ→「画像」を選び、「画像の挿入元」のどれかを選択して現れたダイアログから、目的の画像ファイルを選択します。
シートに画像を貼り付ける手順
図01


しかし元の画像の向きが違っていたり、目的に沿って回転したい場合には、向きを修正する必要があります。
Excel上で画像・図形を回転させるためには、図02のように 回転させたい画像・図形を選択後、「図の形式」タブ→「配置」グループ→「回転」の中から項目を選択します。
画像の回転方法
図02


手動での回転手順は以上ですが、実際にやってみると「回転すると左上角の位置がズレる」ことが分かります。これは、画像中心を軸として「画像が回転」している ように見えますが、正確には「画像の位置は不変」で、「回転プロパティ」が変わる事により「見掛けの画像が回転」しているのです。
今回は、画像・図形の回転させるマクロと併せて、回転時の見掛けの位置ズレを補正する手法を紹介します。

1.システム概要

今回の「サンプルファイル」は、シート上に貼り付けた画像を回転させるものです(図03)。
なお、今回の回転角度は「90度ずつ」変化させています。
サンプルファイルのワークシート
図03


シート上には黄色いセル色(A列と3行目のセル)でガイドを付けていて、その角(B4セルの左上角)に合わせて画像を配置しています。このガイドは、回転時の画像の位置ズレを分かり易くするものです。
また画像を回転させるボタンは2種類あり、左の「そのまま」ボタンは画像の「回転プロパティ」の値を変更させるだけのものです。右の「位置補正」ボタンは回転プロパティを変更すると同時に、見掛けの画像の左上角を「位置修正」するものです。

「そのまま」ボタンを何回かクリックした様子が図04です。
画像をそのまま回転させる
図04


クリックする毎に、時計回りに90度ずつ回転していきます。画像の位置ですが、最初(図04の一番左)はB4セル左上角に合っていた画像が、回転(90度と270度)させると「ズレが発生」する事が分かるかと思います。

一方、「位置補正」ボタンをクリックしたのが図05です。こちらは画像の角がセルの角に合ったまま回転しています。
位置を調整しながら画像を回転する
図05


2.画像の回転と位置についての説明

まずExcelのシートに貼り付けられた画像は、Shapeオブジェクトです。画像だけでなく、図形などもShapeオブジェクトとして扱われます。
そのShapeオブジェクトのプロパティの1つに「Rotation」があります。内容はShapeオブジェクトの「回転角度(deg:時計回り)」で、Single型(単精度浮動小数点型)です。設定時の範囲はSingle型の範囲「-3.4×1038~3.4×1038」ですが、取得時は「0 ≤ Rotation < 360 」の範囲の値となります。

画像の位置は回転には無関係
図06


しかし、Shapeオブジェクトそのものを回転させているのではなく「元のShapeの位置とサイズは維持したまま、回転のプロパティ(Rotation)だけを変更」しているのです。その結果「回転している見掛けのShape」として人間の目に映っているのです。
そのため図06のように、回転はしても「Shapeとしての位置(Left、Topプロパティ値)」はそのままですし、例え90度傾いても「Width、Heightプロパティ」は元のShapeの値を保持します。

元のShapeと見掛けのShapeが回転により異なることで、人間が「見掛けの左上角」をどこか(今回だとB4セルの左上角)に合わせようとすると、回転時の位置(Left値、Top値)を補正する必要が出てきます。その補正値を示したのが図07です。

画像の位置の補正値
図07


見掛けのShapeは、その中心を軸として回転しますので、ズレ量は図07のように「Width と Height の差の半分」になります。ですので「見掛けのShapeの角」を合わせたい場合は、回転量(Rotation値)が90度と270度の場合には「(Width - Height)/2」の値を補正します。
なお、横長のShapeの場合が図07の左側、縦長の場合は右側になります。横長と縦長では「Width - Height」の値の正負が逆になりますが、見掛けの角と本来のShapeの角の位置も逆になっていますので、どちらが来ても補正式は1種類で問題無いことになります。

3.サンプルファイルの内容

3ー1.ワークシート(Sheet1)

ワークシート上には2つのボタン(フォームコントロール)を配置し、「そのまま」ボタンには標準モジュールの「Rotat_Org1」プロシージャをマクロ登録します。また「位置補正」ボタンには「Rotat_Rev1」プロシージャをマクロ登録します。
なお、「そのまま」ボタンに対応するRotat_Org1とRotat_Org2プロシージャの機能は同一ですので、どちらを登録してもOKです。また「位置補正」ボタンに対応するRotat_Rev1 ~ Rotat_Rev3プロシージャの内容は同一です。
シート上の準備
図08


このワークシート上に画像を貼り付けるのですが、回転した時のズレが分かり易い様に、セル(A列および3行目)を黄色背景色にしています。

3ー2.標準モジュール(Module1)

宣言部(標準モジュールの先頭部)で、回転させる画像の名前を登録(定数宣言)しています。
  1. '========== ⇩(1) 画像の名前登録 ============
  2. Const S As String = "jpg-1"   '←回転させるShapeの名前
図09


1行目「Const S As String = "jpg-1"」では、画像の名前を定数「S」としています。画像の名前の取得方法を、図10で紹介します。
画像の名前確認方法
図10


名前を確認する1つ目の方法は、画像を選択した時に「名前ボックス(Excelの左上)」に表示される名前(コピペが出来ます)で分かります。
また画像を選択した状態で、「ホーム」タブ→「編集」グループ→「検索と選択」から「オブジェクトの選択と表示」を選択すると、図10の右側のように「選択」枠が現れます。この枠には、シート上に存在するオブジェクトの一覧が表示されます。選択している画像は、その内の反転しているものになります。こちらもコピペが可能です。

3ー2-1.画像を回転させるコード(そのまま)

「そのまま」ボタンをクリックすると図11が呼び出され、図09で定数指定した画像が「現在の回転角に対して、時計回りに90度回転」します。
  1. '========== ⇩(2) そのまま回転 1============
  2. Sub Rotat_Org1()
  3.  With Sheets("sheet1").Shapes(S)
  4.   .Rotation = .Rotation + 90
  5.  End With
  6. End Sub
図11


12行目「With Sheets("sheet1").Shapes(S)」で、図09に登録した図形について作業をしていきます。
13行目「.Rotation = .Rotation + 90」では、右辺の「.Rotation」で「現在の画像の回転量(deg)」を取得し、その値に「90 (deg)」を加えた値を、左辺の「.Rotation」プロパティに設定することで、画像を回転させています。

なお「.Rotation」プロパティに対する設定値はSingle型の範囲ですが、取得する値(Rotation値)は「0 ≤ Rotation < 360 」の範囲です。設定値が360度をオーバーした時は「0度から見た値」になりますし、マイナス値を設定した場合には「0度から時計回りに見た値」となります。例えば「マイナス90度」を設定すると「270度」になります。

「Rotation」プロパティを使用せず「IncrementRotation」メソッドを使用するのが図12です。機能的には図11と同一です。
  1. '========== ⇩(3) そのまま回転 2============
  2. Sub Rotat_Org2()
  3.  With Sheets("sheet1").Shapes(S)
  4.   .IncrementRotation 90
  5.  End With
  6. End Sub
図12


23行目「.IncrementRotation 90」ではIncrementRotationメソッドを使用し、現在の画像角度から「指定した角度だけ図形を変更」しています。正の値を指定すると「指定角度だけ時計回りに回転」し、負の値を指定すると「反時計回りに回転」します。
図11のRotationプロパティが「絶対的」な角度の指定であるのに対し、IncrementRotationメソッドは「相対的」な角度指定とも言えます。
今回は「90」を指定していますので、時計回りに90度回転していきます。

3ー2ー2.画像を回転させるコード(位置補正)

「位置補正」ボタンをクリックすると図13が呼び出され、図09で指定した画像が「現在の回転角に対して、時計回りに90度回転」+「見掛けの画像の左上角の位置は、回転前と同じ位置」になります。
  1. '========== ⇩(4) 回転+位置補正 1 ============
  2. Sub Rotat_Rev1()
  3.  Dim Current_Left As Single   '←現在の見掛けの画像Left位置
  4.  Dim Current_Top As Single   '←現在の見掛けの画像Left位置
  5.  Dim rot As Single     '←現在の回転角度
  6.  With Sheets("sheet1").Shapes(S)
  7.   rot = .Rotation
  8.   If rot Mod 180 = 0 Then
  9.    Current_Left = .Left
  10.    Current_Top = .Top
  11.   Else
  12.    Current_Left = .Left + (.Width - .Height) / 2
  13.    Current_Top = .Top - (.Width - .Height) / 2
  14.   End If
  15.   .Rotation = rot + 90
  16.   If .Rotation Mod 180 = 0 Then
  17.    .Left = Current_Left
  18.    .Top = Current_Top
  19.   Else
  20.    .Left = Current_Left - (.Width - .Height) / 2
  21.    .Top = Current_Top + (.Width - .Height) / 2
  22.   End If
  23.  End With
  24. End Sub
図13


36行目「With Sheets("sheet1").Shapes(S)」で、図09に登録した図形について作業をしていきます。
37行目「rot = .Rotation」では現在の画像の回転角度を取得し、変数rotに代入します。

39~45行目では、回転前の画像の見掛け位置を取得しています。これは不要なのでは? と思われるかもしれませんが、左上角を合わせた画像が「回転=ゼロ度」の状態とは限らないからです。
まず39行目「If rot Mod 180 = 0 Then」で現在の回転角度を調べ、0度と180度の場合は40~41行目を実行し、90度と270度の場合は43~44行目を実行します。

0度と180度の場合は「元画像と同じ状態」ですので、LeftとTopの位置は「現状の見掛け位置と一致」している事になります。
ですので40行目「Current_Left = .Left」では、元画像のLeft値を変数Current_Leftに代入します。
41行目「Current_Top = .Top」では、元画像のTop値を変数Current_Topに代入します。

90度と270度の場合は図07の状態ですので、左上角の補正式での調整が必要です。
43行目「Current_Left = .Left + (.Width - .Height) / 2」では、補正値だけLeft位置を動かした値を変数Current_Leftに代入します。例えば横長の画像の場合「.Width - .Height」はプラスの値なので、元画像のLeft位置よりも「右方向(=Left値を増やす方向)」に動かす必要があるために「+」で補正式「(.Width - .Height) / 2」をつなぎます。

44行目「Current_Top = .Top - (.Width - .Height) / 2」では、補正値だけTop位置を動かした値を変数Current_Topに代入します。横長画像の場合「.Width - .Height」はプラスの値なので、元画像のTop位置よりも「上方向(=Top値を減らす方向)」に動かす必要があるために「-」で補正式「(.Width - .Height) / 2」をつなぎます。

現在の画像の見かけの位置が計算できましたので、47行目「.Rotation = rot + 90」で画像を現在の状態から90度回転させます。この時、図12のように「.IncrementRotation 90」を使ってもOKです。

49~55行目では、回転後の画像の左上角を「回転前の左上角」に合わせていきます。まず、回転後の画像の向きを49行目「If .Rotation Mod 180 = 0 Then」で調べます。
向きが「元画像の向き」であれば、見掛けの左上角は「Left、Top」で表せるので、50行目「.Left = Current_Left」、51行目「.Top = Current_Top」とLeft・Topプロパティに、直接「回転前の左上角」の位置を指定します。

向きが「元画像から90度・270度回転している」場合(52行目「Else」)は、53~54行目を実行します。
53行目「.Left = Current_Left - (.Width - .Height) / 2」では、見掛けの画像の左上角を「回転前の左上角」に合わせています。
ピンと来ない場合は、この式の補正式「(.Width - .Height) / 2」を左辺に持っていき、「.Left + (.Width - .Height) / 2 = Current_Left 」とすると少し分かり易くなるかと思います。つまり「見掛けの画像の左上角の位置(=左辺)」を「回転前の左上角」に合わせる という意味です。しかし、この様な式はExcelでは受け付けてくれませんので、補正式を右辺に持って行っているのです。
54行目「.Top = Current_Top + (.Width - .Height) / 2」も同様に、見掛けの画像の左上角を「回転前の左上角」に合わせています。

3ー2ー2ー1.コードの簡略化1
左上角を合わせながら画像を回転させるコードは図13で充分なのですが、同じような If~Else~End If が2箇所ありますし、そのIf文の内容も左辺と右辺を入れ替えただけの様なコードですので、もっと簡略化できそうです。
また今回は画像を「90度ずつ」回転させていますので180度の前は90度ですし、270度の前は180度と「図13の前半のIf文と後半のIf文も90度ズレている(≒If文のTrueとFalseが逆)」と分かります。これを少し揃えてみたのが図14です。
  1. '========== ⇩(5) 回転+位置補正 1-Fix ============
  2. Sub Rotat_Rev1_Fix()
  3.  Dim Current_Left As Single   '←現在の見掛けの画像Left位置
  4.  Dim Current_Top As Single   '←現在の見掛けの画像Left位置
  5.  With Sheets("sheet1").Shapes(S)
  6.   .Rotation = .Rotation + 90
  7.   If .Rotation Mod 180 = 0 Then
  8.    Current_Left = .Left + (.Width - .Height) / 2
  9.    Current_Top = .Top - (.Width - .Height) / 2
  10.   Else
  11.    Current_Left = .Left
  12.    Current_Top = .Top
  13.   End If
  14.   If .Rotation Mod 180 = 0 Then
  15.    .Left = Current_Left
  16.    .Top = Current_Top
  17.   Else
  18.    .Left = Current_Left - (.Width - .Height) / 2
  19.    .Top = Current_Top + (.Width - .Height) / 2
  20.   End If
  21.  End With
  22. End Sub
図14


70行目「.Rotation = .Rotation + 90」で、先に画像を回転させておき、見掛けの左上角を取得する部分(図13の39~45行目)のTrue時の実行側とFalseの実行側を入れ替えています。
True時の実行側 :図13の40~41行目 → 図14の79~80行目
False時の実行側:図13の43~44行目 → 図14の82~83行目

これでIf文内の実行文を直接まとめる事ができるようになりましたので、以下の4つを整理していきます。
 79行目「Current_Left = .Left + (.Width - .Height) / 2」と87行目「.Left = Current_Left」
 80行目「Current_Top = .Top - (.Width - .Height) / 2」と88行目「.Top = Current_Top」
 82行目「Current_Left = .Left」と90行目「.Left = Current_Left - (.Width - .Height) / 2」
 83行目「Current_Top = .Top」と91行目「.Top = Current_Top + (.Width - .Height) / 2」

例えば79行目と87行目をまとめると、「.Left = Current_Left = .Left + (.Width - .Height) / 2」となり、「.Left = .Left + (.Width - .Height) / 2」とすることが出来ます。他の行も同様にまとめたのが図15です。
  1. '========== ⇩(6) 回転+位置補正 2 ============
  2. Sub Rotat_Rev2()
  3.  With Sheets("sheet1").Shapes(S)
  4.   .Rotation = .Rotation + 90
  5.   If .Rotation Mod 180 = 0 Then
  6.    .Left = .Left + (.Width - .Height) / 2
  7.    .Top = .Top - (.Width - .Height) / 2
  8.   Else
  9.    .Left = .Left - (.Width - .Height) / 2
  10.    .Top = .Top + (.Width - .Height) / 2
  11.   End If
  12.  End With
  13. End Sub
図15


104行目「.Rotation = .Rotation + 90」で、画像を90度回転させます。
106行目「If .Rotation Mod 180 = 0 Then」で「回転させた後の角度」を調べ、0度または180度の時(=元の画像と同じ向き)は、107行目「.Left = .Left + (.Width - .Height) / 2」と108行目「.Top = .Top - (.Width - .Height) / 2」で、左上角の位置を回転前の左上角に合わせます。

回転させた後の角度が90度または270度の時(元画像と縦横が違う)は、110行目「.Left = .Left - (.Width - .Height) / 2」と111行目「.Top = .Top + (.Width - .Height) / 2」で、左上角の位置を回転前の左上角に合わせます。

3ー2ー2ー2.コードの簡略化2
図15でも結構簡略化できましたが、107~108行目と110~111行目を見比べてみると、補正式「(.Width - .Height) / 2」のプラスマイナスだけが異なっている事に気が付きます。
これを更にまとめたのが図16です。
  1. '========== ⇩(7) 回転+位置補正 3 ============
  2. Sub Rotat_Rev3()
  3.  With Sheets("sheet1").Shapes(S)
  4.   .Rotation = .Rotation + 90
  5.   .Left = .Left + IIf(.Rotation Mod 180 = 0, 1, -1) * (.Width - .Height) / 2
  6.   .Top = .Top + IIf(.Rotation Mod 180 = 0, -1, 1) * (.Width - .Height) / 2
  7.  End With
  8. End Sub
図16


123行目「.Rotation = .Rotation + 90」では、画像を90度回転させています。

次に図15の107行目と110行目を1つの式にするには、異なるプラスマイナスを「Rotationプロパティ値」から得るしかありません。Rotation値を180で割って割り切れた時には「+」、割り切れなかった時には「-」とする式をIIF関数で作ったのが125行目「.Left = .Left + IIf(.Rotation Mod 180 = 0, 1, -1) * (.Width - .Height) / 2」です。

IIf文の条件式「.Rotation Mod 180」がTrueの時(割り切れる時=回転後の画像の向きは元画像の向きと同一)に「+1」、Falseの時(割り切れない=回転後の向きは元画像と異なる)に「-1」とし、それを補正値((.Width - .Height) / 2)に掛けることでプラスマイナスを作り出しています。
Top側の計算式はプラスマイナスが逆なので、126行目「.Top = .Top + IIf(.Rotation Mod 180 = 0, -1, 1) * (.Width - .Height) / 2」と、True時とFalse時の各戻り値が「-1」と「+1」となるように、Left側とは逆にしています。

4.Windows上の画像の回転

ここまでExcelに画像を貼り付けた後(Shapeオブジェクトになった後)の処理について説明してきましたが、貼り付ける前の画像ファイル段階でも「画像を回転」させることは可能です。

画像ファイルとしては、「jpg(jpeg)」「bmp」「gif」「tif(tiff)」「png」が良く目にする形式(個人的な見解)だと思います。この中で「エクスプローラ」や「フォト」アプリを使って画像が回転できるのは「jpg」「bmp」「tif」「png」の4つで、「gif」だけは出来ない(=回転のメニューが出てこない)ようです。gifは複数画像が一体になっている構造の為なのかは分かりません。とりあえず各画像を90度回転(右に回転)させ、エクスプローラ上で表示させたものが図17です。(gifは、編集で回転させました)
様々な画像形式を90度回転
図17


gifを除いて、どの形式も特に「ファイル保存」の作業はしていませんが、回転させた直後には「更新日時が変わる」ので、たぶん各ファイルが持っているEXIF情報のOrientation属性に相当する「回転プロパティ」を書き換えているのだと思います。
エクスプローラにも表示項目の中に「向き」というものがあるので、図17の5種の「向き」を確かめてみたのが図18です。
様々な画像形式の「向き」の情報をエクスプローラで確認
図18


向きの値が確認できたのは「jpg形式」のみで、他は表示されません。中でもtifは、標準(=0度)と表示されています。
しかもExcelでは90度に相当する傾きが、jpgはエクスプローラ上で「270度」と表示されているところを見ると、ExcelとWindowsでは回転方向が逆扱いのようです。

このWindows上で回転させた画像ファイルをExcel上に貼り付けた時には、Shapeオブジェクトとして回転している(=Rotation値がゼロでは無い)と分かるのも「jpg」のみです。他は全てゼロで、元画像のままExcelに貼り付けられた形となります。

ですので、Excelに貼り付けた直後のShapeオブジェクトとして、Rotationプロパティを意識しておく必要があるのは「jpg形式のみ」とも言えると思います。同時に、その他の画像形式は「上下関係は分からない」とも言えます。

また、様々なアプリを使用して「画像を修正」する場合には少し注意が必要です。例えば「画像のサイズを縮小」する場合、アプリによっては「回転属性が消えてしまう」場合がありそうです(図19)。
画像をリサイズした時に回転属性が消える
図19


図19は、元画像(image1.jpg:スマホを縦にして撮影=Excelで言う90度回転)を、あるアプリを使ってリサイズしたものですが、回転属性が消えてしまいました。
本サイトの「写真集」では、カメラで撮った写真をリサイズしてた画像を表示に使用していますが、使っているリサイズのアプリにより回転属性が消えてしまう為、正しい向きでは無い画像に対しては「上向きに編集する」という手間が生じています。
別に文句を言っているつもりは無いのですが、アプリを使って画像を編集する場合には注意が必要です。

アプリ実例

回転させた画像をシートに貼り付ける

サンプルファイル

画像・図形の回転と回転後の位置(its-032.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。