2023-02-02 17:59:48 +00:00
|
|
|
package images
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
2024-07-22 01:01:05 +00:00
|
|
|
"fmt"
|
2023-02-02 17:59:48 +00:00
|
|
|
"image"
|
2024-07-22 01:01:05 +00:00
|
|
|
"io"
|
2023-02-02 17:59:48 +00:00
|
|
|
"io/ioutil"
|
2024-07-22 01:01:05 +00:00
|
|
|
"net/http"
|
2023-02-02 17:59:48 +00:00
|
|
|
"os"
|
2024-07-22 01:01:05 +00:00
|
|
|
"strings"
|
2023-02-02 17:59:48 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
maxChatMessageImageSize = 400000
|
|
|
|
resizeTargetImageSize = 350000
|
|
|
|
idealTargetImageSize = 50000
|
|
|
|
)
|
|
|
|
|
2023-08-21 16:48:07 +00:00
|
|
|
var DefaultBounds = FileSizeLimits{Ideal: idealTargetImageSize, Max: resizeTargetImageSize}
|
|
|
|
|
2024-07-22 01:01:05 +00:00
|
|
|
func FetchAndStoreRemoteImage(url string) (string, error) {
|
|
|
|
resp, err := http.Get(url) //nolint
|
2023-02-02 17:59:48 +00:00
|
|
|
if err != nil {
|
2024-07-22 01:01:05 +00:00
|
|
|
return "", fmt.Errorf("error fetching image from URL: %w", err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return "", fmt.Errorf("bad status code: %s", resp.Status)
|
2023-02-02 17:59:48 +00:00
|
|
|
}
|
|
|
|
|
2024-07-22 01:01:05 +00:00
|
|
|
tempFile, err := ioutil.TempFile("", "image-*")
|
2023-02-02 17:59:48 +00:00
|
|
|
if err != nil {
|
2024-07-22 01:01:05 +00:00
|
|
|
return "", fmt.Errorf("error creating a temporary file: %w", err)
|
2023-02-02 17:59:48 +00:00
|
|
|
}
|
2024-07-22 01:01:05 +00:00
|
|
|
defer tempFile.Close()
|
2023-02-02 17:59:48 +00:00
|
|
|
|
2024-07-22 01:01:05 +00:00
|
|
|
_, err = io.Copy(tempFile, resp.Body)
|
2023-02-02 17:59:48 +00:00
|
|
|
if err != nil {
|
2024-07-22 01:01:05 +00:00
|
|
|
os.Remove(tempFile.Name()) // Ensure temporary file is deleted on error
|
|
|
|
return "", fmt.Errorf("error writing image to temp file: %w", err)
|
2023-02-02 17:59:48 +00:00
|
|
|
}
|
|
|
|
|
2024-07-22 01:01:05 +00:00
|
|
|
return tempFile.Name(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func OpenAndDecodeImage(imagePath string) (image.Image, error) {
|
|
|
|
file, err := os.Open(imagePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error opening image file: %w", err)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
img, err := Decode(imagePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error decoding image: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return img, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func AdjustImage(img image.Image, crop bool, inputImage CroppedImage) ([]byte, error) {
|
2023-02-02 17:59:48 +00:00
|
|
|
if crop {
|
|
|
|
cropRect := image.Rectangle{
|
|
|
|
Min: image.Point{X: inputImage.X, Y: inputImage.Y},
|
|
|
|
Max: image.Point{X: inputImage.X + inputImage.Width, Y: inputImage.Y + inputImage.Height},
|
|
|
|
}
|
2024-07-22 01:01:05 +00:00
|
|
|
var err error
|
2023-02-02 17:59:48 +00:00
|
|
|
img, err = Crop(img, cropRect)
|
|
|
|
if err != nil {
|
2024-07-22 01:01:05 +00:00
|
|
|
return nil, fmt.Errorf("error cropping image: %w", err)
|
2023-02-02 17:59:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bb := bytes.NewBuffer([]byte{})
|
2024-07-22 01:01:05 +00:00
|
|
|
err := CompressToFileLimits(bb, img, DefaultBounds)
|
2023-02-02 17:59:48 +00:00
|
|
|
if err != nil {
|
2024-07-22 01:01:05 +00:00
|
|
|
return nil, fmt.Errorf("error compressing image: %w", err)
|
2023-02-02 17:59:48 +00:00
|
|
|
}
|
|
|
|
|
2024-07-22 01:01:05 +00:00
|
|
|
payload := bb.Bytes()
|
2023-02-02 17:59:48 +00:00
|
|
|
if len(payload) > maxChatMessageImageSize {
|
|
|
|
return nil, errors.New("image too large")
|
|
|
|
}
|
|
|
|
|
|
|
|
return payload, nil
|
|
|
|
}
|
2024-07-22 01:01:05 +00:00
|
|
|
func OpenAndAdjustImage(inputImage CroppedImage, crop bool) ([]byte, error) {
|
|
|
|
var imgPath string = inputImage.ImagePath
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Check if the image is from a remote source
|
|
|
|
if strings.HasPrefix(inputImage.ImagePath, "http://") || strings.HasPrefix(inputImage.ImagePath, "https://") {
|
|
|
|
imgPath, err = FetchAndStoreRemoteImage(inputImage.ImagePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer os.Remove(imgPath) // Clean up the temporary file
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode the image
|
|
|
|
img, err := OpenAndDecodeImage(imgPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adjust (crop and compress) the image
|
|
|
|
return AdjustImage(img, crop, inputImage)
|
|
|
|
}
|