package models import ( "fmt" "image" "io" "log" "net/http" "os" "path/filepath" "regexp" "github.com/chai2010/webp" "github.com/disintegration/imaging" giftowebp "github.com/sizeofint/gif-to-webp" ) // Image is a struct that contains the information of an image type Image struct { image image.Image format string data []byte } // 初始化图片 func (img *Image) Init(content string) error { if len(regexp.MustCompile(`image.gameuiux.cn`).FindStringSubmatch(content)) > 0 { key := regexp.MustCompile(`^https?://image.gameuiux.cn/`).ReplaceAllString(content, "") filePath := filepath.Join("oss", key) // 先检查本地是否存在原图, 没有则从oss下载 if _, err := os.Stat(filePath); err != nil { ensureDirExists := func(filePath string) error { dir := filepath.Dir(filePath) // 获取文件所在的目录 if _, err := os.Stat(dir); os.IsNotExist(err) { // 如果目录不存在,创建多级目录 if err := os.MkdirAll(dir, 0755); err != nil { return err } } return nil } // 确保目录存在 if err := ensureDirExists(filePath); err != nil { log.Fatalf("Failed to create directory: %v\n", err) } bucket := GetBucket("gameui-image2") fmt.Println("从 OSS 下载:", key, filePath) err = bucket.GetObjectToFile(key, filePath) if err != nil { log.Println("Failed to download file: ", err) return err } } // 从本地打开文件 body, err := os.Open(filePath) if err != nil { log.Println("打开本地图片失败:", err) return err } defer body.Close() // 判断图片格式是否为 gif 或 GIF if len(regexp.MustCompile(`\.gif$`).FindStringSubmatch(key)) > 0 { img.format = "gif" img.data, err = io.ReadAll(body) if err != nil { log.Println("读取图片失败", err) return err } return nil } // 将文件解码为 image.Image img.image, img.format, err = image.Decode(body) if err != nil { log.Println("解码图像失败", err) return err } return nil } else { var resp *http.Response log.Println("直接从网络下载图片:", content) resp, err := http.Get(content) if err != nil { log.Println("下载图片失败", err) return err } defer resp.Body.Close() // 判断图片格式是否为 gif 或 GIF if len(regexp.MustCompile(`\.gif$`).FindStringSubmatch(content)) > 0 { img.format = "gif" img.data, err = io.ReadAll(resp.Body) if err != nil { log.Println("读取图片失败", err) return err } println("数据长度:", len(img.data)) return nil } // 将文件解码为 image.Image img.image, img.format, err = image.Decode(resp.Body) if err != nil { log.Println("解码图像失败", err) return nil } return nil } } // 将图片输出为指定尺寸的 webp 格式(默认使用 Lanczos 缩放算法) func (img *Image) ToWebP(width int, height int, fit string) ([]byte, error) { // 如果原图是GIF格式的动态图片,直接不作尺寸处理,直接转换为webp格式 if img.format == "gif" { converter := giftowebp.NewConverter() converter.LoopCompatibility = true // 是否兼容循环动画 converter.WebPConfig.SetLossless(1) // 0 有损压缩 1无损压缩 converter.WebPConfig.SetMethod(6) // 压缩速度 0-6 0最快 6质量最好 converter.WebPConfig.SetQuality(100) // 压缩质量 0-100 converter.WebPAnimEncoderOptions.SetKmin(9) converter.WebPAnimEncoderOptions.SetKmax(17) return converter.Convert(img.data) } // 如果指定了宽高却没有指定fit, 则默认fit为cover if width != 0 && height != 0 && fit == "" { fit = "cover" } // 如果未指定宽高和fit, 则不缩放图片直接返回webp if width == 0 && height == 0 && fit == "" { return webp.EncodeRGBA(img.image, 100) } switch fit { case "cover": return webp.EncodeRGBA(imaging.Fill(img.image, width, height, imaging.Center, imaging.Lanczos), 100) case "contain": return webp.EncodeRGBA(imaging.Fit(img.image, width, height, imaging.Lanczos), 100) case "fill": return webp.EncodeRGBA(imaging.Fill(img.image, width, height, imaging.Center, imaging.Lanczos), 100) case "inside": return webp.EncodeRGBA(imaging.Fit(img.image, width, height, imaging.Lanczos), 100) case "outside": return webp.EncodeRGBA(imaging.Fill(img.image, width, height, imaging.Center, imaging.Lanczos), 100) case "scale-down": return webp.EncodeRGBA(imaging.Fit(img.image, width, height, imaging.Lanczos), 100) default: return webp.EncodeRGBA(imaging.Resize(img.image, width, height, imaging.Lanczos), 100) } }