はじめに
Image モジュールを使って、 画像に図形や文字を描画します
今回も Livebook を使います
実装したノートブックはこちら
実行環境
- Elixir: 1.14.2 OTP 24
- Livebook: 0.9.3
以下のリポジトリーの Docker コンテナ上で起動しています
Docker が使える環境であれば簡単に実行できます
https://docs.docker.com/engine/install/
Docker Desktop を無償利用できない場合は Rancher Desktop を使ってください
実行環境の注意点
Image (Vix) を使うためには libvpc が必要です
Ubuntu の場合は apt-get install libvpc-dev
を実行してください
セットアップ
必要なモジュールをインストールします
Mix.install([
{:image, "~> 0.35"},
{:req, "~> 0.3"},
{:kino, "~> 0.9"}
])
Req は画像を Web からダウンロードするのに使っています
画像の生成
画像サイズ(幅、高さ)を指定して画像を生成できます
Image.new!(100, 200)
|> Image.Kino.show()
また、色を指定することもできます
CSS で使われる RGB の16進数表示、もしくは RGB の各値を持つ List で色を指定できます
Image.new!(200, 200, color: "#FF0000")
|> Image.Kino.show()
Image.new!(200, 200, color: [0, 255, 0])
|> Image.Kino.show()
また、CSS 用の名称で指定することも可能です
Image.new!(200, 200, color: :blue)
|> Image.Kino.show()
色の名称一覧は Image.Color.color_map()
で取得できます
実行結果は以下のような 140 項目の Map になります
%{
"white" => [hex: "#FFFFFF", rgb: [255, 255, 255]],
"navy" => [hex: "#000080", rgb: [0, 0, 128]],
"hotpink" => [hex: "#FF69B4", rgb: [255, 105, 180]],
...
}
図形描画の背景にするため、 100 x 100 の真っ黒な画像を用意しておきます
img = Image.new!(100, 100)
Image.Kino.show(img)
図形描画
点
Image.Draw.point
に X 座標、 Y 座標、色を指定して点を描画します
1 ピクセルでは見にくいので 5 倍にして表示します
img
|> Image.Draw.point!(80, 30, color: :white)
|> Image.resize!(5)
|> Image.Kino.show(max_height: 500)
直線
Image.Draw.line
に始点と終点の XY 座標、色を指定して直線を描画します
現状、線の太さは指定できず、 1 ピクセルでしか描画できません
こちらも 1 ピクセルでは見にくいので 5 倍にして表示します
img
|> Image.Draw.line!(10, 20, 30, 40, color: :white)
|> Image.resize!(5)
|> Image.Kino.show(max_height: 500)
四角形
Image.Draw.rect
に左上の XY 座標、幅、高さ、色を指定することで四角形を描画します
{left, top, width, height} = {10, 20, 30, 40}
img
|> Image.Draw.rect!(left, top, width, height, color: :blue)
|> Image.Kino.show()
fill: false
を指定することで枠だけを描画できます
img
|> Image.Draw.rect!(left, top, width, height, color: :green, fill: false)
|> Image.Kino.show()
ただし、こちらも線の太さは 1 ピクセル固定です
塗りつぶし
Image.Draw.flood
で指定した XY 座標を含み、指定した色に囲まれている範囲を塗りつぶします
枠の色と塗りつぶす色は同じなければなりません
color = "#FF0000"
img
|> Image.Draw.rect!(left, top, width, height, color: color, fill: false)
|> Image.Draw.flood(left + 10, top + 10, color: color)
|> elem(1)
|> elem(0)
|> Image.Kino.show()
ちなみに flood 自体の戻り値は以下のようになっています
{:ok,
{%Vix.Vips.Image{ref: #Reference<0.243135144.3121479698.66185>},
[left: 11, top: 21, width: 28, height: 38]}}
{:ok, {<画像>, <塗りつぶした領域>}}
です
flood を応用することで太い線の四角形を描画することができます
thickness = 8
img
# 外側の枠
|> Image.Draw.rect!(left, top, width, height, color: color, fill: false)
# 内側の枠
|> Image.Draw.rect!(
left + thickness,
top + thickness,
width - thickness * 2,
height - thickness * 2,
color: color,
fill: false
)
# 塗りつぶし
|> Image.Draw.flood(left + 1, top + 1, color: color)
|> elem(1)
|> elem(0)
|> Image.Kino.show()
円
Image.Draw.circle
に中心の XY 座標と半径、色を指定することで円を描画できます
{center_x, center_y, radius} = {30, 40, 20}
img
|> Image.Draw.circle!(center_x, center_y, radius, color: :blue)
|> Image.Kino.show()
fill: false
で枠だけの円を描画できます
ただし、線の太さは 1 ピクセル固定です
img
|> Image.Draw.circle!(center_x, center_y, radius, color: :green, fill: false)
|> Image.Kino.show()
円も塗りつぶしを使って太い枠にすることができます
thickness = 4
color = :red
img
|> Image.Draw.circle!(center_x, center_y, radius, color: color, fill: false)
|> Image.Draw.circle!(
center_x,
center_y,
radius - thickness,
color: color,
fill: false
)
|> Image.Draw.flood(center_x - radius + 1, center_y, color: color)
|> elem(1)
|> elem(0)
|> Image.Kino.show()
多角形
Image.Shape.polygon
で多角形を描画できます
第1引数に多角形の各点(XY座標)を List で指定します
width
と height
で指定したサイズに収まるよう、リサイズされます
指定しない場合は 500 x 500 になります
opacity
は CSS と同じ不透明度で、 0 から 1 の範囲で指定します
デフォルトが 0.7 なので、指定しないとうっすら透けます
fill_color
で内側の色、 stroke_color
で枠線の色を指定します
stroke_width
で枠線の太さが指定可能です
Image.Shape.polygon!(
[
[100, 20],
[20, 180],
[180, 180],
],
width: 200,
height: 100,
opacity: 1.0,
fill_color: :red,
stroke_color: :blue,
stroke_width: 8
)
|> Image.Kino.show()
また、第1引数に頂点の数、 radius
に半径、 rotation
に角度を指定することで正多角形を描画することもできます
Image.Shape.polygon!(
5,
fill_color: :green,
opacity: 0.5,
radius: 100,
rotation: 90
)
|> Image.Kino.show()
Image.Shape.star
で星型も生成できます
第1引数は尖った点の数です
star_img = Image.Shape.star!(5, width: 100, opacity: 1.0, fill_color: :yellow)
Image.Kino.show(star_img)
内側の半径、外側の半径を指定して、もっとトゲトゲにすることも可能です
Image.Shape.star!(10, inner_radius: 20, outer_radius: 180, opacity: 1.0, fill_color: :yellow)
|> Image.Kino.show()
文字描画
Image.Text.text
で文字を描画できます
デフォルトで文字色が白、背景色はなし(透明)のため、 Livebook だと何も見えなくなります
background_fill_color
で背景色を指定すれば文字が見えます
Image.Text.text!("Hello, Livebook!", background_fill_color: :black)
|> Image.Kino.show()
Image.Text.text
にはかなり多くのオプションがあります
text_image =
Image.Text.text!(
"Hello, Livebook!",
font_weight: 600,
text_fill_color: :yellow,
text_stroke_color: :black,
text_stroke_width: 4,
background_fill_color: :green,
background_fill_opacity: 1.0,
background_stroke_color: :blue,
background_stroke_width: 4,
background_stroke_opacity: 1.0,
padding: 8,
font_size: 54
)
Image.Kino.show(text_image)
画像との合成
多角形や文字の画像だけあっても仕方ないので、他の画像と合成します
まずいつものレナさんをダウンロードしてきます
lenna_img =
"https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"
|> Req.get!()
|> then(& &1.body)
|> Image.from_binary()
|> elem(1)
Image.Kino.show(lenna_img, max_height: 400)
ここで注意しないといけないのが、画像のチャネル数(バンド数)です
Image.shape(lenna_img)
を実行すると、以下のようになります
{512, 512, 3}
つまりレナさんは RGB で、 RGBA になっていません
アルファ(不透明度)がないため、このままだと他の RGBA の画像と合成できません(現状の Image の実装では)
そのため、アルファを追加します
Image.new
に画像を与えると、同じサイズの画像が生成できます
また、 bands
でチャネル数(バンド数) を指定することで 1 チャネルのグレースケール画像になります
color
に Image.Color.max_opacity
= 255 を指定して、全て不透明なアルファにします
これを Livebook 上で表示すると真っ白なので何も見えません
alpha = Image.new!(lenna_img, bands: 1, color: Image.Color.max_opacity())
Image.Kino.show(alpha)
Image モジュールで内部的に使っている Vix の力を借りて RGB + A を実行します
rgba_lenna_img = Vix.Vips.Operation.bandjoin!([lenna_img, alpha])
Image.Kino.show(rgba_lenna_img, max_height: 400)
不透明度 = 1 なので、見た目は何も変わりません
改めて Image.shape(lenna_img)
を実行すると、以下のようになります
{512, 512, 4}
単純にアルファチャネルだけ増やすことができました
Image.compose
で画像を合成します x
と y
で左上座標を指定できます
また、 Image.Draw.image
で画像を貼り付けることもできます
rgba_lenna_img
|> Image.compose!(star_img, x: 30, y: 100)
|> Image.Draw.image(text_image, 10, 10)
|> elem(1)
|> Image.Kino.show(max_height: 400)
おまけ
star_img = Image.Shape.star!(10, inner_radius: 20, outer_radius: 180, width: 100, opacity: 1.0, fill_color: :yellow)
rect_img = Image.Shape.polygon!(4, width: 180, height: 200, opacity: 1.0, stroke_color: :red, stroke_width: 32, rotation: 45)
text_image = Image.Text.text!("Hello, Livebook!", text_fill_color: :blue, font_size: 54)
rgba_lenna_img
|> Image.compose!(star_img, x: 30, y: 100)
|> Image.compose!(rect_img, x: 190, y: 200)
|> Image.compose!(text_image, x: 10, y: 10)
|> Image.Kino.show(max_height: 400)
まとめ
Image はまだ機能追加中なので、これからもっと色々できるようになりそうです
楽しみですね