package processor import ( "fmt" "image" "image/gif" "image/jpeg" "image/png" "os" "path/filepath" "golang.org/x/image/draw" ) const ( maxImageWidth = 1024 jpegQuality = 80 ) // validateImage reads and decodes the source image, resizing if necessary. // It performs only read validation; the caller is responsible for writing assets. func validateImage(srcPath string) (image.Image, error) { img, err := decodeImage(srcPath) if err != nil { return nil, err } return resizeIfNeeded(img), nil } // writeImageAsset writes the prepared image as JPEG into postDir. func writeImageAsset(img image.Image, postDir string) error { outPath := filepath.Join(postDir, "image.jpg") return writeJPEG(img, outPath) } // decodeImage decodes a JPEG, PNG, or GIF (first frame) from srcPath. func decodeImage(srcPath string) (image.Image, error) { f, err := os.Open(srcPath) if err != nil { return nil, fmt.Errorf("open image %s: %w", srcPath, err) } defer f.Close() ext := filepath.Ext(srcPath) switch ext { case ".jpg", ".jpeg": img, err := jpeg.Decode(f) if err != nil { return nil, fmt.Errorf("decode JPEG %s: %w", srcPath, err) } return img, nil case ".png": img, err := png.Decode(f) if err != nil { return nil, fmt.Errorf("decode PNG %s: %w", srcPath, err) } return img, nil case ".gif": // Use only the first frame of animated GIFs. g, err := gif.Decode(f) if err != nil { return nil, fmt.Errorf("decode GIF %s: %w", srcPath, err) } return g, nil default: return nil, fmt.Errorf("unsupported image format: %s", ext) } } // resizeIfNeeded returns a resized copy of img if its width exceeds maxImageWidth, // preserving aspect ratio. Otherwise the original is returned unchanged. func resizeIfNeeded(img image.Image) image.Image { bounds := img.Bounds() w := bounds.Dx() if w <= maxImageWidth { return img } h := bounds.Dy() newW := maxImageWidth newH := (h * newW) / w dst := image.NewRGBA(image.Rect(0, 0, newW, newH)) draw.BiLinear.Scale(dst, dst.Bounds(), img, bounds, draw.Over, nil) return dst } // writeJPEG encodes img as JPEG at the configured quality level and writes to path. func writeJPEG(img image.Image, path string) error { f, err := os.Create(path) if err != nil { return fmt.Errorf("create JPEG %s: %w", path, err) } defer f.Close() opts := &jpeg.Options{Quality: jpegQuality} if err := jpeg.Encode(f, img, opts); err != nil { return fmt.Errorf("encode JPEG %s: %w", path, err) } return nil }