package pipeline import ( "bytes" "fmt" "image" "image/jpeg" "image/png" "strings" "golang.org/x/image/tiff" _ "golang.org/x/image/webp" // decode-only; we re-encode webp as png ) // RotateImage rotates src clockwise by angle (must be 0/90/180/270). func RotateImage(src image.Image, angle int) image.Image { angle = ((angle % 360) + 360) % 360 if angle == 0 { return src } b := src.Bounds() w, h := b.Dx(), b.Dy() switch angle { case 180: dst := image.NewRGBA(image.Rect(0, 0, w, h)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { dst.Set(w-1-x, h-1-y, src.At(b.Min.X+x, b.Min.Y+y)) } } return dst case 90: dst := image.NewRGBA(image.Rect(0, 0, h, w)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { dst.Set(h-1-y, x, src.At(b.Min.X+x, b.Min.Y+y)) } } return dst case 270: dst := image.NewRGBA(image.Rect(0, 0, h, w)) for y := 0; y < h; y++ { for x := 0; x < w; x++ { dst.Set(y, w-1-x, src.At(b.Min.X+x, b.Min.Y+y)) } } return dst } return src } // EncodeImage re-encodes img using format hint from the original filename / detected format. // Falls back to PNG when format is not directly writable. func EncodeImage(img image.Image, format string) ([]byte, string, error) { var buf bytes.Buffer switch strings.ToLower(format) { case "jpeg", "jpg": if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 92}); err != nil { return nil, "", err } return buf.Bytes(), "jpg", nil case "tiff", "tif": if err := tiff.Encode(&buf, img, &tiff.Options{Compression: tiff.Deflate}); err != nil { return nil, "", err } return buf.Bytes(), "tiff", nil case "png", "": if err := png.Encode(&buf, img); err != nil { return nil, "", err } return buf.Bytes(), "png", nil case "webp": // Re-encode to PNG (no webp encoder in stdlib/x/image). if err := png.Encode(&buf, img); err != nil { return nil, "", err } return buf.Bytes(), "png", nil } return nil, "", fmt.Errorf("unsupported format: %s", format) }