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  }