如何在Go中快速缩放和锐化图像?

huangapple go评论152阅读模式
英文:

How can I quickly scale and sharpen an image in Go?

问题

我目前正在将一个相当基本的相册应用程序从PHP移植到Go。该应用程序具有自动生成每个图像的缩略图和中等大小版本的功能。

在PHP中,我使用了GD库,因为它内置并且工作得很好(代码在问题的末尾)。我以为我可以在Go中复制这个功能,并在https://github.com/bolknote/go-gd中找到了go-gd(同样的代码在末尾)。它可以工作,但速度大约慢了10倍(使用time wget $URL进行测量)。PHP实现从10 MP图像生成一个1024x768版本大约需要1秒钟,而Go代码则需要近10秒钟。

有没有办法加快速度,或者有没有其他在Go中实现缩放和卷积的图像处理库,同时速度还相对较快?

PHP代码

  1. public function saveThumb($outName, $options) {
  2. $this->img = imagecreatefromjpeg($filename);
  3. if (!is_dir(dirname($outName))) {
  4. mkdir(dirname($outName), 0777, true);
  5. }
  6. $width = imagesx($this->img);
  7. $height = imagesy($this->img);
  8. if ($options["keep_aspect"]) {
  9. $factor = min($options["size_x"]/$width, $options["size_y"]/$height);
  10. $new_width = round($factor*$width);
  11. $new_height = round($factor*$height);
  12. } else {
  13. $new_width = $options["size_x"];
  14. $new_height = $options["size_y"];
  15. }
  16. // create a new temporary image
  17. $tmp_img = imagecreatetruecolor($new_width, $new_height);
  18. // copy and resize old image into new image
  19. imagecopyresampled($tmp_img, $this->img, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
  20. if ($options["sharpen"]) {
  21. // define the sharpen matrix
  22. $sharpen = array(
  23. array(-1, -1.7, -1),
  24. array(-1.7, 20, -1.7),
  25. array(-1, -1.7, -1)
  26. );
  27. // calculate the sharpen divisor
  28. $divisor = array_sum(array_map('array_sum', $sharpen));
  29. // apply the matrix
  30. imageconvolution($tmp_img, $sharpen, $divisor, 0);
  31. }
  32. // save thumbnail into a file
  33. imagejpeg($tmp_img, $outName);
  34. }

Go代码

  1. func (entry *entry) GenerateThumb(options ImageType, overwrite bool) os.Error {
  2. targetFilename := entry.Filename(imageType)
  3. sourceFilename := entry.Filename(IMAGE_TYPE_FULL)
  4. targetDirname, _ := filepath.Split(targetFilename)
  5. os.MkdirAll(targetDirname, 0777)
  6. targetFi, errT := os.Stat(targetFilename)
  7. sourceFi, errS := os.Stat(sourceFilename)
  8. image := gd.CreateFromJpeg(sourceFilename)
  9. if image == nil {
  10. return os.NewError("Image could not be loaded")
  11. }
  12. var targetX, targetY int = 0, 0
  13. if options.KeepAspect {
  14. factor := math.Fmin(float64(options.SizeX)/float64(image.Sx()), float64(options.SizeY)/float64(image.Sy()))
  15. targetX = int(factor*float64(image.Sx()))
  16. targetY = int(factor*float64(image.Sy()))
  17. } else {
  18. targetX = options.SizeX
  19. targetY = options.SizeY
  20. }
  21. tmpImage := gd.CreateTrueColor(targetX, targetY)
  22. image.CopyResampled(tmpImage, 0, 0, 0, 0, tmpImage.Sx(), tmpImage.Sy(), image.Sx(), image.Sy())
  23. if options.Sharpen {
  24. sharpenMatrix := [3][3]float32{
  25. {-1, -1.7, -1},
  26. {-1.7, 20, -1.7},
  27. {-1, -1.7, -1} }
  28. tmpImage.Convolution(sharpenMatrix, 9.2, 0)
  29. }
  30. tmpImage.Jpeg(targetFilename, 90)
  31. return nil
  32. }

编辑:使用resize.go的Go代码(参见答案)

  1. func (entry *entry) GenerateThumb(options ImageType, overwrite bool) os.Error {
  2. targetFilename := entry.Filename(imageType)
  3. sourceFilename := entry.Filename(IMAGE_TYPE_FULL)
  4. targetDirname, _ := filepath.Split(targetFilename)
  5. os.MkdirAll(targetDirname, 0777)
  6. targetFi, errT := os.Stat(targetFilename)
  7. sourceFi, errS := os.Stat(sourceFilename)
  8. if errT == nil && errS == nil {
  9. if targetFi.Mtime_ns > sourceFi.Mtime_ns && !overwrite {
  10. // already up-to-date, nothing to do
  11. return nil
  12. }
  13. }
  14. log.Printf("Generate(\"%v\", %v)\n", imageType, overwrite)
  15. inFile, fErr := os.Open(sourceFilename)
  16. if fErr != nil {
  17. log.Fatal(fErr)
  18. }
  19. defer inFile.Close()
  20. img, _, err := image.Decode(inFile)
  21. if err != nil {
  22. log.Fatal(err)
  23. }
  24. var targetX, targetY int
  25. if options.KeepAspect {
  26. factor := math.Fmin(float64(options.SizeX)/float64(img.Bounds().Max.X), float64(options.SizeY)/float64(img.Bounds().Max.Y))
  27. targetX = int(factor*float64(img.Bounds().Max.X))
  28. targetY = int(factor*float64(img.Bounds().Max.Y))
  29. } else {
  30. targetX = curType.SizeX
  31. targetY = curType.SizeY
  32. }
  33. newImg := resize.Resample(img, image.Rect(0, 0, img.Bounds().Max.X, img.Bounds().Max.Y), targetX, targetY)
  34. var outFile *os.File
  35. outFile, fErr = os.Create(targetFilename)
  36. if fErr != nil {
  37. log.Fatal(fErr)
  38. }
  39. defer outFile.Close()
  40. err = jpeg.Encode(outFile, newImg, &jpeg.Options{90})
  41. if err != nil {
  42. log.Fatal(err)
  43. }
  44. return nil
  45. }
英文:

I'm currently porting a pretty basic gallery application from PHP to Go. This application features automatic generation of thumbnails and middle-sized version of every image.

In PHP I used GD, because it ships with it and worked pretty well. (Code is at the end of the question). I thought I could just replicate that in Go and found go-gd from https://github.com/bolknote/go-gd (again, code is at the end). It works, but it is roughly 10 times slower (measured using time wget $URL). The PHP implementation takes about 1 second for generating a 1024x768 version from a 10 MP-image, while the Go-Code takes almost 10 seconds.

Is there any way to speed this up or any other image-processing libary for Go, which implements scaling and convolution while being reasonably fast?

PHP-Code

  1. public function saveThumb($outName, $options) {
  2. $this->img = imagecreatefromjpeg($filename);
  3. if (!is_dir(dirname($outName))) {
  4. mkdir(dirname($outName), 0777, true);
  5. }
  6. $width = imagesx($this->img);
  7. $height = imagesy($this->img);
  8. if ($options["keep_aspect"]) {
  9. $factor = min($options["size_x"]/$width, $options["size_y"]/$height);
  10. $new_width = round($factor*$width);
  11. $new_height = round($factor*$height);
  12. } else {
  13. $new_width = $options["size_x"];
  14. $new_height = $options["size_y"];
  15. }
  16. // create a new temporary image
  17. $tmp_img = imagecreatetruecolor($new_width, $new_height);
  18. // copy and resize old image into new image
  19. imagecopyresampled($tmp_img, $this->img, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
  20. if ($options["sharpen"]) {
  21. // define the sharpen matrix
  22. $sharpen = array(
  23. array(-1, -1.7, -1),
  24. array(-1.7, 20, -1.7),
  25. array(-1, -1.7, -1)
  26. );
  27. // calculate the sharpen divisor
  28. $divisor = array_sum(array_map('array_sum', $sharpen));
  29. // apply the matrix
  30. imageconvolution($tmp_img, $sharpen, $divisor, 0);
  31. }
  32. // save thumbnail into a file
  33. imagejpeg($tmp_img, $outName);
  34. }

Go-Code

  1. func (entry *entry) GenerateThumb(options ImageType, overwrite bool) os.Error {
  2. targetFilename := entry.Filename(imageType)
  3. sourceFilename := entry.Filename(IMAGE_TYPE_FULL)
  4. targetDirname, _ := filepath.Split(targetFilename)
  5. os.MkdirAll(targetDirname, 0777)
  6. targetFi, errT := os.Stat(targetFilename)
  7. sourceFi, errS := os.Stat(sourceFilename)
  8. image := gd.CreateFromJpeg(sourceFilename)
  9. if image == nil {
  10. return os.NewError("Image could not be loaded")
  11. }
  12. var targetX, targetY int = 0, 0
  13. if options.KeepAspect {
  14. factor := math.Fmin(float64(options.SizeX)/float64(image.Sx()), float64(options.SizeY)/float64(image.Sy()))
  15. targetX = int(factor*float64(image.Sx()))
  16. targetY = int(factor*float64(image.Sy()))
  17. } else {
  18. targetX = options.SizeX
  19. targetY = options.SizeY
  20. }
  21. tmpImage := gd.CreateTrueColor(targetX, targetY)
  22. image.CopyResampled(tmpImage, 0, 0, 0, 0, tmpImage.Sx(), tmpImage.Sy(), image.Sx(), image.Sy())
  23. if options.Sharpen {
  24. sharpenMatrix := [3][3]float32{
  25. {-1, -1.7, -1},
  26. {-1.7, 20, -1.7},
  27. {-1, -1.7, -1} }
  28. tmpImage.Convolution(sharpenMatrix, 9.2, 0)
  29. }
  30. tmpImage.Jpeg(targetFilename, 90)
  31. return nil
  32. }

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

  1. func (entry *entry) GenerateThumb(options ImageType, overwrite bool) os.Error {
  2. targetFilename := entry.Filename(imageType)
  3. sourceFilename := entry.Filename(IMAGE_TYPE_FULL)
  4. targetDirname, _ := filepath.Split(targetFilename)
  5. os.MkdirAll(targetDirname, 0777)
  6. targetFi, errT := os.Stat(targetFilename)
  7. sourceFi, errS := os.Stat(sourceFilename)
  8. if errT == nil && errS == nil {
  9. if targetFi.Mtime_ns > sourceFi.Mtime_ns && !overwrite {
  10. // already up-to-date, nothing to do
  11. return nil
  12. }
  13. }
  14. log.Printf("Generate(\"%v\", %v)\n", imageType, overwrite)
  15. inFile, fErr := os.Open(sourceFilename)
  16. if fErr != nil {
  17. log.Fatal(fErr)
  18. }
  19. defer inFile.Close()
  20. img, _, err := image.Decode(inFile)
  21. if err != nil {
  22. log.Fatal(err)
  23. }
  24. var targetX, targetY int
  25. if options.KeepAspect {
  26. factor := math.Fmin(float64(options.SizeX)/float64(img.Bounds().Max.X), float64(options.SizeY)/float64(img.Bounds().Max.Y))
  27. targetX = int(factor*float64(img.Bounds().Max.X))
  28. targetY = int(factor*float64(img.Bounds().Max.Y))
  29. } else {
  30. targetX = curType.SizeX
  31. targetY = curType.SizeY
  32. }
  33. newImg := resize.Resample(img, image.Rect(0, 0, img.Bounds().Max.X, img.Bounds().Max.Y), targetX, targetY)
  34. var outFile *os.File
  35. outFile, fErr = os.Create(targetFilename)
  36. if fErr != nil {
  37. log.Fatal(fErr)
  38. }
  39. defer outFile.Close()
  40. err = jpeg.Encode(outFile, newImg, &jpeg.Options{90})
  41. if err != nil {
  42. log.Fatal(err)
  43. }
  44. return nil
  45. }

答案1

得分: 6

你应该查看这个调整大小的库:github.com/nfnt/resize。它有6个好的插值函数可供选择。

英文:

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

答案2

得分: 3

The Moustachio example application for GAE by Andrew Gerrand contains a resize.go file with a native Go implementation. There was also a similar question on the go-nuts mailing list some days ago and Nigel has posted an updated version of this file there. You might want to try it 如何在Go中快速缩放和锐化图像?

英文:

The Moustachio example application for GAE by Andrew Gerrand contains a resize.go file with a native Go implementation. There was also a similar question on the go-nuts mailing list some days ago and Nigel has posted an updated version of this file there. You might want to try it 如何在Go中快速缩放和锐化图像?

答案3

得分: 0

最简单的解决方案似乎是将图像保存到磁盘,并使用Image Magic的convert命令进行转换。如果您想要额外的性能,可以使用内存磁盘。

英文:

The easiest solution seems to save the image to disk, and execute convert from Image Magic to transform it. You can use a ram disk if you want extra performance.

huangapple
  • 本文由 发表于 2011年9月5日 22:19:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/7309355.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定