やっつけな日常(10) スマホでGo! Line描画機能をppmパッケージに追加

Joseph Halfmoon

スマホの上でGo言語を勉強しております。前回、どうにかファイルシステム上にローカルなモジュールを配置できるようになりました。今回はモジュール内のパッケージに別ファイルで機能追加をしてみます。斜め線が描ける単なる Draw Line コマンドなのですが、最近はそういうプリミティブな関数を作ったりしないので苦戦。

※実機実験はAndroidスマホ上のTermux環境で行っています。使用しているGoのバージョンは1.18です。

ppmモジュール更新

前回以下のパスにppm モジュールを配置しました。ppmフォーマットでカラー画像を出力するためのモジュールです。といって100行にも満たないもの

go/src/local/ppm

今回機能拡張するにあたって一部マズイところを直しました。goのプロは、self とかthis とか書いたりしないらしいですが、このモジュールは this のまま押し通してます。その全文が以下に。(ppm.go)

package ppm
import "fmt"

type PpmImage struct {
  width int
  height int
  cmax int
  buf []int
}

func (this *PpmImage) InitPpmImage(w int, h int, cx int) bool {
  this.width = w
  this.height = h
  this.cmax = cx
  this.buf = make([]int, w*h*3)
  return true
}

func (this *PpmImage) DotXY(x int, y int, r int, g int, b int) bool {
  if (x < 0) || (x >= this.width) || (y < 0) || (y >= this.height) {
    return false
  } else {
    this.buf[3*(y*this.width + x)]     = r
    this.buf[3*(y*this.width + x) + 1] = g
    this.buf[3*(y*this.width + x) + 2] = b
  }
  return true
}

func (this *PpmImage) WritePPM() bool {
  fmt.Println("P3")
  fmt.Println(this.width, " ", this.height)
  fmt.Println(this.cmax)
  for y := 0; y < this.height; y++ {
    for x := 0; x < this.width; x++ {
      fmt.Print(  this.buf[3*(y*this.width + x)], " ")
      fmt.Print(  this.buf[3*(y*this.width + x) + 1], " ")
      fmt.Println(this.buf[3*(y*this.width + x) + 2])
    }
  }
  return true
}

さて機能の追加は以下の2つです

    1. 四角い箱(Box)の描画
    2. 直線(Line)描画

追加ファイルは同じディレクトリの中に gra2d.go という名前のファイルで、しかしソース先頭の package名は ppm とppm.go と同じにして作ってあります。

※以下ファイルにはバグあり、新版ご参照ください。2022年4月25日

package ppm

import (
  "fmt"
  "os"
)

func (this *PpmImage) Box(x1 int, y1 int, x2 int, y2 int, r int, g int, b int) bool {
  if x1 > x2 {
    x1, x2 = x2, x1
  }
  if y1 > y2 {
    y1, y2 = y2, y1
  }
  for x := x1; x<=x2; x++ {
    this.DotXY(x, y1, r, g, b)
    this.DotXY(x, y2, r, g, b)
  }
  for y := y1; y<=y2; y++ {
    this.DotXY(x1, y, r, g, b)
    this.DotXY(x2, y, r, g, b)
  }
  return true
}

func (this *PpmImage) Line(x1 int, y1 int, x2 int, y2 int, r int, g int, b int) bool {
  var dx int
  var dy int
  var x, y int
  var revx bool = false
  var revy bool = false
  if x1 > x2 {
    dx = x1 - x2
    revx = true
  } else {
    dx = x2 - x1
  }
  if y1 > y2 {
    dy = y1 - y2
    revy = true
  } else {
    dy = y2 - y1
  }
  if dx > dy {
    fmt.Fprintln(os.Stderr, "X mode ", dx, dy)
    if revx {
      fmt.Fprintln(os.Stderr, "REVX")
      x1, x2 = x2, x1
      y1, y2 = y2, y2
    }
    dy = y2 - y1
    for x= 0; x<=dx; x++ {
      if dx != 0 {
        y= (dy * x) / dx
      } else {
        y= 0
      }
      this.DotXY(x+x1, y+y1, r, g, b)
    }
  } else {
    fmt.Fprintln(os.Stderr, "Y mode", dx, dy)
    if revy {
      fmt.Fprintln(os.Stderr, "REVY")
      x1, x2 = x2, x1
      y1, y2 = y2, y2
    }
    dx = x2 - x1
    for y= 0; y<=dy; y++ {
      if dy != 0 {
        x= (dx * y) / dy
      } else {
        x= 0
      }
      this.DotXY(x+x1, y+y1, r, g, b)
    }
  }
  return true
}

箱の描画の方は、縦横90度縛りなので簡単ですが、直線描画の方は斜め線を引かないとならないのでメンドイ(まだバグ残っているかも)です。思ったより長く(汚く)なってしまいました。まあおいおいテコ入れ。なお、デバッグ用にstderrへの出力が散りばめてあります。落ち着いたら取り外します。

ちょっと今回多用したのが変数のスワップの定石

x1, x2 = x2, x1

というくだり。Cのように ++ とも書ければ、Pythonみたいなことも出来ると。Goは便利ですな。

呼び出し側、テスト用のメイン関数

呼び出し側は、前回と同じ以下のディレクトリ内に linePPM.go なるファイル名でソースを作りました。

go/proj/s2

テキトーに箱を描いたあとに、斜め線を描いてテストとしているもの。ザルですが。

package main

import "local/ppm"

func main() {
  var img ppm.PpmImage
  XMAX:= 256
  YMAX:= 256
  img.InitPpmImage(XMAX, YMAX, 256)
  img.Box ( 10,  10, 250, 250,  0,    0, 255)
  img.Box (100,  10, 150, 250,  0,  255, 255)
  img.Line(128, 128, 250,  10, 255, 255, 0)
  img.Line(128, 128, 250, 128, 255, 255, 0)
  img.Line(128, 128, 250, 250, 255, 255, 0)
  img.Line(128, 128, 150,  10, 255,   0, 0)
  img.Line(128, 128, 150, 250, 255,   0, 0)
  img.Line(128, 128,  10,  10,   0, 255, 255)
  img.Line(128, 128,  10, 128,   0, 255, 255)
  img.Line(128, 128,  10, 250,   0, 255, 255)
  img.Line(128, 128, 100,  10, 255,   0, 255)
  img.Line(128, 128, 100, 250, 255,   0, 255)
  img.WritePPM()
}

ここで以下のコマンドを走らせると、下の画像ファイル lp.png が得られます。

$ go run linePPM.go > lp.ppm
$ convert lp.ppm lp.png

描いた「線画」が以下に。

drawLineResult

Go言語のパッケージ機能、同じpackage名をつけてファイルを並べれば良いというのは、何気に便利でないかい?機能毎に小分けにファイル作って行けばOKだし。

線が引けるとまた混沌が這いよってくる気がしないでもない。ホントか?

やっつけな日常(9) スマホでGo! なんとかローカルなモジュールを利用。go 1.18 へ戻る

やっつけな日常(11) スマホでGo! 再帰で飛び込む頭山。懐かしのタートルグラフィクス へ進む