diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..f80a7fd6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}", + "args": ["testJpg"] + } + ] +} \ No newline at end of file diff --git a/kadai1/tetuyosi/.gitignore b/kadai1/tetuyosi/.gitignore new file mode 100644 index 00000000..93f151f3 --- /dev/null +++ b/kadai1/tetuyosi/.gitignore @@ -0,0 +1,2 @@ +bin +main \ No newline at end of file diff --git a/kadai1/tetuyosi/Makefile b/kadai1/tetuyosi/Makefile new file mode 100644 index 00000000..906f3334 --- /dev/null +++ b/kadai1/tetuyosi/Makefile @@ -0,0 +1,2 @@ +build: + go build main.go diff --git a/kadai1/tetuyosi/README.md b/kadai1/tetuyosi/README.md new file mode 100644 index 00000000..a8690e21 --- /dev/null +++ b/kadai1/tetuyosi/README.md @@ -0,0 +1,37 @@ +### 画像変換コマンドを作ろう + +## 次の仕様を満たすコマンドを作って下さい + +* ディレクトリを指定する +* 指定したディレクトリ以下のJPGファイルをPNGに変換(デフォルト) +* ディレクトリ以下は再帰的に処理する +* 変換前と変換後の画像形式を指定できる(オプション) + +## 以下を満たすように開発してください + +* mainパッケージと分離する +* 自作パッケージと標準パッケージと準標準パッケージのみ使う + - 準標準パッケージ:golang.org/x以下のパッケージ +* ユーザ定義型を作ってみる +* GoDocを生成してみる +* Go Modulesを使ってみる + +## 利用方法 + +1. build +``` +make build +``` + +2. 実行 + +- jpg -> png +``` +./main <変換対象画像の入ったフォルダ> +``` + +- png -> jpg +``` +./main <変換対象画像の入ったフォルダ> -srcExt png -destExt jpg +``` + diff --git a/kadai1/tetuyosi/go.mod b/kadai1/tetuyosi/go.mod new file mode 100644 index 00000000..3753ab99 --- /dev/null +++ b/kadai1/tetuyosi/go.mod @@ -0,0 +1,3 @@ +module github.com/tetuyosi/gopherdojo-studyroom/kadai1/tetuyosi + +go 1.16 diff --git a/kadai1/tetuyosi/go.sum b/kadai1/tetuyosi/go.sum new file mode 100644 index 00000000..e69de29b diff --git a/kadai1/tetuyosi/iconv/iconv.go b/kadai1/tetuyosi/iconv/iconv.go new file mode 100644 index 00000000..8e47b0a1 --- /dev/null +++ b/kadai1/tetuyosi/iconv/iconv.go @@ -0,0 +1,101 @@ +// package iconv implements functions to convert images to a desired format. +package iconv + +import ( + "fmt" + "image" + "image/gif" + "image/jpeg" + "image/png" + "os" + "path/filepath" + "strings" +) + +// A Convert image interface +type Conv struct { + FilePath string + SrcExt string + DestExt string + image image.Image +} + +// jpeg or jpg return same extension +var extMap = map[string]string{ + "jpg": "jpg", + "jpeg": "jpg", + "png": "png", + "gif": "gif", +} + +// Return Conv object +func New(path string, srcExt string, destExt string) (*Conv, error) { + fileExt := strings.ToLower(strings.TrimLeft(filepath.Ext(path), ".")) + srcExt = strings.ToLower(srcExt) + destExt = strings.ToLower(destExt) + if !isConvertible(fileExt) { + return nil, fmt.Errorf("変換ファイル拡張子が不正です。") + } + if !isConvertible(srcExt) { + return nil, fmt.Errorf("変換元フォーマット指定の誤りです。") + } + if !isConvertible(destExt) { + return nil, fmt.Errorf("変換先フォーマット指定の誤りです。") + } + return &Conv{FilePath: path, SrcExt: srcExt, DestExt: destExt}, nil +} + +// Check convertible extension +func isConvertible(ext string) bool { + _, isExist := extMap[ext] + return isExist +} + +// Read file and convert internal image +func (c *Conv) Imaging() error { + f, err := os.Open(c.FilePath) + if err != nil { + return fmt.Errorf("指定ファイルが開けません") + } + defer f.Close() + + switch extMap[c.SrcExt] { + case "jpg": + c.image, err = jpeg.Decode(f) + case "png": + c.image, err = png.Decode(f) + case "gif": + c.image, err = gif.Decode(f) + default: + return fmt.Errorf("変換前拡張子が不正です") + } + if err != nil { + return fmt.Errorf("画像読み込みに失敗しました") + } + return nil +} + +// internal image convert to desired format +func (c *Conv) Convert() error { + name := strings.TrimSuffix(filepath.Base(c.FilePath), c.SrcExt) + c.DestExt + + f, err := os.Create(filepath.Dir(c.FilePath) + "/" + name) + if err != nil { + return fmt.Errorf("ファイル作成に失敗しました") + } + defer f.Close() + switch extMap[c.DestExt] { + case "jpg": + err = jpeg.Encode(f, c.image, &jpeg.Options{}) + case "png": + err = png.Encode(f, c.image) + case "gif": + err = gif.Encode(f, c.image, &gif.Options{}) + default: + return fmt.Errorf("変換後拡張子が不正です") + } + if err != nil { + return fmt.Errorf("画像変換に失敗しました") + } + return nil +} diff --git a/kadai1/tetuyosi/iconv/iconv_test.go b/kadai1/tetuyosi/iconv/iconv_test.go new file mode 100644 index 00000000..986b1934 --- /dev/null +++ b/kadai1/tetuyosi/iconv/iconv_test.go @@ -0,0 +1,132 @@ +package iconv_test + +import ( + "image" + "image/color" + "image/gif" + "image/jpeg" + "image/png" + "os" + "reflect" + "testing" + + iconv "github.com/tetuyosi/gopherdojo-studyroom/kadai1/tetuyosi/iconv" +) + +func TestMain(m *testing.M) { + code := m.Run() + os.Exit(code) +} + +// 正常ケース +func Test_New_01(t *testing.T) { + actual, _ := iconv.New("test.jpg", "jpg", "png") + expected := iconv.Conv{ + FilePath: "test.jpg", + SrcExt: "jpg", + DestExt: "png", + } + if !reflect.DeepEqual(actual, &expected) { + t.Errorf("got: %v\nwant: %v", actual, &expected) + } +} + +// エラーケース +func Test_New_02(t *testing.T) { + _, actual := iconv.New("test.txt", "jpg", "png") + expected := "変換ファイル拡張子が不正です。" + if actual.Error() != expected { + t.Errorf("got: %v\nwant: %v", actual.Error(), expected) + } +} + +func Test_New_03(t *testing.T) { + _, actual := iconv.New("test.jpg", "txt", "png") + expected := "変換元フォーマット指定の誤りです。" + if actual.Error() != expected { + t.Errorf("got: %v\nwant: %v", actual.Error(), expected) + } +} + +func Test_New_04(t *testing.T) { + _, actual := iconv.New("test.jpg", "jpg", "txt") + expected := "変換先フォーマット指定の誤りです。" + if actual.Error() != expected { + t.Errorf("got: %v\nwant: %v", actual.Error(), expected) + } +} + +func Test_New_05(t *testing.T) { + actual, _ := iconv.New("test.JPEG", "jpg", "png") + expected := iconv.Conv{ + FilePath: "test.JPEG", + SrcExt: "jpg", + DestExt: "png", + } + if !reflect.DeepEqual(actual, &expected) { + t.Errorf("got: %v\nwant: %v", actual, &expected) + } +} + +// test jpg -> png +func Test_Convert_01(t *testing.T) { + makeImage("jpg") + c, _ := iconv.New("test.jpg", "jpg", "png") + c.Imaging() + c.Convert() + _, actual := os.Stat("test.png") + if actual != nil { + t.Errorf("convert file not created") + } + defer func() { + os.Remove("test.jpg") + os.Remove("test.png") + }() +} + +// test png -> jpg +func Test_Convert_02(t *testing.T) { + makeImage("png") + c, _ := iconv.New("test.png", "png", "jpg") + c.Imaging() + c.Convert() + _, actual := os.Stat("test.jpg") + if actual != nil { + t.Errorf("convert file not created") + } + defer func() { + os.Remove("test.jpg") + os.Remove("test.png") + }() +} + +// test gif -> jpg +func Test_Convert_03(t *testing.T) { + makeImage("gif") + c, _ := iconv.New("test.gif", "gif", "jpg") + c.Imaging() + c.Convert() + _, actual := os.Stat("test.jpg") + if actual != nil { + t.Errorf("convert file not created") + } + defer func() { + os.Remove("test.jpg") + os.Remove("test.gif") + }() +} + +func makeImage(t string) { + img := image.NewRGBA(image.Rect(0, 0, 100, 50)) + img.Set(2, 3, color.RGBA{255, 0, 0, 255}) + f, _ := os.OpenFile("test."+t, os.O_WRONLY|os.O_CREATE, 0600) + defer f.Close() + switch t { + case "jpg": + jpeg.Encode(f, img, &jpeg.Options{}) + case "png": + png.Encode(f, img) + case "gif": + gif.Encode(f, img, &gif.Options{}) + } +} diff --git a/kadai1/tetuyosi/main.go b/kadai1/tetuyosi/main.go new file mode 100644 index 00000000..3b498eda --- /dev/null +++ b/kadai1/tetuyosi/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "flag" + "fmt" + "io/fs" + "os" + "path/filepath" + + iconv "github.com/tetuyosi/gopherdojo-studyroom/kadai1/tetuyosi/iconv" +) + +// 変換対象ディレクトリ名 +var dir string + +// 変換対象拡張子(jpg,jpeg,png,gif) +var srcExt string + +// 変換後拡張子(jpg,jpeg,png,gif) +var destExt string + +func init() { + flag.StringVar(&srcExt, "srcExt", "jpg", "変換前画像フォーマット") + flag.StringVar(&destExt, "destExt", "png", "変換後画像フォーマット") +} + +func main() { + flag.Parse() + + dir = flag.Arg(0) + // 引数のチェック + if dir == "" { + fmt.Printf("変換対象ディレクトリ名を入れてください。") + os.Exit(1) + } + _, err := os.Stat(dir) + // フォルダチェック + if os.IsNotExist(err) { + fmt.Printf("指定ディレクトリは存在しません。%s", dir) + os.Exit(1) + } + + // 画像変換 + err = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + c, err := iconv.New(path, srcExt, destExt) + if err != nil { + return nil + } + err = c.Imaging() + if err != nil { + return err + } + err = c.Convert() + if err != nil { + return err + } + return nil + }) + if err != nil { + fmt.Printf("%s", err) + os.Exit(1) + } + os.Exit(0) +}