package asset import ( "bytes" "fmt" "image" "image/jpeg" "image/png" "io" "path/filepath" "strings" "golang.org/x/image/draw" ) // IsImageResizable returns whether or not an image can be resized, based on its // extension. func IsImageResizable(path string) bool { switch strings.ToLower(filepath.Ext(path)) { case ".jpg", ".jpeg", ".png": return true default: return false } } type imageLoader struct { loader Loader } // NewImageLoader wraps an existing Loader in order to perform various // image-related transformations on any image assets being loaded. Non-image // assets are loaded as-is. func NewImageLoader(loader Loader) Loader { return &imageLoader{loader} } func (l *imageLoader) Load(path string, into io.Writer, opts LoadOpts) error { if opts.ImageWidth == 0 { return l.loader.Load(path, into, opts) } if !IsImageResizable(path) { return ErrCannotResize } buf := new(bytes.Buffer) if err := l.loader.Load(path, buf, opts); err != nil { return fmt.Errorf("loading image into buffer: %w", err) } img, format, err := image.Decode(buf) if err != nil { return fmt.Errorf("decoding image: %w", err) } maxWidth := float64(opts.ImageWidth) imgRect := img.Bounds() imgW, imgH := float64(imgRect.Dx()), float64(imgRect.Dy()) if imgW > maxWidth { newH := imgH * maxWidth / imgW newImg := image.NewRGBA(image.Rect(0, 0, int(maxWidth), int(newH))) // Resize draw.BiLinear.Scale( newImg, newImg.Bounds(), img, img.Bounds(), draw.Over, nil, ) img = newImg } switch format { case "jpeg": return jpeg.Encode(into, img, nil) case "png": return png.Encode(into, img) default: return fmt.Errorf("unknown image format %q", format) } }