You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
5.0 KiB

package core
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"log"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/disintegration/imaging"
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/webp"
)
// ResampleFilter aliases imaging.ResampleFilter so callers don't need to
// import the imaging package directly.
type ResampleFilter = imaging.ResampleFilter
var (
ResampleLanczos = imaging.Lanczos
ResampleCubic = imaging.CatmullRom
ResampleLinear = imaging.Linear
ResampleBox = imaging.Box
)
// Run processes all files in summary using cfg, sending a Progress value on
// the returned channel for every image (success or failure).
// The channel is closed when all images are done.
// If cfg.TSFile is set, the TypeScript file is written after all workers finish.
func Run(summary *ImageSummary, cfg *Config) <-chan Progress {
ch := make(chan Progress, len(summary.Files))
total := len(summary.Files)
go func() {
defer close(ch)
var (
wg sync.WaitGroup
mu sync.Mutex
counter atomic.Int32
tsmu sync.Mutex
tsEntries []ProcessResult
)
jobs := make(chan string, total)
for i := 0; i < cfg.Workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for inputPath := range jobs {
result := ProcessOne(inputPath, cfg)
current := int(counter.Add(1))
if result.Success && cfg.TSFile != "" {
tsmu.Lock()
tsEntries = append(tsEntries, result)
tsmu.Unlock()
}
mu.Lock()
ch <- Progress{Current: current, Total: total, Result: result}
mu.Unlock()
}
}()
}
for _, f := range summary.Files {
jobs <- f
}
close(jobs)
wg.Wait()
// Write TypeScript file after all workers are done
if cfg.TSFile != "" && len(tsEntries) > 0 {
if err := WriteTSFile(cfg.TSFile, tsEntries); err != nil {
ch <- Progress{
Current: total,
Total: total,
Result: ProcessResult{
Filename: cfg.TSFile,
Success: false,
Error: fmt.Errorf("error writing TypeScript file: %v", err),
},
}
}
}
}()
return ch
}
// ProcessOne processes a single image and returns the result.
func ProcessOne(inputPath string, cfg *Config) ProcessResult {
start := time.Now()
result := ProcessResult{
Filename: filepath.Base(inputPath),
}
if fi, err := os.Stat(inputPath); err == nil {
result.InputSize = fi.Size()
}
outputPath := BuildOutputPath(inputPath, cfg)
result.OutputPath = outputPath
base := filepath.Base(outputPath)
result.ImageKey = SanitizeTSKey(base[:len(base)-len(filepath.Ext(base))])
if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
result.Error = fmt.Errorf("error creating directory: %v", err)
return result
}
f, err := os.Open(inputPath)
if err != nil {
result.Error = fmt.Errorf("error opening file: %v", err)
return result
}
defer f.Close()
img, srcFormat, err := image.Decode(f)
if err != nil {
result.Error = fmt.Errorf("error decoding image: %v", err)
return result
}
if cfg.Verbose {
bounds := img.Bounds()
log.Printf(" %s: format=%s size=%dx%d",
result.Filename, srcFormat, bounds.Dx(), bounds.Dy())
}
resized := imaging.Resize(img, cfg.Width, cfg.Height, GetResampleFilter(cfg.Resample))
// Resolve the output format
outputFormat := cfg.Format
if outputFormat == "" {
if cfg.B64Enabled || cfg.TSFile != "" {
srcExt := strings.TrimPrefix(strings.ToLower(filepath.Ext(inputPath)), ".")
outputFormat = ExtToFormat(srcExt)
} else {
outputFormat = strings.TrimPrefix(strings.ToLower(filepath.Ext(outputPath)), ".")
}
}
// Encode into a buffer (always — enables b64 and avoids double-open)
var buf bytes.Buffer
mimeType, encErr := EncodeImage(&buf, resized, outputFormat, cfg.Quality)
if encErr != nil {
result.Error = fmt.Errorf("error encoding image: %v", encErr)
return result
}
// Build base64 data URI when needed
if cfg.B64Enabled || cfg.TSFile != "" {
encoded := base64.StdEncoding.EncodeToString(buf.Bytes())
result.B64Data = "data:" + mimeType + ";base64," + encoded
}
// Write output (skip when -ts: writing is deferred to WriteTSFile)
if cfg.TSFile == "" {
var writeErr error
if cfg.B64Enabled {
writeErr = os.WriteFile(outputPath, []byte(result.B64Data), 0644)
} else {
writeErr = os.WriteFile(outputPath, buf.Bytes(), 0644)
}
if writeErr != nil {
result.Error = fmt.Errorf("error writing output file: %v", writeErr)
return result
}
if fi, err := os.Stat(outputPath); err == nil {
result.OutputSize = fi.Size()
}
}
result.Success = true
result.Duration = time.Since(start)
return result
}
// WriteTSFile writes a TypeScript module exporting all images as data URIs.
func WriteTSFile(path string, entries []ProcessResult) error {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err
}
var sb strings.Builder
sb.WriteString("export const images: Record<string, string> = {\n")
for _, e := range entries {
fmt.Fprintf(&sb, " %s: %q,\n", e.ImageKey, e.B64Data)
}
sb.WriteString("}\n")
return os.WriteFile(path, []byte(sb.String()), 0644)
}

Powered by TurnKey Linux.