Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kadai-2-sh-tatsuno #19

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions kadai2/sh-tatsuno/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 概要
* 画像変換ツールgophotoの改良
* io.Readerとio.Writerについて(下記)

# 画像変換ツールgophoto
使い方としては下記のようにdirectoryを-d, -iで入力形式, -oで出力形式を指定する
-nで最大変換数を指定する. 現在の対応は.jpeg, .jpg, .png, .gif
```shell
$ gophoto -d dir -i .png -o .jpeg -n 10
```


# io.Readerとio.Writerの調査
* 標準パッケージでどのように使われているか
* io.Readerとio.Writerがあることでどういう利点があるのか

## 概要
* io.Writer: 出力の抽象化
* io.Reader: 入力の抽象化

```golang
type Writer interface {
Write(p []byte)(n int, err error)
}
```

```golang
type Reader interface {
Read(p []byte) (n int, err error)
}
```

ReadとWriteをそれぞれ実装したものが全てReader, Writerのインターフェースとして扱われる

## 標準パッケージ
### Write/Readが実装されているもの
* syscall
* システムコールに利用する
* file
* ファイルの読み書き
* os.Stdout
* 標準出力
* bytes.Buffer
* バイトへの読み書き
* strings.Builder
* Stringとのやりとり
* net.IPConn
* ネットワークへの読み書き

## io.Writer
io.Writerという入出力に共通する処理を仕様として満たすインターフェース型を定義することでバイト列bを書き込んでバイト数n及びエラーeを出力する処理を通常のファイルに限定せずに汎用的に行うことができる

例えば、
io.MultiWriterのように複数のio.Writerを受け取り書き込み内容を同時に書き込む関数を使うことで、様々な形式の書き込みに対して抽象的に処理できる

## io.Reader
読み込むデータ型がRead関数を実装していれば予め用意した[]byte形式のバッファに読み込むことができる。ただし、バッファ管理をしつつ、毎回格納するバッファのサイズを用意してそこに突っ込むのは面倒なため、ioutil.RealAllのような補助関数を利用することでより利便性高く利用できる

## その他
io.WriteString()のような関数がある。これらはbyteのsliceを使わずに直接stringに書き込むため標準のWriteメソッドではなくこちらを使った方が効率がいい

## 類似するioインターフェース
* io.Closer
* Closeメソッドを持ち、使用したファイルを閉じる
* io.Seeker
* 読み書きの位置を移動する
* io.ReadAt
* 対象オブジェクトがランダムアクセスできるときに特定の位置に自由にアクセスできる

## 参考
Goならわかるシステムプログラミング, 渋川よしき, LambdaNote, 2017
[How to use the io.Writer interface · YourBasic Go](https://yourbasic.org/golang/io-writer-interface-explained/)

## テストカバレッジ
- conv: 71.4%
- dir: 91.7%
91 changes: 91 additions & 0 deletions kadai2/sh-tatsuno/gophoto/conv/image_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package conv

import (
"image"
"image/gif"
"image/jpeg"
"image/png"
"os"
"path/filepath"

"golang.org/x/xerrors"
)

// ImageData image struct
type ImageData struct {
Path string
Data image.Image
}

// NewImageData generate ImageData struct
func NewImageData(path string) (*ImageData, error) {
file, err := os.Open(path)
defer file.Close()
if err != nil {
return nil, err
}
image, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return &ImageData{
Path: path,
Data: image,
}, nil
}

// Convert convert image
func (i *ImageData) Convert(ext string) error {

// check path
if filepath.Ext(i.Path) == ext {
return nil
}

// save file
newPath := i.Path[:len(i.Path)-len(filepath.Ext(i.Path))] + ext

// if file exist, do nothing
if _, err := os.Stat(newPath); !os.IsNotExist(err) {
return nil
}

i.Path = newPath
if err := func(ext string) error {
for _, suffix := range []string{".jpeg", ".jpg", ".gif", ".png"} {
if ext == suffix {
return nil
}
}
return xerrors.New("invalid extension")
}(ext); err != nil {
return err
}

// if file exist, do nothing
if _, err := os.Stat(i.Path); !os.IsNotExist(err) {
return nil
}

dst, err := os.Create(i.Path)
if err != nil {
return err
}
defer dst.Close()

switch ext {
case ".jpeg", ".jpg":
err = jpeg.Encode(dst, i.Data, nil)
case ".gif":
err = gif.Encode(dst, i.Data, nil)
case ".png":
err = png.Encode(dst, i.Data)
default:
err = xerrors.New("error in main method")
}
if err != nil {
return err
}
return nil

}
87 changes: 87 additions & 0 deletions kadai2/sh-tatsuno/gophoto/conv/image_data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package conv

import (
"image/jpeg"
"image/png"
"os"
"path/filepath"
"testing"
)

func Test_ImageData(t *testing.T) {
t.Helper()
t.Run("OK: .png -> .jpeg", func(t *testing.T) {

// ### Given ###
img, err := NewImageData("./testdata/lena-png.png")
if err != nil {
t.Fatalf("Cannot load file. err: %v", err)
}

// ### When ###
path := "./testdata/lena-png.jpeg"
if err = img.Convert(filepath.Ext(path)); err != nil {
t.Fatalf("Cannot save file. err: %v", err)
}

// ### Then ###
file, err := os.Open(path)
if err != nil {
t.Fatalf("Cannot open saved file. err: %v", err)
}
if _, err = jpeg.Decode(file); err != nil {
t.Fatalf("Cannot decode saved file. err: %v", err)
}
if err := os.Remove(path); err != nil {
t.Fatalf("Cannot remove saved file. err: %v", err)
}

})

t.Run("OK: .jpeg -> .png", func(t *testing.T) {

// ### Given ###
img, err := NewImageData("./testdata/lena-jpeg.jpeg")
if err != nil {
t.Fatalf("Cannot load file. err: %v", err)
}

// ### When ###
path := "./testdata/lena-jpeg.png"
if err = img.Convert(filepath.Ext(path)); err != nil {
t.Fatalf("Cannot save file. err: %v", err)
}

// ### Then ###
file, err := os.Open(path)
if err != nil {
t.Fatalf("Cannot open saved file. err: %v", err)
}
if _, err = png.Decode(file); err != nil {
t.Fatalf("Cannot decode saved file. err: %v", err)
}
if err := os.Remove(path); err != nil {
t.Fatalf("Cannot remove saved file. err: %v", err)
}

})

t.Run("NG: cannot openfile", func(t *testing.T) {

// ### Given ###
_, err := NewImageData("./testdata/noexist.png")
if err == nil {
t.Fatal("Load no exist file.")
}
})

t.Run("NG: invalid extension", func(t *testing.T) {

// ### Given ###
_, err := NewImageData("./img.go")
if err == nil {
t.Fatal("Load invalid file.")
}
})

}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions kadai2/sh-tatsuno/gophoto/dir/lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dir

import (
"io/ioutil"
"path/filepath"
)

// Lookup lookup directory to find image files
func Lookup(dir string, ext string, pathList []string) ([]string, error) {

fileInfos, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}

for _, fileInfo := range fileInfos {

// get file info
path := filepath.Join(dir, fileInfo.Name())

// check if the path is about directory
if fileInfo.IsDir() {
pathList, err = Lookup(path, ext, pathList)
if err != nil {
return nil, err
}
}

// check if the suffix is equal to the input format
if filepath.Ext(path) == ext {
pathList = append(pathList, path)
}
}

return pathList, nil
}
52 changes: 52 additions & 0 deletions kadai2/sh-tatsuno/gophoto/dir/lookup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package dir

import (
"reflect"
"testing"
)

func Test_Lookup(t *testing.T) {
t.Helper()

lookupOKTests := []struct {
testCase string
ext string
expected []string
}{
{"OK: .jpg", ".jpg", []string{"testdata/test1.jpg", "testdata/test3/test4.jpg", "testdata/test3/test5.jpg"}},
{"OK: .png", ".png", []string{"testdata/test2.png", "testdata/test3/test6.png"}},
{"OK: .gif", ".gif", []string{}},
}
for _, test := range lookupOKTests {

t.Run(test.testCase, func(t *testing.T) {
actual := []string{}
// ### Given ###
actual, err := Lookup("./testdata", test.ext, actual)
if err != nil {
t.Fatalf("Failed test. err: %v", err)
}

// ### Then ###
if !reflect.DeepEqual(actual, test.expected) {
t.Fatalf("Failed test. expected: %v,\n but actual: %v", test.expected, actual)
}
})
}

t.Run("NG: not found directory", func(t *testing.T) {
actual := []string{}
// ### Given ###
_, err := Lookup("./testdata2", ".jpg", actual)
if err == nil {
t.Fatalf("should be error.")
}

expected := "open ./testdata2: no such file or directory"

// ### Then ###
if err.Error() != expected {
t.Fatalf("Failed test. expected: %s,\n but actual: %s", expected, err.Error())
}
})
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading