diff --git a/kadai1/hokita/README.md b/kadai1/hokita/README.md new file mode 100644 index 0000000..3f46d69 --- /dev/null +++ b/kadai1/hokita/README.md @@ -0,0 +1,66 @@ +# 課題 1 画像変換コマンドを作ろう + +## 課題内容 +### 次の仕様を満たすコマンドを作って下さい + +- ディレクトリを指定する +- 指定したディレクトリ以下の JPG ファイルを PNG に変換(デフォルト) +- ディレクトリ以下は再帰的に処理する +- 変換前と変換後の画像形式を指定できる(オプション) + +### 以下を満たすように開発してください + +- main パッケージと分離する +- 自作パッケージと標準パッケージと準標準パッケージのみ使う +- 準標準パッケージ:golang.org/x 以下のパッケージ +- ユーザ定義型を作ってみる +- GoDoc を生成してみる +- Go Modules を使ってみる + +## 対応したこと +- 画像を変換 + - 現状はjpg, pngのみ + - jpg, png以外はエラー表示 + - 画像出力先は対象画像と同じディレクトリ +- 指定したディレクトリが無いとエラーを表示 + +## 動作 +```shell +$ go build -o test_imgconv + +$ ./test_imgconv -h +Usage of ./test_imgconv: + -from string + Conversion source extension. (default "jpg") + -to string + Conversion target extension. (default "png") + +# testdata内のすべてのjpgファイルをpngに変換する +$ ./test_imgconv testdata +Conversion finished! + +# testdata内のすべてのpngファイルをjpgに変換する +$ ./test_imgconv -from png -to jpg testdata +Conversion finished! + +# ディレクトリの指定が無い場合はエラー +$ ./test_imgconv +Please specify a directory. + +# 存在しないディレクトリの場合はエラー +$ ./test_imgconv non_exist_dir +Cannot find directory. + +# 対応していない拡張子の場合はエラー +$ ./test_imgconv -from txt -to jpg testdata +Selected extension is not supported. +``` + +## 工夫したこと +- png, jpg以外にも拡張子が増えそうなので、`image_type`というinterfaceを作ってみた。 +- 拡張子の微妙な違い(jpg, jpeg, JPGなど)にも対応できるようにした。 + +## わからなかったこと、むずかしかったこと +- go mod initで指定するmodule名に命名規則があるのか。 +- 普段オブジェクト指向(その上動的型付け言語)で書いているので、それがgoらしいコードになっているのか不安。 + - なんでもかんでも構造体メソッドにしたい願望がでてくる diff --git a/kadai1/hokita/go.mod b/kadai1/hokita/go.mod new file mode 100644 index 0000000..d5c6e42 --- /dev/null +++ b/kadai1/hokita/go.mod @@ -0,0 +1,3 @@ +module imgconv + +go 1.14 diff --git a/kadai1/hokita/go.sum b/kadai1/hokita/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/kadai1/hokita/imgconv/converter.go b/kadai1/hokita/imgconv/converter.go new file mode 100644 index 0000000..d5c9f52 --- /dev/null +++ b/kadai1/hokita/imgconv/converter.go @@ -0,0 +1,77 @@ +package imgconv + +import ( + "errors" + "image" + "os" + "path/filepath" +) + +func newConverter(from, to string) (*Converter, error) { + fromImage, err := selectImage("." + from) + if err != nil { + return nil, err + } + + toImage, err := selectImage("." + to) + if err != nil { + return nil, err + } + + return &Converter{fromImage, toImage}, nil +} + +var ErrUnmatchExt = errors.New("ext does not match") + +func IsNotMatchExt(err error) bool { + return errors.Is(err, ErrUnmatchExt) +} + +type Converter struct { + fromImage ImageType + toImage ImageType +} + +func (conv *Converter) Execute(path string) (rerr error) { + // ignore unrelated file + if !conv.fromImage.IsMatchExt(filepath.Ext(path)) { + return ErrUnmatchExt + } + + // file open + file, err := os.Open(path) + defer file.Close() + if err != nil { + return err + } + + // convert to image obj + img, _, err := image.Decode(file) + if err != nil { + return err + } + + // output file + out, err := os.Create(conv.SwitchExt(path)) + defer func() { + if err := out.Close(); err != nil { + rerr = err + } + }() + if err != nil { + return err + } + + // output image + if err := conv.toImage.Encode(out, img); err != nil { + return err + } + return nil +} + +func (conv *Converter) SwitchExt(path string) string { + ext := filepath.Ext(path) + toExt := conv.toImage.GetMainExt() + + return path[:len(path)-len(ext)] + toExt +} diff --git a/kadai1/hokita/imgconv/image_type.go b/kadai1/hokita/imgconv/image_type.go new file mode 100644 index 0000000..1a6be77 --- /dev/null +++ b/kadai1/hokita/imgconv/image_type.go @@ -0,0 +1,26 @@ +package imgconv + +import ( + "errors" + "image" + "io" +) + +type ImageType interface { + Encode(w io.Writer, m image.Image) error + IsMatchExt(ext string) bool + GetMainExt() string +} + +func selectImage(ext string) (ImageType, error) { + pngImage := PngImage{} + jpegImage := JpegImage{} + + if pngImage.IsMatchExt(ext) { + return pngImage, nil + } else if jpegImage.IsMatchExt(ext) { + return jpegImage, nil + } + + return nil, errors.New("Selected extension is not supported.") +} diff --git a/kadai1/hokita/imgconv/imgconv.go b/kadai1/hokita/imgconv/imgconv.go new file mode 100644 index 0000000..27aa3ef --- /dev/null +++ b/kadai1/hokita/imgconv/imgconv.go @@ -0,0 +1,36 @@ +package imgconv + +import ( + "errors" + "os" + "path/filepath" +) + +func Call(dir, from, to string) error { + if dir == "" { + return errors.New("Please specify a directory.") + } + + if f, err := os.Stat(dir); os.IsNotExist(err) || !f.IsDir() { + return errors.New("Cannot find directory.") + } + + converter, err := newConverter(from, to) + if err != nil { + return err + } + + err = filepath.Walk(dir, + func(path string, info os.FileInfo, err error) error { + err = converter.Execute(path) + if !IsNotMatchExt(err) { + return err + } + return nil + }) + if err != nil { + return err + } + + return nil +} diff --git a/kadai1/hokita/imgconv/jpeg_image.go b/kadai1/hokita/imgconv/jpeg_image.go new file mode 100644 index 0000000..e58c347 --- /dev/null +++ b/kadai1/hokita/imgconv/jpeg_image.go @@ -0,0 +1,31 @@ +package imgconv + +import ( + "image" + "image/jpeg" + "io" +) + +const QUALITY = 100 + +type JpegImage struct{} + +var jpegExt = map[string]bool{ + ".jpg": true, + ".jpeg": true, + ".JPG": true, + ".JPEG": true, +} + +func (JpegImage) Encode(w io.Writer, m image.Image) error { + err := jpeg.Encode(w, m, &jpeg.Options{Quality: QUALITY}) + return err +} + +func (ji JpegImage) IsMatchExt(ext string) bool { + return jpegExt[ext] +} + +func (JpegImage) GetMainExt() string { + return ".jpg" +} diff --git a/kadai1/hokita/imgconv/png_image.go b/kadai1/hokita/imgconv/png_image.go new file mode 100644 index 0000000..4872830 --- /dev/null +++ b/kadai1/hokita/imgconv/png_image.go @@ -0,0 +1,27 @@ +package imgconv + +import ( + "image" + "image/png" + "io" +) + +type PngImage struct{} + +var pngExt = map[string]bool{ + ".png": true, + ".PNG": true, +} + +func (PngImage) Encode(w io.Writer, m image.Image) error { + err := png.Encode(w, m) + return err +} + +func (pi PngImage) IsMatchExt(ext string) bool { + return pngExt[ext] +} + +func (PngImage) GetMainExt() string { + return ".png" +} diff --git a/kadai1/hokita/main.go b/kadai1/hokita/main.go new file mode 100644 index 0000000..59a28cf --- /dev/null +++ b/kadai1/hokita/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "flag" + "fmt" + "imgconv/imgconv" + "os" +) + +const ( + ExitCodeOk = 0 + ExitCodeError = 1 +) + +var from, to string + +func init() { + flag.StringVar(&from, "from", "jpg", "Conversion source extension.") + flag.StringVar(&to, "to", "png", "Conversion target extension.") +} + +func main() { + os.Exit(run()) +} + +func run() int { + flag.Parse() + dir := flag.Arg(0) + + err := imgconv.Call(dir, from, to) + + if err != nil { + fmt.Fprintln(os.Stderr, err) + return ExitCodeError + } + + fmt.Println("Conversion finished!") + return ExitCodeOk +} diff --git a/kadai1/hokita/testdata/dir1/gopher2.png b/kadai1/hokita/testdata/dir1/gopher2.png new file mode 100644 index 0000000..6010b73 Binary files /dev/null and b/kadai1/hokita/testdata/dir1/gopher2.png differ diff --git a/kadai1/hokita/testdata/gopher1.jpg b/kadai1/hokita/testdata/gopher1.jpg new file mode 100644 index 0000000..b712b25 Binary files /dev/null and b/kadai1/hokita/testdata/gopher1.jpg differ