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 }