6
1
mirror of https://git.mills.io/saltyim/saltyim.git synced 2024-06-16 03:48:24 +00:00
prologic-saltyim/internal/utils.go
James Mills 3fccb3ae5f Add Avatar service and cli for updating avatar on a broker (#116)
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
2022-04-02 02:59:39 +00:00

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
}