github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/pkg/thumb/builtin.go (about) 1 package thumb 2 3 import ( 4 "context" 5 "fmt" 6 "image" 7 "image/gif" 8 "image/jpeg" 9 "image/png" 10 "io" 11 "path/filepath" 12 "strings" 13 14 model "github.com/cloudreve/Cloudreve/v3/models" 15 "github.com/cloudreve/Cloudreve/v3/pkg/util" 16 "github.com/gofrs/uuid" 17 //"github.com/nfnt/resize" 18 "golang.org/x/image/draw" 19 ) 20 21 func init() { 22 RegisterGenerator(&Builtin{}) 23 } 24 25 // Thumb 缩略图 26 type Thumb struct { 27 src image.Image 28 ext string 29 } 30 31 // NewThumbFromFile 从文件数据获取新的Thumb对象, 32 // 尝试通过文件名name解码图像 33 func NewThumbFromFile(file io.Reader, name string) (*Thumb, error) { 34 ext := strings.ToLower(filepath.Ext(name)) 35 // 无扩展名时 36 if len(ext) == 0 { 37 return nil, fmt.Errorf("unknown image format: %w", ErrPassThrough) 38 } 39 40 var err error 41 var img image.Image 42 switch ext[1:] { 43 case "jpg", "jpeg": 44 img, err = jpeg.Decode(file) 45 case "gif": 46 img, err = gif.Decode(file) 47 case "png": 48 img, err = png.Decode(file) 49 default: 50 return nil, fmt.Errorf("unknown image format: %w", ErrPassThrough) 51 } 52 if err != nil { 53 return nil, fmt.Errorf("failed to parse image: %w (%w)", err, ErrPassThrough) 54 } 55 56 return &Thumb{ 57 src: img, 58 ext: ext[1:], 59 }, nil 60 } 61 62 // GetThumb 生成给定最大尺寸的缩略图 63 func (image *Thumb) GetThumb(width, height uint) { 64 //image.src = resize.Thumbnail(width, height, image.src, resize.Lanczos3) 65 image.src = Thumbnail(width, height, image.src) 66 } 67 68 // GetSize 获取图像尺寸 69 func (image *Thumb) GetSize() (int, int) { 70 b := image.src.Bounds() 71 return b.Max.X, b.Max.Y 72 } 73 74 // Save 保存图像到给定路径 75 func (image *Thumb) Save(w io.Writer) (err error) { 76 switch model.GetSettingByNameWithDefault("thumb_encode_method", "jpg") { 77 case "png": 78 err = png.Encode(w, image.src) 79 default: 80 err = jpeg.Encode(w, image.src, &jpeg.Options{Quality: model.GetIntSetting("thumb_encode_quality", 85)}) 81 } 82 83 return err 84 85 } 86 87 // Thumbnail will downscale provided image to max width and height preserving 88 // original aspect ratio and using the interpolation function interp. 89 // It will return original image, without processing it, if original sizes 90 // are already smaller than provided constraints. 91 func Thumbnail(maxWidth, maxHeight uint, img image.Image) image.Image { 92 origBounds := img.Bounds() 93 origWidth := uint(origBounds.Dx()) 94 origHeight := uint(origBounds.Dy()) 95 newWidth, newHeight := origWidth, origHeight 96 97 // Return original image if it have same or smaller size as constraints 98 if maxWidth >= origWidth && maxHeight >= origHeight { 99 return img 100 } 101 102 // Preserve aspect ratio 103 if origWidth > maxWidth { 104 newHeight = uint(origHeight * maxWidth / origWidth) 105 if newHeight < 1 { 106 newHeight = 1 107 } 108 newWidth = maxWidth 109 } 110 111 if newHeight > maxHeight { 112 newWidth = uint(newWidth * maxHeight / newHeight) 113 if newWidth < 1 { 114 newWidth = 1 115 } 116 newHeight = maxHeight 117 } 118 return Resize(newWidth, newHeight, img) 119 } 120 121 func Resize(newWidth, newHeight uint, img image.Image) image.Image { 122 // Set the expected size that you want: 123 dst := image.NewRGBA(image.Rect(0, 0, int(newWidth), int(newHeight))) 124 // Resize: 125 draw.BiLinear.Scale(dst, dst.Rect, img, img.Bounds(), draw.Src, nil) 126 return dst 127 } 128 129 // CreateAvatar 创建头像 130 func (image *Thumb) CreateAvatar(uid uint) error { 131 // 读取头像相关设定 132 savePath := util.RelativePath(model.GetSettingByName("avatar_path")) 133 s := model.GetIntSetting("avatar_size_s", 50) 134 m := model.GetIntSetting("avatar_size_m", 130) 135 l := model.GetIntSetting("avatar_size_l", 200) 136 137 // 生成头像缩略图 138 src := image.src 139 for k, size := range []int{s, m, l} { 140 out, err := util.CreatNestedFile(filepath.Join(savePath, fmt.Sprintf("avatar_%d_%d.png", uid, k))) 141 142 if err != nil { 143 return err 144 } 145 defer out.Close() 146 147 image.src = Resize(uint(size), uint(size), src) 148 err = image.Save(out) 149 if err != nil { 150 return err 151 } 152 } 153 154 return nil 155 156 } 157 158 type Builtin struct{} 159 160 func (b Builtin) Generate(ctx context.Context, file io.Reader, src, name string, options map[string]string) (*Result, error) { 161 img, err := NewThumbFromFile(file, name) 162 if err != nil { 163 return nil, err 164 } 165 166 img.GetThumb(thumbSize(options)) 167 tempPath := filepath.Join( 168 util.RelativePath(model.GetSettingByName("temp_path")), 169 "thumb", 170 fmt.Sprintf("thumb_%s", uuid.Must(uuid.NewV4()).String()), 171 ) 172 173 thumbFile, err := util.CreatNestedFile(tempPath) 174 if err != nil { 175 return nil, fmt.Errorf("failed to create temp file: %w", err) 176 } 177 178 defer thumbFile.Close() 179 if err := img.Save(thumbFile); err != nil { 180 return nil, err 181 } 182 183 return &Result{Path: tempPath}, nil 184 } 185 186 func (b Builtin) Priority() int { 187 return 300 188 } 189 190 func (b Builtin) EnableFlag() string { 191 return "thumb_builtin_enabled" 192 }