В настоящее время я портирую довольно простое приложение галереи с PHP на Go. В этом приложении реализована автоматическая генерация миниатюр и средней версии каждого изображения.
В PHP я использовал GD, потому что он поставляется с ним и работал довольно хорошо. (Код в конце вопроса). Я думал, что смогу просто повторить это в Go и нашел go-gd
из https://github.com/bolknote/go-gd (опять же, код в конце). Это работает, но примерно в 10 раз медленнее (измеряется с помощью time wget $URL
). Реализация PHP занимает около 1 секунды для генерации версии 1024x768 из 10 MP-изображения, в то время как Go-код занимает почти 10 секунд.
Есть ли какой-нибудь способ ускорить это или любую другую библиотеку обработки изображений для Go, которая реализует масштабирование и свертку, будучи достаточно быстрой?
PHP-код
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-код
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
}
РЕДАКТИРОВАТЬ: Go-код с использованием resize.go (см. Ответ)
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
}