first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:35:24 +03:00
commit bbbf76b184
592 changed files with 246870 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
package utils
import (
"fmt"
"image"
"image/jpeg"
"image/png"
"mime/multipart"
"os"
"path/filepath"
"strings"
"time"
"gauth-central/config"
"github.com/chai2010/webp"
"github.com/disintegration/imaging"
)
type ImageOptions struct {
Width int
Height int
Quality float32 // 1-100 (WebP uses float32)
Format string // "webp", "jpg", "png"
Mode string // "cover", "contain", "resize"
}
// SaveOptimizedImage processes and saves an image
// Returns the relative path to the saved file (e.g., "/uploads/avatars/filename.webp")
func SaveOptimizedImage(fileHeader *multipart.FileHeader, uploadDir string, userID string, opts *ImageOptions) (string, error) {
// If opts is nil, use defaults from config
if opts == nil {
opts = &ImageOptions{
Width: config.AppConfig.AvatarWidth,
Height: config.AppConfig.AvatarHeight,
Quality: float32(config.AppConfig.AvatarQuality),
Format: config.AppConfig.AvatarFormat,
Mode: config.AppConfig.AvatarMode,
}
}
// Open the file
srcFile, err := fileHeader.Open()
if err != nil {
return "", fmt.Errorf("failed to open file: %v", err)
}
defer srcFile.Close()
// Decode image
img, _, err := image.Decode(srcFile)
if err != nil {
return "", fmt.Errorf("failed to decode image: %v", err)
}
// Resize logic
if opts.Width > 0 || opts.Height > 0 {
switch strings.ToLower(opts.Mode) {
case "cover":
// Fill requires both dimensions to be effective for cropping.
// If one is missing, we fall back to Resize which preserves aspect ratio.
if opts.Width > 0 && opts.Height > 0 {
img = imaging.Fill(img, opts.Width, opts.Height, imaging.Center, imaging.Lanczos)
} else {
img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
}
case "contain":
// Fit fits the image within the box, preserving aspect ratio.
// If one dimension is 0, Fit might behave like Resize(w, h) if implementation allows,
// but imaging.Fit usually expects a box.
// If one is 0, we assume the user wants to limit the other dimension.
if opts.Width > 0 && opts.Height > 0 {
img = imaging.Fit(img, opts.Width, opts.Height, imaging.Lanczos)
} else {
img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
}
default:
// "resize" or empty: Resize preserves aspect ratio if one arg is 0.
// If both are provided, it stretches unless we use Fill/Fit.
// imaging.Resize stretches if both are non-zero.
img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos)
}
}
// Determine output format and filename
ext := "." + strings.ToLower(opts.Format)
if opts.Format == "" {
ext = ".webp" // Default to WebP
opts.Format = "webp"
}
// Generate filename
filename := fmt.Sprintf("%s_%d%s", userID, time.Now().UnixNano(), ext)
// Ensure directory exists
if err := os.MkdirAll(uploadDir, 0755); err != nil {
return "", fmt.Errorf("failed to create directory: %v", err)
}
fullPath := filepath.Join(uploadDir, filename)
outFile, err := os.Create(fullPath)
if err != nil {
return "", fmt.Errorf("failed to create output file: %v", err)
}
defer outFile.Close()
// Encode
switch strings.ToLower(opts.Format) {
case "jpg", "jpeg":
quality := int(opts.Quality)
if quality < 1 || quality > 100 {
quality = 90
}
err = jpeg.Encode(outFile, img, &jpeg.Options{Quality: quality})
case "png":
err = png.Encode(outFile, img) // PNG is lossless
case "webp", "":
err = webp.Encode(outFile, img, &webp.Options{Lossless: false, Quality: opts.Quality})
default:
// Fallback to WebP
err = webp.Encode(outFile, img, &webp.Options{Lossless: false, Quality: opts.Quality})
}
if err != nil {
return "", fmt.Errorf("failed to encode image: %v", err)
}
// Return relative path
relPath := filepath.Join(uploadDir, filename)
if strings.HasPrefix(relPath, ".") {
relPath = relPath[1:]
}
if !strings.HasPrefix(relPath, "/") {
relPath = "/" + relPath
}
return relPath, nil
}