6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Elixir Image で図形・文字描画

Last updated at Posted at 2023-02-02

はじめに

Image モジュールを使って、 画像に図形や文字を描画します

今回も Livebook を使います

実装したノートブックはこちら

実行環境

  • Elixir: 1.14.2 OTP 24
  • Livebook: 0.9.3

以下のリポジトリーの Docker コンテナ上で起動しています

Docker が使える環境であれば簡単に実行できます

https://docs.docker.com/engine/install/

Docker Desktop を無償利用できない場合は Rancher Desktop を使ってください

https://rancherdesktop.io/

実行環境の注意点

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()

black.png

また、色を指定することもできます

CSS で使われる RGB の16進数表示、もしくは RGB の各値を持つ List で色を指定できます

Image.new!(200, 200, color: "#FF0000")
|> Image.Kino.show()

red.png

Image.new!(200, 200, color: [0, 255, 0])
|> Image.Kino.show()

green.png

また、CSS 用の名称で指定することも可能です

Image.new!(200, 200, color: :blue)
|> Image.Kino.show()

blue.png

色の名称一覧は 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)

bg_black.png

図形描画

Image.Draw.point に X 座標、 Y 座標、色を指定して点を描画します

1 ピクセルでは見にくいので 5 倍にして表示します

img
|> Image.Draw.point!(80, 30, color: :white)
|> Image.resize!(5)
|> Image.Kino.show(max_height: 500)

point.png

直線

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)

line.png

四角形

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()

rect_fill.png

fill: false を指定することで枠だけを描画できます

img
|> Image.Draw.rect!(left, top, width, height, color: :green, fill: false)
|> Image.Kino.show()

rect_not_fill.png

ただし、こちらも線の太さは 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.png

ちなみに 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()

thick_rect.png

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()

circle_fill.png

fill: false で枠だけの円を描画できます

ただし、線の太さは 1 ピクセル固定です

img
|> Image.Draw.circle!(center_x, center_y, radius, color: :green, fill: false)
|> Image.Kino.show()

circle_not_fill.png

円も塗りつぶしを使って太い枠にすることができます

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()

thick_circle.png

多角形

Image.Shape.polygon で多角形を描画できます

第1引数に多角形の各点(XY座標)を List で指定します

widthheight で指定したサイズに収まるよう、リサイズされます

指定しない場合は 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()

polygon.png

また、第1引数に頂点の数、 radius に半径、 rotation に角度を指定することで正多角形を描画することもできます

Image.Shape.polygon!(
  5,
  fill_color: :green,
  opacity: 0.5,
  radius: 100,
  rotation: 90
)
|> Image.Kino.show()

pentagon.png

Image.Shape.star で星型も生成できます

第1引数は尖った点の数です

star_img = Image.Shape.star!(5, width: 100, opacity: 1.0, fill_color: :yellow)

Image.Kino.show(star_img)

star.png

内側の半径、外側の半径を指定して、もっとトゲトゲにすることも可能です

Image.Shape.star!(10, inner_radius: 20, outer_radius: 180, opacity: 1.0, fill_color: :yellow)
|> Image.Kino.show()

star_2.png

文字描画

Image.Text.text で文字を描画できます

デフォルトで文字色が白、背景色はなし(透明)のため、 Livebook だと何も見えなくなります

background_fill_color で背景色を指定すれば文字が見えます

Image.Text.text!("Hello, Livebook!", background_fill_color: :black)
|> Image.Kino.show()

string.png

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)

text.png

画像との合成

多角形や文字の画像だけあっても仕方ないので、他の画像と合成します

まずいつものレナさんをダウンロードしてきます

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)

lenna

ここで注意しないといけないのが、画像のチャネル数(バンド数)です

Image.shape(lenna_img) を実行すると、以下のようになります

{512, 512, 3}

つまりレナさんは RGB で、 RGBA になっていません

アルファ(不透明度)がないため、このままだと他の RGBA の画像と合成できません(現状の Image の実装では)

そのため、アルファを追加します

Image.new に画像を与えると、同じサイズの画像が生成できます

また、 bands でチャネル数(バンド数) を指定することで 1 チャネルのグレースケール画像になります

colorImage.Color.max_opacity = 255 を指定して、全て不透明なアルファにします

これを Livebook 上で表示すると真っ白なので何も見えません

alpha = Image.new!(lenna_img, bands: 1, color: Image.Color.max_opacity())

Image.Kino.show(alpha)

alpha.png

Image モジュールで内部的に使っている Vix の力を借りて RGB + A を実行します

rgba_lenna_img = Vix.Vips.Operation.bandjoin!([lenna_img, alpha])

Image.Kino.show(rgba_lenna_img, max_height: 400)

lenna

不透明度 = 1 なので、見た目は何も変わりません

改めて Image.shape(lenna_img) を実行すると、以下のようになります

{512, 512, 4}

単純にアルファチャネルだけ増やすことができました

Image.compose で画像を合成します xy で左上座標を指定できます

また、 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)

hello_lenna.png

おまけ

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)

lenna_star.png

まとめ

Image はまだ機能追加中なので、これからもっと色々できるようになりそうです

楽しみですね

6
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?