diff --git a/DirectThumbnailer.go b/DirectThumbnailer.go index 97d32c9..e78cd3a 100644 --- a/DirectThumbnailer.go +++ b/DirectThumbnailer.go @@ -50,7 +50,10 @@ func (this *DirectThumbnailer) RenderFile(absPath string) ([]byte, error) { // Scale - dest := this.scaleImage(src) + dest, err := this.scaleImage(src) + if err != nil { + return nil, err + } // Rasterise result diff --git a/config.go b/config.go index cd23c27..a24d973 100644 --- a/config.go +++ b/config.go @@ -14,7 +14,9 @@ const ( FitInside AspectFormat = 82 // Crop to dimensions NearestNeighbour ScaleFormat = 120 - Bilinear ScaleFormat = 121 + BilinearFast ScaleFormat = 121 + BilinearAccurate ScaleFormat = 122 + Bicubic ScaleFormat = 123 ) type Config struct { diff --git a/scaler.go b/scaler.go index 98b6afb..def2867 100644 --- a/scaler.go +++ b/scaler.go @@ -7,59 +7,76 @@ import ( "image/png" "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 srcH := src.Bounds().Max.Y - destW := 0 - destH := 0 + var srcCopyPosition, destCopyPosition image.Rectangle - if srcW > srcH { - destW = this.cfg.Width - destH = this.cfg.Height * srcH / srcW - } else { - destW = this.cfg.Width * srcW / srcH - destH = this.cfg.Height + switch this.cfg.Aspect { + case FitInside: + + var destW, destH int + + 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) - scaleH := float64(srcH) / float64(destH) + dest := image.NewRGBA(image.Rect(0, 0, this.cfg.Width, this.cfg.Height)) - 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 { - - 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: + draw.NearestNeighbor.Scale(dest, destCopyPosition, src, srcCopyPosition, draw.Src, nil) - for y := 0; y < destH; y += 1 { - for x := 0; x < destW; x += 1 { - mapx := int(float64(x) * scaleW) - mapy := int(float64(y) * scaleH) - dest.Set(x+offsetX, y+offsetY, src.At(mapx, mapy)) - } - } + case BilinearFast: + draw.ApproxBiLinear.Scale(dest, destCopyPosition, src, srcCopyPosition, draw.Src, nil) + 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) {