package main import ( "image" "image/color" _ "image/gif" "image/jpeg" _ "image/png" "io" ) func init() { thumbnailers = append(thumbnailers, &imageThumbnailer{}) } type imageThumbnailer struct{} func (it *imageThumbnailer) CanHandle(ext string) bool { switch ext { case ".jpg", ".jpeg", ".png", ".gif": return true } return false } func (it *imageThumbnailer) Generate(src io.Reader, dst io.Writer, width int) error { img, _, err := image.Decode(src) if err != nil { return err } return jpeg.Encode(dst, resizeBox(img, width), &jpeg.Options{Quality: 80}) } // resizeBox downsamples src to the requested width using a box filter. // Aspect ratio is preserved. Upscaling is a no-op (returns src unchanged). // Each source pixel is visited exactly once; alpha is discarded. func resizeBox(src image.Image, width int) image.Image { b := src.Bounds() srcW, srcH := b.Dx(), b.Dy() if srcW <= width { return src } dstW := width dstH := srcH * width / srcW if dstH < 1 { dstH = 1 } dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH)) for y := 0; y < dstH; y++ { sy0 := y * srcH / dstH sy1 := (y + 1) * srcH / dstH if sy1 == sy0 { sy1 = sy0 + 1 } for x := 0; x < dstW; x++ { sx0 := x * srcW / dstW sx1 := (x + 1) * srcW / dstW if sx1 == sx0 { sx1 = sx0 + 1 } var r, g, bl, n uint64 for sy := sy0; sy < sy1; sy++ { for sx := sx0; sx < sx1; sx++ { sr, sg, sb, _ := src.At(b.Min.X+sx, b.Min.Y+sy).RGBA() r += uint64(sr >> 8) g += uint64(sg >> 8) bl += uint64(sb >> 8) n++ } } dst.SetRGBA(x, y, color.RGBA{ R: uint8(r / n), G: uint8(g / n), B: uint8(bl / n), A: 255, }) } } return dst }