scaler: support FitInside, Bilinear{Fast/Accurate} variants, BiCubic

This commit is contained in:
mappu 2018-06-09 17:17:57 +12:00
parent 525dcf31ad
commit 9a833d025a
3 changed files with 61 additions and 39 deletions

View File

@ -50,7 +50,10 @@ func (this *DirectThumbnailer) RenderFile(absPath string) ([]byte, error) {
// Scale // Scale
dest := this.scaleImage(src) dest, err := this.scaleImage(src)
if err != nil {
return nil, err
}
// Rasterise result // Rasterise result

View File

@ -14,7 +14,9 @@ const (
FitInside AspectFormat = 82 // Crop to dimensions FitInside AspectFormat = 82 // Crop to dimensions
NearestNeighbour ScaleFormat = 120 NearestNeighbour ScaleFormat = 120
Bilinear ScaleFormat = 121 BilinearFast ScaleFormat = 121
BilinearAccurate ScaleFormat = 122
Bicubic ScaleFormat = 123
) )
type Config struct { type Config struct {

View File

@ -7,59 +7,76 @@ import (
"image/png" "image/png"
"golang.org/x/image/bmp" "golang.org/x/image/bmp"
"golang.org/x/image/draw"
) )
func (this *DirectThumbnailer) scaleImage(src image.Image) image.Image { func (this *DirectThumbnailer) scaleImage(src image.Image) (image.Image, error) {
srcW := src.Bounds().Max.X srcW := src.Bounds().Max.X
srcH := src.Bounds().Max.Y srcH := src.Bounds().Max.Y
destW := 0 var srcCopyPosition, destCopyPosition image.Rectangle
destH := 0
if srcW > srcH { switch this.cfg.Aspect {
destW = this.cfg.Width case FitInside:
destH = this.cfg.Height * srcH / srcW
} else { var destW, destH int
destW = this.cfg.Width * srcW / srcH
destH = this.cfg.Height if srcW > srcH {
destW = this.cfg.Width
destH = this.cfg.Height * srcH / srcW
} else {
destW = this.cfg.Width * srcW / srcH
destH = this.cfg.Height
}
offsetX := (this.cfg.Width - destW) / 2
offsetY := (this.cfg.Height - destH) / 2
srcCopyPosition = src.Bounds()
destCopyPosition = image.Rect(offsetX, offsetY, destW+offsetX, destH+offsetY)
case FitOutside:
var srcSmallestDim, offsetX, offsetY int
if srcW > srcH {
// Landscape
srcSmallestDim = srcH
offsetY = 0
offsetX = (srcW - srcH) / 2
} else {
// Portrait (or square)
srcSmallestDim = srcW
offsetY = (srcH - srcW) / 2
offsetX = 0
}
srcCopyPosition = image.Rect(offsetX, offsetY, srcSmallestDim+offsetX, srcSmallestDim+offsetY)
destCopyPosition = image.Rect(0, 0, this.cfg.Width, this.cfg.Height)
default:
return nil, ErrInvalidOption
} }
offsetX := (this.cfg.Width - destW) / 2 //
offsetY := (this.cfg.Height - destH) / 2
scaleW := float64(srcW) / float64(destW) dest := image.NewRGBA(image.Rect(0, 0, this.cfg.Width, this.cfg.Height))
scaleH := float64(srcH) / float64(destH)
dest := image.NewRGBA(image.Rectangle{Max: image.Point{X: this.cfg.Width, Y: this.cfg.Height}}) // For a transparent destination, Op.Src is faster than Op.Over
switch this.cfg.Scale { switch this.cfg.Scale {
case Bilinear:
for y := 0; y < destH; y += 1 {
for x := 0; x < destW; x += 1 {
c00 := src.At(int(float64(x)*scaleW), int(float64(y)*scaleH))
c01 := src.At(int((float64(x)+0.5)*scaleW), int(float64(y)*scaleH))
c10 := src.At(int(float64(x)*scaleW), int((float64(y)+0.5)*scaleH))
c11 := src.At(int((float64(x)+0.5)*scaleW), int((float64(y)+0.5)*scaleH))
cBlend := Blend(Blend(c00, c01), Blend(c10, c11))
dest.Set(x+offsetX, y+offsetY, cBlend)
}
}
case NearestNeighbour: case NearestNeighbour:
draw.NearestNeighbor.Scale(dest, destCopyPosition, src, srcCopyPosition, draw.Src, nil)
for y := 0; y < destH; y += 1 { case BilinearFast:
for x := 0; x < destW; x += 1 { draw.ApproxBiLinear.Scale(dest, destCopyPosition, src, srcCopyPosition, draw.Src, nil)
mapx := int(float64(x) * scaleW)
mapy := int(float64(y) * scaleH)
dest.Set(x+offsetX, y+offsetY, src.At(mapx, mapy))
}
}
case BilinearAccurate:
draw.BiLinear.Scale(dest, destCopyPosition, src, srcCopyPosition, draw.Src, nil)
case Bicubic:
draw.CatmullRom.Scale(dest, destCopyPosition, src, srcCopyPosition, draw.Src, nil)
} }
return dest return dest, nil
} }
func (this *DirectThumbnailer) encode(dest image.Image) ([]byte, error) { func (this *DirectThumbnailer) encode(dest image.Image) ([]byte, error) {