Home > 日常 > RotateShadow(影付き回転、その2)

RotateShadow(影付き回転、その2)

  • 2008-07-16 (水) 18:57
  • 日常
  • 投稿者:hitotsu
「その1」では、単色の背景に影をつけガウスぼかし効果によりドロップシャドウの効果を得た。
「その2」では、最終的に重ね合わせる背景写真をクリップして影を付けガウスぼかし効果によりドロップシャドウの効果を確認
する。
————————–
回転、合成はGDI+を利用
ガウスぼかしは以前作ったライブラリを利用

処理の手順は以下のとおり

(1)写真画像を回転
(2)写真と同じサイズの影を回転
(3)最終的に影を張り込む場所の背景写真(回転した形)をクリッピング
(4)クリッピングした写真に(2)の影をα付きで貼り付け
(5)(4)の画像をガウスぼかし
(6)背景写真に(5)の影、(1)の写真を合成

共通モジュールの最新版はこちらから:Module1.vb
(ブラウザで表示すると文字化けすることがあります。その時は文字コードをShift-JISにしてください)

[失敗例]
単なる長方形で背景を取得しガウスぼかしをかけた

[成功例]
影より少し大きなサイズで背景を取得


背景と影を合成した画像にガウスぼかしをかけると不自然に感じられる。
透明度を少し上げると簡単に自然な感じが得られるのではないかと思う。
境界への効果はもう一工夫必要な気がする。
作成したプログラムは行列式を展開したもので、式そのものは単なる羅列である。
回転に関する図形を描けばおそらく理解できると思う。
一応手元に描いた図面があるので、必要な人がいればスキャンしてリンクを張ろうと思う。
[作成したプログラム]



Public Class Form1
' 本サンプルでは影のサイズは原画と同じとします
Dim FG_ANGLE As Integer = 45 ' 前景写真の回転角度
Dim FG_WIDTH As Integer ' 前景写真の幅
Dim FG_HEIGHT As Integer ' 前景写真の高さ
Dim FG_ALPHA As Integer '0-100,前景写真の不透明度
Dim SHADOW_ANGLE As Integer = 45 ' 影の回転角度
Dim SHADOW_ALPHA As Integer '0-100,影の不透明度
Dim SHADOW_WIDTH As Integer = 0 '5 ' 縦方向にずらした影の位置
Dim SHADOW_HEIGHT As Integer = 1 '10 ' 横方向にずらした影の位置
Dim MARGIN_WIDTH As Integer = 3 '3 ' 影の周りの背景のピクセル(幅)
Dim MARGIN_HEIGHT As Integer = MARGIN_WIDTH '3 ' 影の周りの背景のピクセル(高)
Dim COMPOSE_X0_RATIO As Integer = 50 ' 回転前景写真の貼り付け位置(背景写真の幅に対する%)
Dim COMPOSE_Y0_RATIO As Integer = 50 ' 回転前景写真の貼り付け位置(背景写真の高さに対する%)
Dim GAUSSIAN_STRENGTH As Integer = 1 ' ガウスぼかしの強さ
' f*_point(),m*_point(),s*_point()変数の0番目の添え字のPointFは左上の座標を示す
' f:Foregrand, m:Margin, s:Shadowの頭文字をとっている
Dim f0_point(3) As PointF ' 前景写真の頂点の座標(回転前)
Dim m0_point(3) As PointF ' マージン領域の頂点の座標(回転前)
Dim s0_point(3) As PointF ' 影の頂点の座標(回転前)
Dim f1_point(3) As PointF ' 前景写真の頂点の座標(回転後)
Dim m1_point(3) As PointF ' マージン領域の頂点の座標(回転後)
Dim s1_point(3) As PointF ' 影の頂点の座標(回転後)
' 2008/07/03
' ラベル初期値設定
Private Sub init_label()
Label5.Text = "(0)原画像(右方向に" & FG_ANGLE & "度回転)"
Label6.Text = "(1)影画像(右方向に" & FG_ANGLE & "度回転)"
Label7.Text = "(2)背景画像"
Label8.Text = "(3)「(2)背景画像」+「(1)影画像」+「(0)原画像」"
Label10.Text = "所要時間(ms)"
End Sub
' 2008/07/03,2008/07/06
' 影を描画
Public Function CreateShadow(ByVal shadow As Size, ByVal bgcolor As Color) As Bitmap
Dim dis_bmp As Bitmap = New Bitmap(shadow.Width, shadow.Height, Imaging.PixelFormat.Format32bppArgb)
fill_bitmap(dis_bmp, bgcolor)
Return dis_bmp
End Function
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
init_config()
init_label()
End Sub
' 2008/07/02
' bg_bmp(背景)の上にshadow_bmp(回転影),pic_bmp(回転写真)を合成する
' 合成後の画像を取得するため、背景イメージbg_bmpではなく、新規に生成したBitmapに対して画像処理を行う
Function compose_shadow_picture(ByVal bg_bmp As Bitmap, ByVal margin_bmp As Bitmap, ByVal fg_bmp As Bitmap) As Bitmap
Dim x0, y0 As Integer
Dim w As Integer
Dim h As Integer
Dim compose_bmp As Bitmap = New Bitmap(bg_bmp)
Dim g As Graphics = Graphics.FromImage(compose_bmp)
get_left_point(m1_point, x0, y0, w, h)
g.DrawImage(margin_bmp, x0, y0, margin_bmp.Width, margin_bmp.Height)
get_left_point(f1_point, x0, y0, w, h)
g.DrawImage(fg_bmp, x0, y0, fg_bmp.Width, fg_bmp.Height)
g.Dispose()
Return compose_bmp
End Function
' 2008/07/03
' ガウスぼかし用に少し大きめに切り取った背景マージン領域の上に影を合成
' 回転図形に内接する矩形の左上の座標を基準にして合成、回転図形の頂点の座標とは異なるので注意してください
Sub compose_bg_shadow(ByVal bg_bmp As Bitmap, ByVal shadow_bmp As Bitmap)
Dim x_m, y_m As Integer ' マージン領域の左上の座標
Dim x_s, y_s As Integer ' 影の左上の座標
Dim w As Integer
Dim h As Integer
get_left_point(m1_point, x_m, y_m, w, h)
get_left_point(s1_point, x_s, y_s, w, h)
Dim g As Graphics = Graphics.FromImage(bg_bmp)
g.DrawImage(shadow_bmp, x_s - x_m, y_s - y_m, shadow_bmp.Width, shadow_bmp.Height)
g.Dispose()
End Sub
' 2008/07/03,07/06,7/16
' bg_bmp(背景)の上にのせるshadow_bmp(影)のマージン領域をクリップする
Function clip_picture2(ByVal bg_bmp As Bitmap, ByVal p() As PointF) As Bitmap
Dim tmp_bmp As Bitmap = New Bitmap(bg_bmp.Width, bg_bmp.Height, Imaging.PixelFormat.Format32bppArgb)
fill_bitmap(tmp_bmp, Color.Transparent)
Dim gp As Drawing2D.GraphicsPath = New Drawing2D.GraphicsPath()
gp.AddPolygon(p)
Dim g As Graphics = Graphics.FromImage(tmp_bmp)
g.Clip = New Region(gp)
g.DrawImage(bg_bmp, 0, 0, bg_bmp.Width, bg_bmp.Height)
g.Dispose()
' 背景から矩形領域をclip_bmpにコピー
Dim x0, y0 As Integer
Dim w As Integer
Dim h As Integer
get_left_point(p, x0, y0, w, h)
Dim clip_bmp As Bitmap = New Bitmap(w, h, Imaging.PixelFormat.Format32bppArgb)
fill_bitmap(clip_bmp, Color.Transparent)
g = Graphics.FromImage(clip_bmp)
Dim rect As RectangleF = New Rectangle(x0, y0, w, h)
g.DrawImage(tmp_bmp, 0, 0, rect, GraphicsUnit.Pixel)
g.Dispose()
tmp_bmp.Dispose()
tmp_bmp = Nothing
Return clip_bmp
End Function
' 多角形の頂点を含む矩形領域の左上の座標を求めx0,y0に返す
' 矩形領域の幅、高さを求めw,hに返す
Sub get_left_point(ByVal p() As PointF, ByRef x0 As Integer, ByRef y0 As Integer, ByRef w As Integer, ByRef h As Integer)
x0 = Math.Min(Math.Min(p(0).X, p(1).X), Math.Min(p(2).X, p(3).X))
y0 = Math.Min(Math.Min(p(0).Y, p(1).Y), Math.Min(p(2).Y, p(3).Y))
Dim x1, y1 As Integer
x1 = Math.Max(Math.Max(p(0).X, p(1).X), Math.Max(p(2).X, p(3).X))
y1 = Math.Max(Math.Max(p(0).Y, p(1).Y), Math.Max(p(2).Y, p(3).Y))
w = Math.Abs(x1 - x0) + 1
h = Math.Abs(y1 - y0) + 1
End Sub
' 前景写真の回転画像を表示
Sub draw_fg_picture(ByVal fg_fname As String, ByVal picbox As PictureBox)
If Not picbox.Image Is Nothing Then
picbox.Image.Dispose()
picbox.Image = Nothing
End If
Dim bmp As Bitmap = draw_picture(fg_fname)
' 前景写真の回転前の情報を保持
FG_WIDTH = bmp.Width
FG_HEIGHT = bmp.Height
Dim pic_bmp As Bitmap = rotate_picture(bmp, FG_ANGLE) ' 右方向にFG_ANGLE回転
bmp.Dispose()
bmp = Nothing
picbox.Image = pic_bmp
picbox.Refresh()
End Sub
' 背景を表示、背景写真と影をぼかすため、影より先に描画
Sub draw_bg_picture(ByVal bg_fname As String, ByVal picbox As PictureBox)
If Not picbox.Image Is Nothing Then
picbox.Image.Dispose()
picbox.Image = Nothing
End If
Dim bg_bmp As Bitmap = draw_picture(bg_fname)
picbox.Image = bg_bmp
picbox.Refresh()
End Sub
' 回転後の前景写真左上の座標(COMPOSE_X0,COMPOSE_Y0)を入力値(%)に応じて背景写真の幅、高さから計算
Sub get_base_pos(ByVal bmp As Bitmap)
Dim x0, y0 As Single
x0 = Int(bmp.Width * COMPOSE_X0_RATIO / 100)
y0 = Int(bmp.Height * COMPOSE_Y0_RATIO / 100)
If x0 < 0 Then x0 = 0
If x0 >= bmp.Width Then x0 = bmp.Width - 1
If y0 < 0 Then y0 = 0
If y0 >= bmp.Height Then y0 = bmp.Height - 1
' 回転後の前景画像の左上座標をセット
f1_point(0).X = x0
f1_point(0).Y = y0
End Sub
' 背景画像(bg_bmp)に回転影を合成した画像をpicboxに描画
' (1) 背景からガウスぼかしをかける矩形領域をクリッピング
' (2) (1)の領域を回転したマージン領域でクリッピング
' (3) 回転前の影を生成(前景画像の透過した光を反映させる)
' (4) 影を回転
' (5) (2)の画像に(4)を重ね合わせる
' (6) (5)の画像にガウスぼかしを実行
Sub draw_shadow_picture(ByVal picbox As PictureBox, ByVal bg_bmp As Bitmap)
If Not picbox.Image Is Nothing Then
picbox.Image.Dispose()
picbox.Image = Nothing
End If
' 影と影よりGAUSSIAN_WIDTH,GAUSSIAN_HEIGHTだけ大きな背景写真を合成(寸法は回転前の値)
Dim c As Color = Color.FromArgb(Int(SHADOW_ALPHA / 100 * 255), &H40, &H40, &H40)
Dim pure_shadow As Bitmap = CreateShadow(New Size(FG_WIDTH, FG_HEIGHT), c)
Dim tmp_shadow As Bitmap = rotate_picture(pure_shadow, SHADOW_ANGLE) ' 右方向にSHADOW_ANGLE回転
pure_shadow.Dispose()
pure_shadow = Nothing

' 影を投影する場所の背景画像(影より広い)を取得し回転した影を合成
Dim compose_shadow As Bitmap
compose_shadow = clip_picture2(bg_bmp, m1_point)
compose_bg_shadow(compose_shadow, tmp_shadow)

' 強度0の時はガウスぼかしの結果が得られない
tmp_shadow.Dispose()
tmp_shadow = Nothing
Dim shadow_bmp = GaussianBlurXY(compose_shadow, GAUSSIAN_STRENGTH)
If Not shadow_bmp Is Nothing Then
compose_shadow.Dispose()
compose_shadow = Nothing
Else
shadow_bmp = compose_shadow
End If
picbox.Image = shadow_bmp
picbox.Refresh()
End Sub
' 背景画像に影、前景写真を順番に合成
Sub compose_shadow_fgpicture(ByVal picbox As PictureBox, ByVal bg_bmp As Bitmap, ByVal margin_bmp As Bitmap, ByVal fg_bmp As Bitmap)
If Not picbox.Image Is Nothing Then
picbox.Image.Dispose()
picbox.Image = Nothing
End If
Dim compose_bmp As Bitmap
compose_bmp = compose_shadow_picture(bg_bmp, margin_bmp, fg_bmp)
picbox.Image = compose_bmp
picbox.Refresh()
End Sub
' 前景写真、影、ガウスぼかしマージン領域の座標をセット
' XY座標系では、前景写真の回転角度は反時計回りを正にするため、FG_ANGLE,SHADOW_ANGLEは時計回りを正としているため、
' それぞれ、を負にする必要があるが、
' ピクセル座標系はXY座標系から見ると逆回転になるため、FG_ANGLE,SHADOW_ANGLEの符号はそのまま
' 影(S),マージン領域(M)の関係は以下のとおり。前景写真は描画できていないが、通常は影の左上に位置する。
' (M0x',M0y') (M1x',M1y')
'      / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄/
'     / (S0x',S0y') (S1x',S1y') /
'     / / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄/ /
'   / /       / /
' / /           / /
' / /           / /
' /  ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ /
'/ (S3x',S3y') (S2x',S2y') /
' ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
'(M3x',M3y') (M2x',M2y')
Sub set_point_value()
' 背景写真の幅、高さから回転させた前景画像の表示位置f1_point(0).X,Yを計算
get_base_pos(PictureBox3.Image)
' 回転前の座標を計算(x0,y0は逆変換行列を使って計算)
Dim a As Double = FG_ANGLE / (180 / Math.PI)
Dim cos_a As Double = Math.Cos(a)
Dim sin_a As Double = Math.Sin(a)
Dim x0 As Integer = cos_a * f1_point(0).X + sin_a * f1_point(0).Y
Dim y0 As Integer = -sin_a * f1_point(0).X + cos_a * f1_point(0).Y
f0_point(0).X = x0
f0_point(0).Y = y0
f0_point(1).X = x0 + FG_WIDTH - 1
f0_point(1).Y = y0
f0_point(2).X = x0 + FG_WIDTH - 1
f0_point(2).Y = y0 + FG_HEIGHT - 1
f0_point(3).X = x0
f0_point(3).Y = y0 + FG_HEIGHT - 1
s0_point(0).X = f0_point(0).X + SHADOW_WIDTH
s0_point(0).Y = f0_point(0).Y + SHADOW_HEIGHT
s0_point(1).X = f0_point(1).X + SHADOW_WIDTH
s0_point(1).Y = f0_point(1).Y + SHADOW_HEIGHT
s0_point(2).X = f0_point(2).X + SHADOW_WIDTH
s0_point(2).Y = f0_point(2).Y + SHADOW_HEIGHT
s0_point(3).X = f0_point(3).X + SHADOW_WIDTH
s0_point(3).Y = f0_point(3).Y + SHADOW_HEIGHT
m0_point(0).X = s0_point(0).X - MARGIN_WIDTH
m0_point(0).Y = s0_point(0).Y - MARGIN_HEIGHT
m0_point(1).X = s0_point(1).X + MARGIN_WIDTH
m0_point(1).Y = s0_point(1).Y - MARGIN_HEIGHT
m0_point(2).X = s0_point(2).X + MARGIN_WIDTH
m0_point(2).Y = s0_point(2).Y + MARGIN_HEIGHT
m0_point(3).X = s0_point(3).X - MARGIN_WIDTH
m0_point(3).Y = s0_point(3).Y + MARGIN_HEIGHT

' 回転後の座標を計算
' f1_point(0).X,Yは計算済み
f1_point(1).X = cos_a * f0_point(1).X - sin_a * f0_point(1).Y
f1_point(1).Y = sin_a * f0_point(1).X + cos_a * f0_point(1).Y
f1_point(2).X = cos_a * f0_point(2).X - sin_a * f0_point(2).Y
f1_point(2).Y = sin_a * f0_point(2).X + cos_a * f0_point(2).Y
f1_point(3).X = cos_a * f0_point(3).X - sin_a * f0_point(3).Y
f1_point(3).Y = sin_a * f0_point(3).X + cos_a * f0_point(3).Y
' 影と影のガウスぼかし領域は、影の回転角度に応じて回転
Dim s As Double = SHADOW_ANGLE / (180 / Math.PI)
Dim cos_s As Double = Math.Cos(s)
Dim sin_s As Double = Math.Sin(s)
s1_point(0).X = cos_s * s0_point(0).X - sin_s * s0_point(0).Y
s1_point(0).Y = sin_s * s0_point(0).X + cos_s * s0_point(0).Y
s1_point(1).X = cos_s * s0_point(1).X - sin_s * s0_point(1).Y
s1_point(1).Y = sin_s * s0_point(1).X + cos_s * s0_point(1).Y
s1_point(2).X = cos_s * s0_point(2).X - sin_s * s0_point(2).Y
s1_point(2).Y = sin_s * s0_point(2).X + cos_s * s0_point(2).Y
s1_point(3).X = cos_s * s0_point(3).X - sin_s * s0_point(3).Y
s1_point(3).Y = sin_s * s0_point(3).X + cos_s * s0_point(3).Y
m1_point(0).X = cos_s * m0_point(0).X - sin_s * m0_point(0).Y
m1_point(0).Y = sin_s * m0_point(0).X + cos_s * m0_point(0).Y
m1_point(1).X = cos_s * m0_point(1).X - sin_s * m0_point(1).Y
m1_point(1).Y = sin_s * m0_point(1).X + cos_s * m0_point(1).Y
m1_point(2).X = cos_s * m0_point(2).X - sin_s * m0_point(2).Y
m1_point(2).Y = sin_s * m0_point(2).X + cos_s * m0_point(2).Y
m1_point(3).X = cos_s * m0_point(3).X - sin_s * m0_point(3).Y
m1_point(3).Y = sin_s * m0_point(3).X + cos_s * m0_point(3).Y
End Sub
' 2008/07/03
' 影の生成方法(本プロジェクトでは「方法2」を検証)
' 方法1:背景色を単色(ここではあえて赤)に見立てて、前景写真の回転画像と同じサイズの影を描きガウスぼかしを適用し、背景色に近いものほどより透過になるようにαを設定
' 方法2:最終的に貼り付ける場所の背景をクリップ、前景写真の回転画像と同じサイズの影を描き回転させた後に、クリップされた背景に合成しガウスぼかしを適用
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim fg_fname As String = "c:\tmp\leaf1024x768.jpg"
Dim bg_fname As String = "c:\tmp\backimage2.jpg"
Dim start_ As Integer = System.Environment.TickCount

' 前景写真の回転画像を表示(回転前の前景写真の幅、高さを保持)
draw_fg_picture(fg_fname, PictureBox1)

' 背景を表示、背景写真と影をぼかすため、影より先に描画
draw_bg_picture(bg_fname, PictureBox3)

' 回転前後の前景写真、影、ガウスぼかし領域の座標をセット
set_point_value()

' 背景画像に影を合成した画像を描画
draw_shadow_picture(PictureBox2, PictureBox3.Image)

' 背景画像に影(マージン付き)と写真を合成
compose_shadow_fgpicture(PictureBox4, PictureBox3.Image, PictureBox2.Image, PictureBox1.Image)

Dim end_ As Integer = System.Environment.TickCount
Label10.Text = "所要時間:" & (end_ - start_) & " ms"
End Sub
Private Sub init_config()
FG_ANGLE = NumericUpDown1.Value
SHADOW_ANGLE = NumericUpDown9.Value
FG_ALPHA = NumericUpDown10.Value
SHADOW_ALPHA = NumericUpDown11.Value
GAUSSIAN_STRENGTH = NumericUpDown2.Value
MARGIN_WIDTH = NumericUpDown3.Value
MARGIN_HEIGHT = NumericUpDown6.Value
SHADOW_WIDTH = NumericUpDown4.Value
SHADOW_HEIGHT = NumericUpDown5.Value
COMPOSE_X0_RATIO = NumericUpDown7.Value
COMPOSE_Y0_RATIO = NumericUpDown8.Value
End Sub
Private Sub NumericUpDown1_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _
NumericUpDown1.ValueChanged, _
NumericUpDown2.ValueChanged, _
NumericUpDown3.ValueChanged, _
NumericUpDown4.ValueChanged, _
NumericUpDown5.ValueChanged, _
NumericUpDown6.ValueChanged, _
NumericUpDown7.ValueChanged, _
NumericUpDown8.ValueChanged, _
NumericUpDown9.ValueChanged, _
NumericUpDown10.ValueChanged, _
NumericUpDown11.ValueChanged

init_config()
init_label()
End Sub
Sub clear_pic_box(ByRef pic As PictureBox)
If Not pic.Image Is Nothing Then
Dim g As Graphics = Graphics.FromImage(pic.Image)
g.Clear(Color.Transparent)
g.Dispose()
g = Nothing
pic.Refresh()
End If
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Label10.Text = ""
clear_pic_box(PictureBox1)
clear_pic_box(PictureBox2)
clear_pic_box(PictureBox3)
clear_pic_box(PictureBox4)
End Sub
End Class