mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 04:50:14 +09:00
242 lines
4.7 KiB
Go
242 lines
4.7 KiB
Go
package utils
|
|
|
|
import (
|
|
"chat/globals"
|
|
"fmt"
|
|
"image"
|
|
"image/gif"
|
|
"image/jpeg"
|
|
"io"
|
|
"math"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/chai2010/webp"
|
|
)
|
|
|
|
type Image struct {
|
|
Object image.Image
|
|
Content string
|
|
}
|
|
type Images []Image
|
|
|
|
func NewImage(url string) (*Image, error) {
|
|
if strings.HasPrefix(url, "data:image/") {
|
|
data := SafeSplit(url, ",", 2)
|
|
if data[1] == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
decoded, err := Base64Decode(data[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
img, _, err := image.Decode(strings.NewReader(string(decoded)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Image{Object: img, Content: url}, nil
|
|
}
|
|
|
|
res, err := http.Get(url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
var img image.Image
|
|
suffix := strings.ToLower(path.Ext(url))
|
|
switch suffix {
|
|
case ".png":
|
|
if img, _, err = image.Decode(res.Body); err != nil {
|
|
return nil, err
|
|
}
|
|
case ".jpg", ".jpeg":
|
|
if img, err = jpeg.Decode(res.Body); err != nil {
|
|
return nil, err
|
|
}
|
|
case "webp":
|
|
if img, err = webp.Decode(res.Body); err != nil {
|
|
return nil, err
|
|
}
|
|
case "gif":
|
|
ticks, err := gif.DecodeAll(res.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
img = ticks.Image[0]
|
|
}
|
|
|
|
return &Image{Object: img, Content: url}, nil
|
|
}
|
|
|
|
func NewImageContent(content string) *Image {
|
|
return &Image{Content: content}
|
|
}
|
|
|
|
func ConvertToBase64(url string) (string, error) {
|
|
if strings.HasPrefix(url, "data:image/") {
|
|
data := strings.Split(url, ",")
|
|
if len(data) != 2 {
|
|
return "", nil
|
|
}
|
|
return data[1], nil
|
|
}
|
|
|
|
res, err := http.Get(url)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
data, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return Base64EncodeBytes(data), nil
|
|
}
|
|
|
|
func (i *Image) GetWidth() int {
|
|
return i.Object.Bounds().Max.X
|
|
}
|
|
|
|
func (i *Image) GetHeight() int {
|
|
return i.Object.Bounds().Max.Y
|
|
}
|
|
|
|
func (i *Image) GetPixel(x int, y int) (uint32, uint32, uint32, uint32) {
|
|
return i.Object.At(x, y).RGBA()
|
|
}
|
|
|
|
func (i *Image) GetPixelColor(x int, y int) (int, int, int) {
|
|
r, g, b, _ := i.GetPixel(x, y)
|
|
return int(r), int(g), int(b)
|
|
}
|
|
|
|
func (i *Image) CountTokens(model string) int {
|
|
if globals.IsVisionModel(model) {
|
|
// tile size is 512x512
|
|
// the max size of image is 2048x2048
|
|
// the image that is larger than 2048x2048 will be resized in 16 tiles
|
|
|
|
x := LimitMax(math.Ceil(float64(i.GetWidth())/512), 4)
|
|
y := LimitMax(math.Ceil(float64(i.GetHeight())/512), 4)
|
|
tiles := int(x) * int(y)
|
|
|
|
return 85 + 170*tiles
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func (i *Image) IsBase64() bool {
|
|
return strings.HasPrefix(i.Content, "data:image/")
|
|
}
|
|
|
|
func (i *Image) GetType() string {
|
|
// example: image/jpeg, image/png, image/gif
|
|
|
|
if i.IsBase64() {
|
|
t := SafeSplit(i.Content, ";", 2)[0]
|
|
return strings.ReplaceAll(t, "data:", "")
|
|
}
|
|
|
|
// example: .jpg, .png, .gif to image/jpeg, image/png, image/gif
|
|
switch strings.ToLower(path.Ext(i.Content)) {
|
|
case ".jpg", ".jpeg":
|
|
return "image/jpeg"
|
|
case ".png":
|
|
return "image/png"
|
|
case ".gif":
|
|
return "image/gif"
|
|
case ".webp":
|
|
return "image/webp"
|
|
case ".bmp":
|
|
return "image/bmp"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (i *Image) ToBase64() string {
|
|
if i.IsBase64() {
|
|
return i.Content
|
|
}
|
|
|
|
// get url content and convert to base64
|
|
data, err := ConvertToBase64(i.Content)
|
|
if err != nil {
|
|
globals.Warn(fmt.Sprintf("cannot convert image to base64: %s", err.Error()))
|
|
return ""
|
|
}
|
|
|
|
return fmt.Sprintf("data:%s;base64,%s", i.GetType(), data)
|
|
}
|
|
|
|
func (i *Image) ToRawBase64() string {
|
|
// example: return /9j/...
|
|
if i.IsBase64() {
|
|
return SafeSplit(i.Content, ",", 2)[1]
|
|
}
|
|
|
|
// get url content and convert to base64
|
|
data, err := ConvertToBase64(i.Content)
|
|
if err != nil {
|
|
globals.Warn(fmt.Sprintf("cannot convert image to base64: %s", err.Error()))
|
|
return ""
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
func DownloadImage(url string, path string) error {
|
|
res, err := http.Get(url)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func(Body io.ReadCloser) {
|
|
err := Body.Close()
|
|
if err != nil {
|
|
globals.Debug("[utils] close file error: %s (path: %s)", err.Error(), path)
|
|
}
|
|
}(res.Body)
|
|
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func(file *os.File) {
|
|
err := file.Close()
|
|
if err != nil {
|
|
globals.Debug("[utils] close file error: %s (path: %s)", err.Error(), path)
|
|
}
|
|
}(file)
|
|
|
|
_, err = io.Copy(file, res.Body)
|
|
return err
|
|
}
|
|
|
|
func StoreImage(url string) string {
|
|
if globals.AcceptImageStore {
|
|
hash := Md5Encrypt(url) + path.Ext(url)
|
|
|
|
if err := DownloadImage(url, fmt.Sprintf("storage/attachments/%s", hash)); err != nil {
|
|
globals.Warn(fmt.Sprintf("[utils] save image error: %s", err.Error()))
|
|
return url
|
|
}
|
|
|
|
return fmt.Sprintf("%s/attachments/%s", globals.NotifyUrl, hash)
|
|
}
|
|
|
|
return url
|
|
}
|