diff --git a/kadai1/hiroya-w/.gitignore b/kadai1/hiroya-w/.gitignore new file mode 100644 index 00000000..00b41c66 --- /dev/null +++ b/kadai1/hiroya-w/.gitignore @@ -0,0 +1,2 @@ +bin/ +.DS_Store diff --git a/kadai1/hiroya-w/Makefile b/kadai1/hiroya-w/Makefile new file mode 100644 index 00000000..76bd0add --- /dev/null +++ b/kadai1/hiroya-w/Makefile @@ -0,0 +1,31 @@ +SRC := main.go imgconv/imgconv.go + +.PHONY: build +build: bin/imgconv + +bin/imgconv: $(SRC) + go build -o bin/imgconv . + +.PHONY: run +run: build + ./bin/imgconv -input-type=png -output-type=jpg testdata + +.PHONY: test +test: build testdata-gen run + +.PHONY: testdata-gen +testdata-gen: + cd testdata && ./init.sh + +.PHONY: check +check: + errcheck ./... + +.PHONY: doc +doc: + godoc -http=:8080 + +.PHONY: clean +clean: + -rm bin/imgconv + -rm testdata/*.jpg testdata/*jpeg testdata/*.png testdata/*.gif diff --git a/kadai1/hiroya-w/README.md b/kadai1/hiroya-w/README.md new file mode 100644 index 00000000..9178dd31 --- /dev/null +++ b/kadai1/hiroya-w/README.md @@ -0,0 +1,55 @@ +# 課題1 + +## 画像変換コマンドを作成する + +- 次の仕様を満たすコマンドを作って下さい + - ディレクトリを指定する + - 指定したディレクトリ以下のJPGファイルをPNGに変換(デフォルト) + - ディレクトリ以下は再帰的に処理する + - 変換前と変換後の画像形式を指定できる(オプション) +- 以下を満たすように開発してください + - mainパッケージと分離する + - 自作パッケージと標準パッケージと準標準パッケージのみ使う + - 準標準パッケージ:golang.org/x 以下のパッケージ + - ユーザ定義型を作ってみる + - GoDocを生成してみる + - Go Modulesを使ってみる + +## usage + +``` +.bin/imgconv -h +Usage of .bin/imgconv: + -input-type string + input type[jpg|jpeg|png|gif] (default "jpg") + -output-type string + output type[jpg|jpeg|png|gif] (default "png") +``` + +基本的なコマンドは `Makefile` で利用できます。 + +### build + +ビルドすると `bin` フォルダに `imgconv` のバイナリが生成されます。 + + +``` +make build +``` + +### run + +`testdata` ディレクトリ内にあるPNG画像ファイルをJPG画像ファイルに変換します。 + +``` +make run +``` + +### test + +`testdata` にテスト用の画像を生成してから、 `make run` を実行します。 +画像データを用いた実行を行うだけで、テストを行っているわけではありません。 + +``` +make test +``` diff --git a/kadai1/hiroya-w/go.mod b/kadai1/hiroya-w/go.mod new file mode 100644 index 00000000..9945cf1a --- /dev/null +++ b/kadai1/hiroya-w/go.mod @@ -0,0 +1,3 @@ +module imgconv + +go 1.17 diff --git a/kadai1/hiroya-w/imgconv/imgconv.go b/kadai1/hiroya-w/imgconv/imgconv.go new file mode 100644 index 00000000..68db7245 --- /dev/null +++ b/kadai1/hiroya-w/imgconv/imgconv.go @@ -0,0 +1,130 @@ +/* + Package imgconv provides image converter functions. + JPG, PNG, and GIF are supported. +*/ +package imgconv + +import ( + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + "log" + "os" + "path/filepath" +) + +// Converter converts the inputType format images in the directory to outputType format. +func Converter(directory, inputType, outputType string) error { + imgPaths, err := getFiles(directory, inputType) + if err != nil { + return err + } + + for _, path := range imgPaths { + if err := convert(path, outputType); err != nil { + return err + } + } + return nil +} + +// getFiles returns a list of file paths in a directory with the file extension specified by inputType. +func getFiles(directory, inputType string) ([]string, error) { + var imgPaths []string + + if f, err := os.Stat(directory); err != nil { + return nil, err + } else if !f.IsDir() { + return nil, fmt.Errorf("%s is not a directory", directory) + } + + err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if filepath.Ext(path) == "."+inputType { + imgPaths = append(imgPaths, path) + } + return nil + }) + + if err != nil { + return nil, err + } + + return imgPaths, nil +} + +// convert converts the image at filePath to the outputType format. +func convert(filePath, outputType string) error { + f, err := os.Open(filePath) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Printf("Error closing file: %s\n", err) + } + }() + + img, _, err := image.Decode(f) + if err != nil { + return err + } + + outputPath := renameExt(filePath, outputType) + output, err := os.Create(outputPath) + if err != nil { + return err + } + defer func() { + if err := output.Close(); err != nil { + log.Printf("Error closing file: %s\n", err) + } + }() + + switch outputType { + case "jpg", "jpeg": + return convertJPG(img, output) + case "png": + return convertPNG(img, output) + case "gif": + return convertGIF(img, output) + default: + return fmt.Errorf("%s is not a supported output type", outputType) + } +} + +// renameExt renames the file extension of the file at filePath to newExt. +func renameExt(filePath, newExt string) string { + return filePath[:len(filePath)-len(filepath.Ext(filePath))] + "." + newExt +} + +// convertJPG converts the image to the JPEG format. +func convertJPG(img image.Image, output *os.File) error { + if err := jpeg.Encode(output, img, nil); err != nil { + return err + } + return nil +} + +// convertJPG converts the image to the PNG format. +func convertPNG(img image.Image, output *os.File) error { + if err := png.Encode(output, img); err != nil { + return err + } + return nil +} + +// convertJPG converts the image to the GIF format. +func convertGIF(img image.Image, output *os.File) error { + if err := gif.Encode(output, img, nil); err != nil { + return err + } + return nil +} diff --git a/kadai1/hiroya-w/main.go b/kadai1/hiroya-w/main.go new file mode 100644 index 00000000..6239e03e --- /dev/null +++ b/kadai1/hiroya-w/main.go @@ -0,0 +1,66 @@ +package main + +import ( + "flag" + "fmt" + "imgconv/imgconv" + "log" +) + +type Options struct { + inputType string + outputType string + directory string +} + +var opt Options + +func init() { + flag.StringVar(&opt.inputType, "input-type", "jpg", "input type[jpg|jpeg|png|gif]") + flag.StringVar(&opt.outputType, "output-type", "png", "output type[jpg|jpeg|png|gif]") +} + +// validateType validates the type of the image +func validateType(t string) error { + switch t { + case "jpg", "jpeg", "png", "gif": + return nil + default: + return fmt.Errorf("invalid type: %s", t) + } +} + +// validateArgs validates the arguments +func validateArgs() error { + flag.Parse() + + if opt.inputType == opt.outputType { + return fmt.Errorf("input and output type can't be the same") + } + + if err := validateType(opt.inputType); err != nil { + return err + } + + if err := validateType(opt.outputType); err != nil { + return err + } + + if flag.Arg(0) == "" { + return fmt.Errorf("directory is required") + } else { + opt.directory = flag.Arg(0) + } + + return nil +} + +func main() { + if err := validateArgs(); err != nil { + log.Fatalln(err) + } + + if err := imgconv.Converter(opt.directory, opt.inputType, opt.outputType); err != nil { + log.Fatalln(err) + } +} diff --git a/kadai1/hiroya-w/testdata/.gitignore b/kadai1/hiroya-w/testdata/.gitignore new file mode 100644 index 00000000..e90edb82 --- /dev/null +++ b/kadai1/hiroya-w/testdata/.gitignore @@ -0,0 +1,4 @@ +*.png +*.jpg +*.jpeg +*.gif diff --git a/kadai1/hiroya-w/testdata/init.sh b/kadai1/hiroya-w/testdata/init.sh new file mode 100755 index 00000000..d9f06158 --- /dev/null +++ b/kadai1/hiroya-w/testdata/init.sh @@ -0,0 +1,9 @@ +#!/bin/sh +curl https://avatars.githubusercontent.com/hiroya-w -o hiroya-w.png + +for i in {1..10} +do + cp hiroya-w.png image$i.png +done + +rm hiroya-w.png