diff options
| author | horus_arch | 2015-04-19 22:09:52 +0200 |
|---|---|---|
| committer | horus_arch | 2015-04-19 22:09:52 +0200 |
| commit | 01e9a34952bd6ddd383680b0ca2312e476ad07a6 (patch) | |
| tree | 00902575e5c271cc5d35ea65aa8795b8caeb97bc /imageprocessor | |
| download | mandible-01e9a34952bd6ddd383680b0ca2312e476ad07a6.tar.gz | |
Initial commit.
Diffstat (limited to 'imageprocessor')
| -rw-r--r-- | imageprocessor/gm/gm.go | 202 | ||||
| -rw-r--r-- | imageprocessor/imageorienter.go | 19 | ||||
| -rw-r--r-- | imageprocessor/imageprocessor.go | 79 | ||||
| -rw-r--r-- | imageprocessor/imagescaler.go | 92 |
4 files changed, 392 insertions, 0 deletions
diff --git a/imageprocessor/gm/gm.go b/imageprocessor/gm/gm.go new file mode 100644 index 0000000..e9e76d9 --- /dev/null +++ b/imageprocessor/gm/gm.go @@ -0,0 +1,202 @@ +package gm + +import ( + "bytes" + "errors" + "fmt" + "log" + "os/exec" + "time" +) + +const GM_COMMAND = "convert" + +func ConvertToJpeg(filename string) (string, error) { + outfile := fmt.Sprintf("%s_jpg", filename) + + args := []string{ + filename, + "-flatten", + "JPEG:" + outfile, + } + + err := runConvertCommand(args) + if err != nil { + return "", err + } + + return outfile, nil +} + +func FixOrientation(filename string) (string, error) { + outfile := fmt.Sprintf("%s_ort", filename) + + args := []string{ + filename, + "-auto-orient", + outfile, + } + + err := runConvertCommand(args) + if err != nil { + return "", err + } + + return outfile, nil +} + +func Quality(filename string, quality int) (string, error) { + outfile := fmt.Sprintf("%s_q", filename) + + args := []string{ + filename, + "-quality", + fmt.Sprintf("%d", quality), + "-density", + "72x72", + outfile, + } + + err := runConvertCommand(args) + if err != nil { + return "", err + } + + return outfile, nil +} + +func ResizePercent(filename string, percent int) (string, error) { + outfile := fmt.Sprintf("%s_rp", filename) + + args := []string{ + filename, + "-resize", + fmt.Sprintf("%d%%", percent), + outfile, + } + + err := runConvertCommand(args) + if err != nil { + return "", err + } + + return outfile, nil +} + +func SquareThumb(filename, name string, size int) (string, error) { + outfile := fmt.Sprintf("%s_%s", filename, name) + + args := []string{ + fmt.Sprintf("%s[0]", filename), + "-quality", + "94", + "-resize", + fmt.Sprintf("%dx%d^", size, size), + "-gravity", + "center", + "-crop", + fmt.Sprintf("%dx%d+0+0", size, size), + "-density", + "72x72", + "-unsharp", + "0.5", + fmt.Sprintf("JPG:%s", outfile), + } + + err := runConvertCommand(args) + if err != nil { + return "", err + } + + return outfile, nil +} + +func Thumb(filename, name string, width, height int) (string, error) { + outfile := fmt.Sprintf("%s_%s", filename, name) + + args := []string{ + fmt.Sprintf("%s[0]", filename), + "-quality", + "83", + "-resize", + fmt.Sprintf("%dx%d>", width, height), + "-density", + "72x72", + fmt.Sprintf("JPG:%s", outfile), + } + + err := runConvertCommand(args) + if err != nil { + return "", err + } + + return outfile, nil +} + +func CircleThumb(filename, name string, width int) (string, error) { + outfile := fmt.Sprintf("%s_%s", filename, name) + + filename, err := Thumb(filename, name, width*2, width*2) + if err != nil { + return "", err + } + + args := []string{ + "-size", + fmt.Sprintf("%dx%d", width, width), + "xc:none", + "-fill", + filename, + "-quality", + "83", + "-density", + "72x72", + "-draw", + fmt.Sprintf("circle %d,%d %d,1", width/2, width/2, width/2), + fmt.Sprintf("PNG:%s", outfile), + } + + err = runConvertCommand(args) + if err != nil { + return "", err + } + + return outfile, nil +} + +func runConvertCommand(args []string) error { + cmd := exec.Command(GM_COMMAND, args...) + + var out bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &stderr + + cmd.Start() + + cmdDone := make(chan error, 1) + go func() { + cmdDone <- cmd.Wait() + }() + + select { + case <-time.After(time.Duration(500000) * time.Millisecond): + killCmd(cmd) + <-cmdDone + return errors.New("Command timed out") + case err := <-cmdDone: + if err != nil { + log.Println(stderr.String()) + } + + return err + } + + return nil +} + +func killCmd(cmd *exec.Cmd) { + if err := cmd.Process.Kill(); err != nil { + log.Printf("Failed to kill command: %v", err) + } +} diff --git a/imageprocessor/imageorienter.go b/imageprocessor/imageorienter.go new file mode 100644 index 0000000..bca901d --- /dev/null +++ b/imageprocessor/imageorienter.go @@ -0,0 +1,19 @@ +package imageprocessor + +import ( + "mandible/imageprocessor/gm" + "mandible/uploadedfile" +) + +type ImageOrienter struct{} + +func (this *ImageOrienter) Process(image *uploadedfile.UploadedFile) error { + filename, err := gm.FixOrientation(image.GetPath()) + if err != nil { + return err + } + + image.SetPath(filename) + + return nil +} diff --git a/imageprocessor/imageprocessor.go b/imageprocessor/imageprocessor.go new file mode 100644 index 0000000..905b522 --- /dev/null +++ b/imageprocessor/imageprocessor.go @@ -0,0 +1,79 @@ +package imageprocessor + +import ( + "mandible/uploadedfile" +) + +type multiProcessType []ProcessType + +func (this multiProcessType) Process(image *uploadedfile.UploadedFile) error { + for _, processor := range this { + err := processor.Process(image) + if err != nil { + return err + } + } + + return nil +} + +type asyncProcessType []ProcessType + +func (this asyncProcessType) Process(image *uploadedfile.UploadedFile) error { + errs := make(chan error, len(this)) + + for _, processor := range this { + go func(p ProcessType) { + errs <- p.Process(image) + }(processor) + } + + for i := 0; i < len(this); i++ { + select { + case err := <-errs: + if err != nil { + return err + } + } + } + + return nil +} + +type ProcessType interface { + Process(image *uploadedfile.UploadedFile) error +} + +type ImageProcessor struct { + processor ProcessType +} + +func (this *ImageProcessor) Run(image *uploadedfile.UploadedFile) error { + return this.processor.Process(image) +} + +func Factory(maxFileSize int64, file *uploadedfile.UploadedFile) (*ImageProcessor, error) { + size, err := file.FileSize() + if err != nil { + return &ImageProcessor{}, err + } + + processor := multiProcessType{} + processor = append(processor, &ImageOrienter{}) + + if size > maxFileSize { + processor = append(processor, &ImageScaler{maxFileSize}) + } + + async := asyncProcessType{} + + for _, t := range file.GetThumbs() { + async = append(async, t) + } + + if len(async) > 0 { + processor = append(processor, async) + } + + return &ImageProcessor{processor}, nil +} diff --git a/imageprocessor/imagescaler.go b/imageprocessor/imagescaler.go new file mode 100644 index 0000000..2ae5328 --- /dev/null +++ b/imageprocessor/imagescaler.go @@ -0,0 +1,92 @@ +package imageprocessor + +import ( + "errors" + + "mandible/imageprocessor/gm" + "mandible/uploadedfile" +) + +type ImageScaler struct { + targetSize int64 +} + +func (this *ImageScaler) Process(image *uploadedfile.UploadedFile) error { + switch image.GetMime() { + case "image/jpeg", "image/jpg": + return this.scaleJpeg(image) + case "image/png": + return this.scalePng(image) + case "image/gif": + return this.scaleGif(image) + } + + return errors.New("Unsuported filetype") +} + +func (this *ImageScaler) scalePng(image *uploadedfile.UploadedFile) error { + filename, err := gm.ConvertToJpeg(image.GetPath()) + if err != nil { + return err + } + + image.SetPath(filename) + image.SetMime("image/jpeg") + return this.scaleJpeg(image) +} + +func (this *ImageScaler) scaleJpeg(image *uploadedfile.UploadedFile) error { + filename, err := gm.Quality(image.GetPath(), 90) + if err != nil { + return err + } + + image.SetPath(filename) + size, err := image.FileSize() + if size < this.targetSize { + return nil + } + + filename, err = gm.Quality(image.GetPath(), 70) + if err != nil { + return err + } + + image.SetPath(filename) + size, err = image.FileSize() + if size < this.targetSize { + return nil + } + + percent := 90 + if (size - this.targetSize) >= (15 * 1024 * 1024) { + percent = 30 + } else if (size - this.targetSize) >= (10 * 1024 * 1024) { + percent = 40 + } else if (size - this.targetSize) >= (5 * 1024 * 1024) { + percent = 60 + } + + for { + filename, err = gm.ResizePercent(image.GetPath(), percent) + if err != nil { + return err + } + + image.SetPath(filename) + size, err := image.FileSize() + if err != nil { + return err + } else if size == 0 || percent < 10 { + return errors.New("Could not scale image to desired filesize") + } else if size < this.targetSize { + return nil + } + + percent -= 10 + } +} + +func (this *ImageScaler) scaleGif(image *uploadedfile.UploadedFile) error { + return errors.New("Unimplimented") +} |
