How to quickly scale and sharpen an image in Go?

I am currently porting a fairly basic gallery application from PHP to Go. This application provides automatic thumbnail generation and a mid-size version of each image.

In PHP, I used GD because it comes with it and worked very well. (The code is at the end of the question). I thought I could just play this in Go and find go-gd from https://github.com/bolknote/go-gd (again, the code at the end). It works, but it's about 10 times slower (measured using time wget $URL ). The PHP implementation takes about 1 second to create a 1024x768 version from a 10 megapixel image, and the Go-Code takes almost 10 seconds.

Is there a way to speed this up or any other image processing library for Go that implements zooming and folding while being fast enough?

Php code

 public function saveThumb($outName, $options) { $this->img = imagecreatefromjpeg($filename); if (!is_dir(dirname($outName))) { mkdir(dirname($outName), 0777, true); } $width = imagesx($this->img); $height = imagesy($this->img); if ($options["keep_aspect"]) { $factor = min($options["size_x"]/$width, $options["size_y"]/$height); $new_width = round($factor*$width); $new_height = round($factor*$height); } else { $new_width = $options["size_x"]; $new_height = $options["size_y"]; } // create a new temporary image $tmp_img = imagecreatetruecolor($new_width, $new_height); // copy and resize old image into new image imagecopyresampled($tmp_img, $this->img, 0, 0, 0, 0, $new_width, $new_height, $width, $height); if ($options["sharpen"]) { // define the sharpen matrix $sharpen = array( array(-1, -1.7, -1), array(-1.7, 20, -1.7), array(-1, -1.7, -1) ); // calculate the sharpen divisor $divisor = array_sum(array_map('array_sum', $sharpen)); // apply the matrix imageconvolution($tmp_img, $sharpen, $divisor, 0); } // save thumbnail into a file imagejpeg($tmp_img, $outName); } 

Go code

 func (entry *entry) GenerateThumb(options ImageType, overwrite bool) os.Error { targetFilename := entry.Filename(imageType) sourceFilename := entry.Filename(IMAGE_TYPE_FULL) targetDirname, _ := filepath.Split(targetFilename) os.MkdirAll(targetDirname, 0777) targetFi, errT := os.Stat(targetFilename) sourceFi, errS := os.Stat(sourceFilename) image := gd.CreateFromJpeg(sourceFilename) if image == nil { return os.NewError("Image could not be loaded") } var targetX, targetY int = 0, 0 if options.KeepAspect { factor := math.Fmin(float64(options.SizeX)/float64(image.Sx()), float64(options.SizeY)/float64(image.Sy())) targetX = int(factor*float64(image.Sx())) targetY = int(factor*float64(image.Sy())) } else { targetX = options.SizeX targetY = options.SizeY } tmpImage := gd.CreateTrueColor(targetX, targetY) image.CopyResampled(tmpImage, 0, 0, 0, 0, tmpImage.Sx(), tmpImage.Sy(), image.Sx(), image.Sy()) if options.Sharpen { sharpenMatrix := [3][3]float32{ {-1, -1.7, -1}, {-1.7, 20, -1.7}, {-1, -1.7, -1} } tmpImage.Convolution(sharpenMatrix, 9.2, 0) } tmpImage.Jpeg(targetFilename, 90) return nil } 

EDIT: Go-Code using resize.go (see answer)

 func (entry *entry) GenerateThumb(options ImageType, overwrite bool) os.Error { targetFilename := entry.Filename(imageType) sourceFilename := entry.Filename(IMAGE_TYPE_FULL) targetDirname, _ := filepath.Split(targetFilename) os.MkdirAll(targetDirname, 0777) targetFi, errT := os.Stat(targetFilename) sourceFi, errS := os.Stat(sourceFilename) if errT == nil && errS == nil { if targetFi.Mtime_ns > sourceFi.Mtime_ns && !overwrite { // already up-to-date, nothing to do return nil } } log.Printf("Generate(\"%v\", %v)\n", imageType, overwrite) inFile, fErr := os.Open(sourceFilename) if fErr != nil { log.Fatal(fErr) } defer inFile.Close() img, _, err := image.Decode(inFile) if err != nil { log.Fatal(err) } var targetX, targetY int if options.KeepAspect { factor := math.Fmin(float64(options.SizeX)/float64(img.Bounds().Max.X), float64(options.SizeY)/float64(img.Bounds().Max.Y)) targetX = int(factor*float64(img.Bounds().Max.X)) targetY = int(factor*float64(img.Bounds().Max.Y)) } else { targetX = curType.SizeX targetY = curType.SizeY } newImg := resize.Resample(img, image.Rect(0, 0, img.Bounds().Max.X, img.Bounds().Max.Y), targetX, targetY) var outFile *os.File outFile, fErr = os.Create(targetFilename) if fErr != nil { log.Fatal(fErr) } defer outFile.Close() err = jpeg.Encode(outFile, newImg, &jpeg.Options{90}) if err != nil { log.Fatal(err) } return nil } 
+4
source share
3 answers

The Moustachio sample application for GAE Andrew Gerrand contains a resize.go file with the original Go implementation. There was also a similar question on the go-nuts mailing list a few days ago, and Nigel posted an updated version of this file. You might want to try :)

+3
source

You should check out this size library: github.com/nfnt/resize . It has 6 good interpolation functions to choose from.

+5
source

The simplest solution seems to save the image to disk and convert from Magic Magic to convert it. You can use a disk with disks if you want additional performance.

0
source

All Articles