From 1e407e21c034d5bfb15e246bf642bfa0362b0e57 Mon Sep 17 00:00:00 2001 From: Sander Schobers Date: Sun, 10 May 2020 09:11:08 +0200 Subject: [PATCH] Added tool to crop & resize images. --- cmd/imadapt/args.go | 72 +++++++++++++++++++++++++++ cmd/imadapt/crop.go | 25 ++++++++++ cmd/imadapt/gray.go | 29 +++++++++++ cmd/imadapt/imadapt.go | 106 +++++++++++++++------------------------- cmd/imadapt/io.go | 29 +++++++++++ cmd/imadapt/setalpha.go | 25 ++++++++++ 6 files changed, 220 insertions(+), 66 deletions(-) create mode 100644 cmd/imadapt/args.go create mode 100644 cmd/imadapt/crop.go create mode 100644 cmd/imadapt/gray.go create mode 100644 cmd/imadapt/io.go create mode 100644 cmd/imadapt/setalpha.go diff --git a/cmd/imadapt/args.go b/cmd/imadapt/args.go new file mode 100644 index 0000000..f9a5c40 --- /dev/null +++ b/cmd/imadapt/args.go @@ -0,0 +1,72 @@ +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" +) + +func atois(s ...string) ([]int, error) { + ints := make([]int, len(s)) + for i, s := range s { + value, err := strconv.Atoi(s) + if err != nil { + return nil, fmt.Errorf("couldn't convert '%s' to a number; error: %v", s, err) + } + ints[i] = value + } + return ints, nil +} + +func scanDir(dir string) ([]string, error) { + var files []string + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if path == dir || info.IsDir() { + return nil + } + files = append(files, path) + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} + +type point struct { + x, y int +} + +func parsePoint(s string) (point, error) { + components := strings.Split(s, ",") + if len(components) != 2 { + return point{}, errors.New("expected two components separated by a comma") + } + ints, err := atois(components...) + if err != nil { + return point{}, err + } + return point{x: ints[0], y: ints[1]}, nil +} + +type rect struct { + min, max point +} + +func parseRect(s string) (rect, error) { + components := strings.Split(s, ",") + if len(components) != 4 { + return rect{}, errors.New("expected four components separated by a comma") + } + ints, err := atois(components...) + if err != nil { + return rect{}, err + } + return rect{min: point{x: ints[0], y: ints[1]}, max: point{x: ints[2], y: ints[3]}}, nil +} diff --git a/cmd/imadapt/crop.go b/cmd/imadapt/crop.go new file mode 100644 index 0000000..56f6d64 --- /dev/null +++ b/cmd/imadapt/crop.go @@ -0,0 +1,25 @@ +package main + +import ( + "image" + "image/draw" + + "github.com/nfnt/resize" +) + +func crop(path string, crop rect, size *point) error { + src, err := decodeImage(path) + if err != nil { + return err + } + srcRect := image.Rect(crop.min.x, crop.min.y, crop.max.x, crop.max.y) + dstRect := image.Rect(0, 0, crop.max.x-crop.min.x, crop.max.y-crop.min.y) + dst := image.NewRGBA(dstRect) + draw.Draw(dst, dstRect, src, srcRect.Min, draw.Src) + if size == nil { + return encodePNG(path, dst) + } + + resized := resize.Resize(uint(size.x), uint(size.y), dst, resize.Bilinear) + return encodePNG(path, resized) +} diff --git a/cmd/imadapt/gray.go b/cmd/imadapt/gray.go new file mode 100644 index 0000000..d58700e --- /dev/null +++ b/cmd/imadapt/gray.go @@ -0,0 +1,29 @@ +package main + +import ( + "image" + "image/color" +) + +func convertToGray(path string) error { + src, err := decodeImage(path) + if err != nil { + return err + } + bounds := src.Bounds() + dst := image.NewRGBA(bounds) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + c := src.At(x, y) + rgba := color.NRGBAModel.Convert(c).(color.NRGBA) + if rgba.A > 0 { + gray := color.GrayModel.Convert(c).(color.Gray) + rgba.R, rgba.G, rgba.B = gray.Y, gray.Y, gray.Y + dst.Set(x, y, rgba) + } else { + dst.Set(x, y, c) + } + } + } + return encodePNG(path, dst) +} diff --git a/cmd/imadapt/imadapt.go b/cmd/imadapt/imadapt.go index c04bbcd..b8c89ae 100644 --- a/cmd/imadapt/imadapt.go +++ b/cmd/imadapt/imadapt.go @@ -4,11 +4,7 @@ import ( "errors" "flag" "fmt" - "image" - "image/color" - "image/png" "log" - "os" ) func run() error { @@ -33,76 +29,54 @@ func run() error { flags := flag.NewFlagSet("gray", flag.ContinueOnError) flags.Parse(args[1:]) for _, path := range flags.Args() { - err := gray(path) + err := convertToGray(path) if err != nil { return fmt.Errorf("couldn't convert to grayscale of '%s'; error: %v", path, err) } } + case "crop": + flags := flag.NewFlagSet("crop", flag.ContinueOnError) + var rect string + var resize string + var dir string + flags.StringVar(&rect, "crop", "", "sets the crop rectangle \"x1,y1,y1,y2\"") + flags.StringVar(&resize, "resize", "", "sets the target size, \"width,height\", if not specified the crop rectangle size is used") + flags.StringVar(&dir, "dir", "", "crops all images in the specified directory (recursively)") + flags.Parse(args[1:]) + + cropRect, err := parseRect(rect) + if err != nil { + return fmt.Errorf("crop rectangle is invalid: error: %v", err) + } + var size *point + if len(resize) != 0 { + p, err := parsePoint(resize) + if err != nil { + return fmt.Errorf("resize parameter is invalid: error: %v", err) + } + size = &p + } + + var files []string + if len(dir) == 0 { + files = flags.Args() + } else { + files, err = scanDir(dir) + if err != nil { + return fmt.Errorf("couldn't find files to crop; error: %v", err) + } + } + + for _, path := range files { + err := crop(path, cropRect, size) + if err != nil { + return fmt.Errorf("couldn't crop '%s'; error: %v", path, err) + } + } } return nil } -func setAlpha(path string, alpha int) error { - src, err := os.Open(path) - if err != nil { - return err - } - defer src.Close() - im, _, err := image.Decode(src) - if err != nil { - return err - } - bounds := im.Bounds() - dst := image.NewNRGBA(bounds) - for y := bounds.Min.Y; y < bounds.Max.Y; y++ { - for x := bounds.Min.X; x < bounds.Max.X; x++ { - c := color.NRGBAModel.Convert(im.At(x, y)).(color.NRGBA) - if c.A > 0 { - c.A = uint8(alpha) - } - dst.Set(x, y, c) - } - } - return encodePNG(path, dst) -} - -func gray(path string) error { - src, err := os.Open(path) - if err != nil { - return err - } - defer src.Close() - im, _, err := image.Decode(src) - if err != nil { - return err - } - bounds := im.Bounds() - dst := image.NewRGBA(bounds) - for y := bounds.Min.Y; y < bounds.Max.Y; y++ { - for x := bounds.Min.X; x < bounds.Max.X; x++ { - c := im.At(x, y) - rgba := color.NRGBAModel.Convert(c).(color.NRGBA) - if rgba.A > 0 { - gray := color.GrayModel.Convert(c).(color.Gray) - rgba.R, rgba.G, rgba.B = gray.Y, gray.Y, gray.Y - dst.Set(x, y, rgba) - } else { - dst.Set(x, y, c) - } - } - } - return encodePNG(path, dst) -} - -func encodePNG(path string, im image.Image) error { - dst, err := os.Create(path) - if err != nil { - return err - } - defer dst.Close() - return png.Encode(dst, im) -} - func main() { if err := run(); err != nil { log.Fatal(err) diff --git a/cmd/imadapt/io.go b/cmd/imadapt/io.go new file mode 100644 index 0000000..54ec97a --- /dev/null +++ b/cmd/imadapt/io.go @@ -0,0 +1,29 @@ +package main + +import ( + "image" + "image/png" + "os" +) + +func encodePNG(path string, im image.Image) error { + dst, err := os.Create(path) + if err != nil { + return err + } + defer dst.Close() + return png.Encode(dst, im) +} + +func decodeImage(path string) (image.Image, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + im, _, err := image.Decode(f) + if err != nil { + return nil, err + } + return im, nil +} diff --git a/cmd/imadapt/setalpha.go b/cmd/imadapt/setalpha.go new file mode 100644 index 0000000..a0c5e93 --- /dev/null +++ b/cmd/imadapt/setalpha.go @@ -0,0 +1,25 @@ +package main + +import ( + "image" + "image/color" +) + +func setAlpha(path string, alpha int) error { + src, err := decodeImage(path) + if err != nil { + return err + } + bounds := src.Bounds() + dst := image.NewNRGBA(bounds) + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + c := color.NRGBAModel.Convert(src.At(x, y)).(color.NRGBA) + if c.A > 0 { + c.A = uint8(alpha) + } + dst.Set(x, y, c) + } + } + return encodePNG(path, dst) +}