Skip to content

Instantly share code, notes, and snippets.

@devi
Created April 7, 2013 17:19
Show Gist options
  • Save devi/5331398 to your computer and use it in GitHub Desktop.
Save devi/5331398 to your computer and use it in GitHub Desktop.
My first learing by doing in Go
// A small utility to resize images inside folder (including subfolder).
package main
import (
"fmt"
"flag"
"math"
"os"
"io/ioutil"
"os/exec"
"bitbucket.org/kardianos/osext"
"log"
"strconv"
"path/filepath"
"strings"
"runtime"
"camlistore.org/pkg/images"
"image/jpeg"
"image/png"
_ "code.google.com/p/vp8-go/webp"
_ "code.google.com/p/go.image/bmp"
_ "code.google.com/p/go.image/tiff"
)
const RESIZER_VERSION = "0.0.1"
var (
itsme string
flagVersion = flag.Bool("v", false, "Display software version")
flagMaxWidth = flag.Float64("mw", 1024, "Maximum width (in pixel)")
flagMaxHeight = flag.Float64("mh", 768, "Maximum height (in pixel)")
flagOutputFormat = flag.String("f", "jpeg", "Output image format (currently supported jpeg and png only)")
flagSrcPath = flag.String("s", "", "Source path to be processed")
flagDstPath = flag.String("d", "", "Destination path to save the results")
flagUseImagemagick = flag.Bool("i", false, "Use Imagemagick if aplicable, fallback to built in function.\nDefault command: convert -resize WidthxHeight /path/to/ImageFilename.ext -auto-orient -filter Lanczos -sampling-factor 1x1 -unsharp 1.5x1+0.7+0.02 -quality 90 -set filename:mysize %wx%h \"/path/to/output/ImageFilename_%[filename:mysize].jpeg\"")
flagCustomIM = flag.String("im", "", "Replace default parameter if using ImageMagick.\nReplaced params:-filter Lanczos -sampling-factor 1x1 -unsharp 1.5x1+0.7+0.02 -quality 90")
flagRecursive = flag.Bool("r", true, "Resize image recursively")
)
func init() {
itsme, _ = osext.Executable()
}
func main() {
flag.Parse()
if *flagVersion {
fmt.Printf("Resizer v%v\nDesigned to resize images inside a folder (including subfolder).\nAny question mailto: devi[dot]mandiri[at]gmail[dot]com\nUsage parameter:\n", RESIZER_VERSION)
flag.PrintDefaults()
return
}
if *flagMaxWidth <= 0 || *flagMaxHeight <= 0 {
log.Fatal("Maximum Width and Maximum Height must be specified\n")
}
if *flagSrcPath == "" {
var err error
*flagSrcPath, err = osext.ExecutableFolder()
if err != nil {
log.Fatal("Source path must be specified\n")
}
}
// if *flagDstPath == "" {
// *flagDstPath = *flagSrcPath
// }
// make sure the path is clean
*flagSrcPath = filepath.Clean(*flagSrcPath)
//*flagDstPath = filepath.Clean(*flagDstPath)
// test path
if !TestPath(*flagSrcPath) {
log.Fatalf("Folder does not exists: %v", *flagSrcPath)
}
if *flagDstPath != "" {
if !TestPath(*flagDstPath) {
log.Fatalf("Folder does not exists: %v", *flagDstPath)
}
}
*flagOutputFormat = strings.ToLower(*flagOutputFormat)
useBuiltIn := true
if *flagUseImagemagick {
// check ImageMagick
if runtime.GOOS == "windows" {
// just to make sure it's not a shit software
out, err := exec.Command("convert", "-version").Output()
if err == nil {
if strings.Contains(string(out), "http://www.imagemagick.org") {
useBuiltIn = false
}
}
} else {
_, err := exec.LookPath("convert")
if err == nil {
useBuiltIn = false
}
}
} else {
if *flagOutputFormat != "jpeg" && *flagOutputFormat != "png" {
log.Fatal("Output format only supported JPEG and PNG\n")
}
}
allowedExt := []string{".jpg", ".jpeg", ".bmp", ".tiff", ".png", ".webp"}
// collect images before execution
pics := []string{}
Walk(*flagSrcPath, func(imgPath string, finfo os.FileInfo, err error) error {
if imgPath != itsme && !finfo.IsDir() {
ext := strings.ToLower(filepath.Ext(finfo.Name()))
if !matchInArray(allowedExt, ext) {
return nil
}
pics = AppendIfMissing(pics, imgPath)
}
return nil
}, *flagRecursive)
for _, pic := range pics {
fmt.Printf("Resizing %v\n", pic)
fName := filepath.Base(pic)
fDest := *flagDstPath;
if fDest == "" || !TestPath(fDest) {
fDest = strings.TrimRight(pic, fName)
}
if useBuiltIn {
err := ResizeImage(pic, fName, fDest)
if err !=nil {
fmt.Printf("Error: %v\n", err)
}
} else {
err := UseImagemagick(pic, fName, fDest)
if err != nil {
fmt.Printf("Error: %v\n", err)
}
}
}
}
// adjusted from filepath.Walk
func Walk(root string, walkFn filepath.WalkFunc, recursive bool) error {
info, err := os.Lstat(root)
if (err != nil) {
return walkFn(root, nil, err)
}
return walk(root, info, walkFn, recursive)
}
func walk(path string, info os.FileInfo, walkFn filepath.WalkFunc, recursive bool) error {
err := walkFn(path, info, nil)
if err != nil {
if info.IsDir() {
return nil
}
return err
}
if !info.IsDir() {
return nil
}
list, err := ioutil.ReadDir(path)
if err != nil {
return walkFn(path, info, err)
}
for _, fileInfo := range list {
if recursive {
err = walk(filepath.Join(path, fileInfo.Name()), fileInfo, walkFn, recursive)
if err != nil {
if !fileInfo.IsDir() {
return err
}
}
} else {
walkFn(filepath.Join(path, fileInfo.Name()), fileInfo, nil)
}
}
return nil
}
// http://stackoverflow.com/a/9561388
func AppendIfMissing(slice []string, i string) []string {
for _, ele := range slice {
if ele == i {
return slice
}
}
return append(slice, i)
}
// borrowed from github.com/gorilla/mux
// matchInArray returns true if the given string value is in the array.
func matchInArray(arr []string, value string) bool {
for _, v := range arr {
if v == value {
return true
}
}
return false
}
func TestPath(s string) bool {
fi,err := os.Stat(s)
switch {
case os.IsNotExist(err):
return false
case err != nil:
return false
case !fi.IsDir():
return false
}
return true
}
// simple round to closest integer
// https://groups.google.com/d/msg/golang-nuts/ITZV08gAugI/N01TlFI_bU8J
func Round(value float64) int {
if value < 0.0 {
value -= 0.5
} else {
value += 0.5
}
return int(value)
}
func ResizeImage(imgPath, imgName, dstPath string) error {
imgFile, err := os.Open(imgPath)
if err != nil {
return err
}
defer imgFile.Close()
// pastikan nilai maxWidth dan maxHeight bukan <= 0
mw := Round(math.Max(*flagMaxWidth, 1))
mh := Round(math.Max(*flagMaxHeight, 1))
dst, _, err := images.Decode(imgFile, &images.DecodeOpts{MaxWidth: mw, MaxHeight: mh})
if err != nil {
return err
}
b := dst.Bounds()
newExt := "_" + strconv.Itoa(b.Dx()) + "x" + strconv.Itoa(b.Dy()) + "." + *flagOutputFormat
newName := strings.Replace(imgName, filepath.Ext(imgName), newExt, -1)
newImg, err := os.Create(filepath.Join(dstPath, newName))
if err != nil {
return err
}
defer newImg.Close()
switch {
case *flagOutputFormat == "jpeg":
if err := jpeg.Encode(newImg, dst, nil); err != nil {
fmt.Println(err)
}
case *flagOutputFormat == "png":
if err := png.Encode(newImg, dst); err != nil {
fmt.Println(err)
}
}
return nil
}
func UseImagemagick(imgPath, imgName, dstPath string) error {
var pixel string
mw := strconv.FormatFloat(*flagMaxWidth, 'f', 2, 64)
mh := strconv.FormatFloat(*flagMaxHeight, 'f', 2, 64)
switch {
case *flagMaxWidth == 0:
pixel = "x" + mh //strconv.Itoa(int(*flagMaxHeight))
case *flagMaxHeight == 0:
pixel = mw //strconv.Itoa(int(*flagMaxWidth))
default:
//pixel = strconv.Itoa(*flagMaxWidth) + "x" + strconv.Itoa(*flagMaxHeight)
pixel = mw + "x" + mh
}
newExt := "_%[filename:mysize]." + *flagOutputFormat
newName := strings.Replace(imgName, filepath.Ext(imgName), newExt, -1)
convertParams := "-filter Lanczos -sampling-factor 1x1 -unsharp 1.5x1+0.7+0.02 -quality 90"
if *flagCustomIM != "" {
convertParams = *flagCustomIM
}
// convert -resize 1024x768 -filter Lanczos -sampling-factor 1x1 -unsharp 1.5x1+0.7+0.02 -quality 90 imgPath -set filename:mysize "%wx%h" filepath.Join(filepath.Dir(imgPath), newName)
cArgs := []string{"-resize", pixel, imgPath, "-set", "filename:mysize", "%wx%h", "-auto-orient"}//, filepath.Join(dstPath, newName)}
cArgs = append(cArgs, strings.Fields(convertParams)...)
cArgs = append(cArgs, filepath.Join(dstPath, newName))
//fmt.Println(cArgs)
err := exec.Command("convert", cArgs...).Run()
if err != nil {
return err
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment