package images import ( "bytes" "context" "encoding/base64" "encoding/binary" "errors" "fmt" "hash/fnv" "image" "image/jpeg" _ "image/png" "io" "strings" "time" _ "golang.org/x/image/webp" "github.com/valkey-io/valkey-go" ) var ErrNotAnImage = errors.New("not an image") func (s *Service) getMime(path string) (mimeType string, err error) { info, err := s.fs.Stat(path) if err != nil { err = fmt.Errorf("image file info could not be fetched: %w", err) } if mimer, ok := info.(interface{ ContentType() string }); ok { mimeType = mimer.ContentType() } return } func (s *Service) getImage(uid string) (file []byte, mimeType string, err error) { path, err := PathFromUID(uid) if err != nil { err = fmt.Errorf("could not get path: %w", err) return } mimeType, err = s.getMime(path) if err != nil { return } if !strings.HasPrefix(mimeType, "image") { err = ErrNotAnImage return } file, err = s.fs.Read(path) if err != nil { err = fmt.Errorf("image file could not be fetched") } return } func (s *Service) GetImage(ctx context.Context, uid string, options Options) (img io.Reader, mimeType string, err error) { rawImage, mimeType, err := s.getImage(uid) if err != nil { return } hash, err := hash(rawImage, options) if err != nil { return } if imgBytes, err := s.cache.Do(ctx, s.cache.B().Get().Key("img:"+hash).Build()).AsBytes(); err == nil { return bytes.NewBuffer(imgBytes), mimeType, err } decImage, _, err := image.Decode(bytes.NewBuffer(rawImage)) if err != nil { return } decImage = scale(decImage, options) outBuff := bytes.NewBuffer(make([]byte, 0, 1024)) if options.Quality == 0 { options.Quality = 80 } err = jpeg.Encode(outBuff, decImage, &jpeg.Options{Quality: options.Quality}) if err != nil { return } if err = s.cache.Do(ctx, s.cache.B().Set().Key("img:"+hash).Value(valkey.BinaryString(outBuff.Bytes())).Ex(time.Hour*48).Build()).Error(); err == nil { return outBuff, "image/jpeg", err } return outBuff, "image/jpeg", err } func hash(bytes []byte, options Options) (hash string, err error) { h := fnv.New128() if _, err = h.Write(bytes); err != nil { return } if err = binary.Write(h, binary.LittleEndian, int64(options.Height)); err != nil { return } if err = binary.Write(h, binary.LittleEndian, int64(options.Width)); err != nil { return } if err = binary.Write(h, binary.LittleEndian, int64(options.Quality)); err != nil { return } res := h.Sum(make([]byte, 0, 128/8)) return base64.RawStdEncoding.EncodeToString(res), nil }