mirror of
https://git.mills.io/saltyim/saltyim.git
synced 2024-06-16 03:48:24 +00:00
![James Mills](/assets/img/avatar_default.png)
This PR also: - Tidies up the default options and config - Tidies up the service user code Co-authored-by: James Mills <prologic@shortcircuit.net.au> Reviewed-on: https://git.mills.io/saltyim/saltyim/pulls/116
114 lines
2.9 KiB
Go
114 lines
2.9 KiB
Go
package internal
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"image"
|
|
"image/png"
|
|
"io"
|
|
"os"
|
|
|
|
// Blank import so we can handle image/jpeg
|
|
_ "image/jpeg"
|
|
|
|
"github.com/disintegration/gift"
|
|
"github.com/disintegration/imageorient"
|
|
"github.com/h2non/filetype"
|
|
"github.com/nullrocks/identicon"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// FileExists returns true if the given file exists
|
|
func FileExists(name string) bool {
|
|
if _, err := os.Stat(name); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GenerateAvatar generates a unique avatar for a user based on an identicon
|
|
func GenerateAvatar(conf *Config, domainNick string) (image.Image, error) {
|
|
ig, err := identicon.New(conf.PrimaryDomain, 7, 4)
|
|
if err != nil {
|
|
log.WithError(err).Error("error creating identicon generator")
|
|
return nil, err
|
|
}
|
|
ii, err := ig.Draw(domainNick)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("error generating external avatar for %s", domainNick)
|
|
return nil, err
|
|
}
|
|
return ii.Image(avatarResolution), nil
|
|
}
|
|
|
|
// GenerateRandomToken generates random tokens used primarily for recovery
|
|
func GenerateRandomToken() string {
|
|
b := make([]byte, 16)
|
|
_, _ = rand.Read(b)
|
|
return fmt.Sprintf("%x", b)
|
|
}
|
|
|
|
// IsImage returns true if the bytes are an image
|
|
func IsImage(data []byte) bool {
|
|
head := data[:261]
|
|
return filetype.IsImage(head)
|
|
}
|
|
|
|
// ImageOptions set options for handling image resizing
|
|
type ImageOptions struct {
|
|
Resize bool
|
|
Width int
|
|
Height int
|
|
}
|
|
|
|
// ProcessImage processes an image and resizes the image according to the
|
|
// image options provided and returns a new image for storage or to be served
|
|
func ProcessImage(r io.Reader, opts *ImageOptions) (image.Image, error) {
|
|
img, _, err := imageorient.Decode(r)
|
|
if err != nil {
|
|
log.WithError(err).Error("imageorient.Decode failed")
|
|
return nil, err
|
|
}
|
|
|
|
if opts != nil && opts.Resize {
|
|
g := gift.New()
|
|
if opts.Width > 0 && opts.Height > 0 {
|
|
g.Add(gift.ResizeToFit(opts.Width, opts.Height, gift.LanczosResampling))
|
|
} else if (opts.Width+opts.Height > 0) && (opts.Height > 0 || img.Bounds().Size().X > opts.Width) {
|
|
g.Add(gift.Resize(opts.Width, opts.Height, gift.LanczosResampling))
|
|
}
|
|
|
|
newImg := image.NewRGBA(g.Bounds(img.Bounds()))
|
|
|
|
g.Draw(newImg, img)
|
|
|
|
return newImg, nil
|
|
}
|
|
|
|
return img, nil
|
|
}
|
|
|
|
// SaverAvatar processes an avatar, processes it and resizes it to the given size
|
|
// saves it to storage with the given filename
|
|
func SaveAvatar(fn string, r io.Reader, size int) error {
|
|
opts := &ImageOptions{Resize: true, Height: size, Width: size}
|
|
img, err := ProcessImage(r, opts)
|
|
if err != nil {
|
|
return fmt.Errorf("error processing avatar: %w", err)
|
|
}
|
|
|
|
f, err := os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("error opening file for writing: %w", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
if png.Encode(f, img); err != nil {
|
|
return fmt.Errorf("error encoding and writing avatar: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|