summaryrefslogtreecommitdiff
path: root/imageprocessor
diff options
context:
space:
mode:
authorhorus_arch2015-04-19 22:09:52 +0200
committerhorus_arch2015-04-19 22:09:52 +0200
commit01e9a34952bd6ddd383680b0ca2312e476ad07a6 (patch)
tree00902575e5c271cc5d35ea65aa8795b8caeb97bc /imageprocessor
downloadmandible-01e9a34952bd6ddd383680b0ca2312e476ad07a6.tar.gz
Initial commit.
Diffstat (limited to 'imageprocessor')
-rw-r--r--imageprocessor/gm/gm.go202
-rw-r--r--imageprocessor/imageorienter.go19
-rw-r--r--imageprocessor/imageprocessor.go79
-rw-r--r--imageprocessor/imagescaler.go92
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")
+}