Skip to content

Kadai2 lfcd85 #18

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

Open
wants to merge 15 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
1 change: 1 addition & 0 deletions kadai2/lfcd85/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bin/convert
12 changes: 12 additions & 0 deletions kadai2/lfcd85/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
bin/convert: cmd/convert/*.go imgconv/*.go
GO111MODULE=on go build -o bin/convert cmd/convert/main.go

fmt:
go fmt ./...
go vet ./...

check:
GO111MODULE=on go test ./imgconv/... -v

coverage:
GO111MODULE=on go test ./imgconv/... -cover
29 changes: 29 additions & 0 deletions kadai2/lfcd85/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# imgconv

An implementation for the image conversion command, kadai-1 of Gopherdojo #5.

Gopher道場 #5 課題1 `画像変換コマンド` の実装です。

## Installation

```bash
$ make bin/convert
```

## Usage

Specify the target directory as an argument. The given directory is recursively processed. Converted files are outputted under `./output/` directory.

コマンド引数に対象ディレクトリを指定してください。ディレクトリ以下は再帰的に処理されます。変換後のファイルは `./output/` ディレクトリ以下に出力されます。

```bash
$ bin/convert test/
```

Input and output image formats can be set by `-f` (from) and `-t` (to) options. Default formats are from JPEG to PNG. JPEG, PNG, GIF are available.

画像形式は `-f` オプション(変換前)・ `-t` オプション(変換後)で指定できます。デフォルトは JPEG → PNG です。JPEG, PNG, GIF 形式が利用可能です。

```bash
$ bin/convert -f png -t jpeg test/
```
Empty file added kadai2/lfcd85/bin/.gitkeep
Empty file.
22 changes: 22 additions & 0 deletions kadai2/lfcd85/cmd/convert/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Command bin/convert ...
package main

import (
"flag"
"fmt"

"github.com/gopherdojo/dojo5/kadai2/lfcd85/imgconv"
)

func main() {
from := flag.String("f", "jpeg", "Image format before conversion (default: jpeg)")
to := flag.String("t", "png", "Image format after conversion (default: png)")
flag.Parse()
dirName := flag.Arg(0)

err := imgconv.Convert(dirName, *from, *to)
if err != nil {
fmt.Println("error:", err)
return
}
}
3 changes: 3 additions & 0 deletions kadai2/lfcd85/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gopherdojo/dojo5/kadai2/lfcd85

go 1.12
182 changes: 182 additions & 0 deletions kadai2/lfcd85/imgconv/imgconv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Package imgconv provides a recursive conversion of images in the directory.
package imgconv

import (
"errors"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"os"
"path/filepath"
"strings"
)

// MapImgFmtExts is a map of the image formats and its extensions.
type MapImgFmtExts map[ImgFmt]Exts

// Exts is a slice of image extensions.
type Exts []Ext

// Ext is a image extension.
type Ext string

// ImgFmt is a image format.
type ImgFmt string

// Converter is a struct which contains info about image formats and extensions.
type Converter struct {
fmtFrom ImgFmt
fmtTo ImgFmt
imgFmtExts MapImgFmtExts
}

// Convert recursively seeks a given directory and converts images from and to given formats.
func Convert(dir string, from string, to string) error {
if dir == "" {
return errors.New("directory name is not provided")
}

cv := &Converter{}
cv.imgFmtExts.Init()
cv.fmtFrom.Detect(cv, from)
cv.fmtTo.Detect(cv, to)
if cv.fmtFrom == "" || cv.fmtTo == "" {
return errors.New("given image format is not supported")
}
if cv.fmtFrom == cv.fmtTo {
return errors.New("image formats before and after conversion are the same")
}

err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

err = cv.convSingleFile(path, info)
return err
})
return err
}

func (cv *Converter) convSingleFile(path string, info os.FileInfo) error {
if info.IsDir() {
outputPath := addOutputDir(path)
return os.MkdirAll(outputPath, 0777)
}
if !cv.fmtFrom.Match(cv, info.Name()) {
return nil
}

f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()

img, err := cv.decodeImg(f)
if err != nil {
return nil
}

return cv.writeOutputFile(img, path)
}

func (cv *Converter) writeOutputFile(img image.Image, path string) error {
outputPath := cv.generateOutputPath(path)

f, err := os.Create(outputPath)
if err != nil {
return err
}
defer f.Close()

err = cv.encodeImg(f, img)
return err
}

func (cv *Converter) decodeImg(r io.Reader) (image.Image, error) {
img, fmtStr, err := image.Decode(r)
if ImgFmt(fmtStr) != cv.fmtFrom {
err = errors.New("image format does not match")
}
return img, err
}

func (cv *Converter) encodeImg(w io.Writer, img image.Image) error {
switch cv.fmtTo {
case "jpeg":
if err := jpeg.Encode(w, img, nil); err != nil {
return err
}
case "png":
if err := png.Encode(w, img); err != nil {
return err
}
case "gif":
if err := gif.Encode(w, img, nil); err != nil {
return err
}
}
return nil
}

func (cv *Converter) generateOutputPath(path string) string {
dirAndBase := strings.TrimRight(path, filepath.Ext(path))
ext := cv.imgFmtExts.ConvToExt(cv.fmtTo)
path = strings.Join([]string{dirAndBase, string(ext)}, ".")
return addOutputDir(path)
}

func addOutputDir(path string) string {
return strings.Join([]string{
"./output",
strings.TrimLeft(path, "./"),
}, "/")
}

// Detect specifies image format from file extension string.
func (imgFmt *ImgFmt) Detect(cv *Converter, extStr string) {
ext := Ext(strings.ToLower(extStr))
*imgFmt = cv.imgFmtExts.ConvToImgFmt(ext)
}

// Match checks whether the file has an extension of the image format.
func (imgFmt ImgFmt) Match(cv *Converter, fileName string) bool {
fileExtStr := strings.TrimPrefix(filepath.Ext(fileName), ".")
fileExt := Ext(strings.ToLower(fileExtStr))
fileImgFmt := cv.imgFmtExts.ConvToImgFmt(fileExt)
return fileImgFmt == imgFmt
}

// Init creates the map of image formats and its extensions available.
func (m *MapImgFmtExts) Init() {
*m = MapImgFmtExts{
"jpeg": Exts{"jpg", "jpeg"},
"png": Exts{"png"},
"gif": Exts{"gif"},
}
}

// ConvToImgFmt converts image extension to its format.
func (m MapImgFmtExts) ConvToImgFmt(ext Ext) ImgFmt {
for imgFmt, fmtExts := range m {
for _, fmtExt := range fmtExts {
if ext == fmtExt {
return imgFmt
}
}
}
return ""
}

// ConvToExt converts image format to its extension.
func (m MapImgFmtExts) ConvToExt(imgFmt ImgFmt) Ext {
for keyImgFmt, fmtExts := range m {
if imgFmt == keyImgFmt {
return fmtExts[0]
}
}
return ""
}
Loading