aboutsummaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
Diffstat (limited to 'utils')
-rw-r--r--utils/format/files.go71
-rw-r--r--utils/format/format.go32
-rw-r--r--utils/format/numbers.go20
-rw-r--r--utils/format/time.go7
-rw-r--r--utils/transformers/image.go73
-rw-r--r--utils/transformers/tokens.go7
-rw-r--r--utils/validators/url.go26
7 files changed, 203 insertions, 33 deletions
diff --git a/utils/format/files.go b/utils/format/files.go
new file mode 100644
index 0000000..82f1546
--- /dev/null
+++ b/utils/format/files.go
@@ -0,0 +1,71 @@
+package format
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ _ "image/gif"
+ "image/jpeg"
+ "image/png"
+ "strings"
+
+ "golang.org/x/image/webp"
+)
+
+func init() {
+ image.RegisterFormat("webp", "RIFF????WEBP", webp.Decode, webp.DecodeConfig)
+}
+
+func FileSize(size int64) string {
+ const unit = 1024
+ if size < unit {
+ return fmt.Sprintf("%d B", size)
+ }
+ div, exp := int64(unit), 0
+ for n := size / unit; n >= unit; n /= unit {
+ div *= unit
+ exp++
+ }
+
+ val := float64(size) / float64(div)
+ unitStr := "KMGTPE"[exp : exp+1]
+ if val == float64(int64(val)) {
+ return fmt.Sprintf("%d %sB", int64(val), unitStr)
+ }
+ return fmt.Sprintf("%.2f %sB", val, unitStr)
+}
+
+func RemoveExtension(fileName string) string {
+ if fileName == "" {
+ return fileName
+ }
+ parts := strings.Split(fileName, ".")
+ if len(parts) <= 1 {
+ return fileName
+ }
+ return strings.Join(parts[:len(parts)-1], ".")
+}
+
+func DecodeImage(imgData []byte) (image.Image, string, error) {
+ img, formatName, err := image.Decode(bytes.NewReader(imgData))
+ return img, formatName, err
+}
+
+func GetImageFileSize(img image.Image) int64 {
+ var buf bytes.Buffer
+ switch img.(type) {
+ case *image.NRGBA, *image.RGBA, *image.YCbCr:
+ // Use PNG encoding for lossless compression
+ err := png.Encode(&buf, img)
+ if err != nil {
+ return 0
+ }
+ default:
+ // Fallback to JPEG encoding for other formats
+ err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 85})
+ if err != nil {
+ return 0
+ }
+ }
+ return int64(buf.Len())
+}
diff --git a/utils/format/format.go b/utils/format/format.go
deleted file mode 100644
index e57bc1f..0000000
--- a/utils/format/format.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package format
-
-import "fmt"
-
-func FileSize(size int64) string {
- const unit = 1024
- if size < unit {
- return fmt.Sprintf("%d B", size)
- }
- div, exp := int64(unit), 0
- for n := size / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
-
- val := float64(size) / float64(div)
- unitStr := "KMGTPE"[exp : exp+1]
- if val == float64(int64(val)) {
- return fmt.Sprintf("%d %sB", int64(val), unitStr)
- }
- return fmt.Sprintf("%.2f %sB", val, unitStr)
-}
-
-func Count(count int64) string {
- if count < 1000 {
- return fmt.Sprintf("%d", count)
- } else if count < 1000000 {
- return fmt.Sprintf("%.1fK", float64(count)/1000)
- } else {
- return fmt.Sprintf("%.1fM", float64(count)/1000000)
- }
-}
diff --git a/utils/format/numbers.go b/utils/format/numbers.go
new file mode 100644
index 0000000..8f546f1
--- /dev/null
+++ b/utils/format/numbers.go
@@ -0,0 +1,20 @@
+package format
+
+import "fmt"
+
+func Count(count int64) string {
+ if count < 1000 {
+ return fmt.Sprintf("%d", count)
+ } else if count < 1000000 {
+ return fmt.Sprintf("%.1fK", float64(count)/1000)
+ } else {
+ return fmt.Sprintf("%.1fM", float64(count)/1000000)
+ }
+}
+
+func Int64ToString(value int64) string {
+ if value < 0 {
+ return fmt.Sprintf("-%d", -value)
+ }
+ return fmt.Sprintf("%d", value)
+}
diff --git a/utils/format/time.go b/utils/format/time.go
new file mode 100644
index 0000000..3e65337
--- /dev/null
+++ b/utils/format/time.go
@@ -0,0 +1,7 @@
+package format
+
+import "time"
+
+func GetCurrentTimeAsTimestamp() int64 {
+ return time.Now().Unix()
+}
diff --git a/utils/transformers/image.go b/utils/transformers/image.go
index a88d8bb..e392f7f 100644
--- a/utils/transformers/image.go
+++ b/utils/transformers/image.go
@@ -1,6 +1,77 @@
package transformers
-import "imageboard/config"
+import (
+ "image"
+ "imageboard/config"
+ "imageboard/models"
+ "imageboard/utils/format"
+ "imageboard/utils/validators"
+ "strings"
+)
+
+func TransformImageToVariant(img image.Image, variant config.ImageSizeType) (models.ImageSize, image.Image, error) {
+ variantSizeMap := map[config.ImageSizeType]int{
+ config.ImageSizeTypeIcon: 64,
+ config.ImageSizeTypeThumbnail: 256,
+ config.ImageSizeTypeSmall: 512,
+ config.ImageSizeTypeMedium: 1024,
+ config.ImageSizeTypeLarge: 2048,
+ config.ImageSizeTypeOriginal: 0, // Original size, no resizing
+ }
+
+ maxWidth := variantSizeMap[variant]
+ if maxWidth > 0 {
+ img = ResizeImage(img, maxWidth)
+ }
+
+ fileSize := format.GetImageFileSize(img)
+
+ return models.ImageSize{
+ SizeType: variant,
+ Width: img.Bounds().Dx(),
+ Height: img.Bounds().Dy(),
+ FileSize: fileSize,
+ }, img, nil
+}
+
+func ResizeImage(img image.Image, maxWidth int) image.Image {
+ if maxWidth <= 0 || img.Bounds().Dx() <= maxWidth {
+ return img
+ }
+
+ ratio := float64(maxWidth) / float64(img.Bounds().Dx())
+ newWidth := int(float64(img.Bounds().Dx()) * ratio)
+ newHeight := int(float64(img.Bounds().Dy()) * ratio)
+ newImg := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
+ for y := 0; y < newHeight; y++ {
+ for x := 0; x < newWidth; x++ {
+ srcX := int(float64(x) / ratio)
+ srcY := int(float64(y) / ratio)
+ if srcX < img.Bounds().Dx() && srcY < img.Bounds().Dy() {
+ newImg.Set(x, y, img.At(srcX, srcY))
+ }
+ }
+ }
+ return newImg
+}
+
+func CreateUniqueFileName(sourceURLOrOriginalName, imageFormat string) string {
+ fileName := sourceURLOrOriginalName
+ if validators.IsValidURL(sourceURLOrOriginalName) {
+ parts := strings.Split(sourceURLOrOriginalName, "/")
+ fileName = parts[len(parts)-1]
+ }
+
+ currentTime := format.GetCurrentTimeAsTimestamp()
+ fileNameWithoutExtension := format.RemoveExtension(fileName)
+ fileName = GenerateTokenFromString(fileNameWithoutExtension + "_" + format.Int64ToString(currentTime))
+
+ if len(fileName) > 32 {
+ mid := len(fileName) / 2
+ fileName = fileName[mid-16 : mid+16]
+ }
+ return fileName + "." + imageFormat
+}
func ConvertStringRatingToType(rating string) (config.Rating, error) {
switch rating {
diff --git a/utils/transformers/tokens.go b/utils/transformers/tokens.go
index 7ad36ed..f2f2e0b 100644
--- a/utils/transformers/tokens.go
+++ b/utils/transformers/tokens.go
@@ -1,6 +1,7 @@
package transformers
import (
+ "crypto"
"crypto/rand"
"encoding/hex"
)
@@ -12,3 +13,9 @@ func GenerateRandomToken() (string, error) {
}
return hex.EncodeToString(bytes), nil
}
+
+func GenerateTokenFromString(input string) string {
+ hasher := crypto.SHA256.New()
+ hasher.Write([]byte(input))
+ return hex.EncodeToString(hasher.Sum(nil))
+}
diff --git a/utils/validators/url.go b/utils/validators/url.go
new file mode 100644
index 0000000..b85eca7
--- /dev/null
+++ b/utils/validators/url.go
@@ -0,0 +1,26 @@
+package validators
+
+import (
+ "regexp"
+ "strings"
+)
+
+func IsValidURL(url string) bool {
+ if url == "" {
+ return false
+ }
+ if len(url) > 2048 { // Common max URL length
+ return false
+ }
+ if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
+ return false
+ }
+
+ pattern := `^(http|https)://[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=]+$`
+ matched, err := regexp.MatchString(pattern, url)
+ if err != nil {
+ return false
+ }
+
+ return matched
+}