// Package collage makes a pic collage package collage import ( "errors" "image" "image/color" "image/draw" "image/jpeg" "io" "io/fs" "go.oneofone.dev/resize" ) type Dimension struct { Width uint `json:"width"` Height uint `json:"height"` } type Point struct { X uint `json:"x"` Y uint `json:"y"` } type Rectangle struct { Start Point `json:"start"` End Point `json:"end"` } func (r Rectangle) ToImgRect() image.Rectangle { return image.Rectangle{ Min: image.Point{int(r.Start.X), int(r.Start.Y)}, Max: image.Point{int(r.End.X), int(r.End.Y)}, } } type Photo struct { ImageName string `json:"image"` Crop Rectangle `json:"crop"` Frame Rectangle `json:"frame"` } type Request struct { BackgroundImage string `json:"background_image"` Aspect Dimension `json:"aspect"` Dimension Dimension `json:"dimension"` Photos []Photo `json:"photos"` } func Make(req *Request, source fs.FS, output io.Writer) error { rec := image.Rect(0, 0, int(req.Aspect.Width), int(req.Aspect.Height)) canvas := image.NewRGBA64(rec) white := color.RGBA{255, 255, 255, 255} draw.Draw(canvas, rec, &image.Uniform{white}, image.Point{}, draw.Src) for _, photo := range req.Photos { img, err := GetImage(source, photo.ImageName) if err != nil { return err } croppedImage, err := Crop(img, photo.Crop) if err != nil { return err } destRect := FrameTranslate(req.Aspect, req.Dimension, photo.Frame).ToImgRect() resizedImg := resize.Resize(uint(destRect.Dx()), uint(destRect.Dy()), croppedImage, resize.Lanczos3) draw.Draw(canvas, destRect, resizedImg, image.Point{0, 0}, draw.Src) } var opt jpeg.Options opt.Quality = 100 return jpeg.Encode(output, canvas, &opt) } func FrameTranslate(resolution Dimension, frameSize Dimension, frame Rectangle) Rectangle { newX := func(oldX uint) uint { return oldX * resolution.Width / frameSize.Width } newY := func(oldY uint) uint { return oldY * resolution.Height / frameSize.Height } return Rectangle{ Start: Point{newX(frame.Start.X), newY(frame.Start.Y)}, End: Point{newX(frame.End.X), newY(frame.End.Y)}, } } type HasSubImage interface { SubImage(r image.Rectangle) image.Image } func Crop(img image.Image, r Rectangle) (image.Image, error) { if imgHS, ok := img.(HasSubImage); ok { return imgHS.SubImage(r.ToImgRect()), nil } return nil, errors.New("image does not support cropping") } func GetImage(source fs.FS, imageName string) (image.Image, error) { imgF, err := source.Open(imageName) if err != nil { return nil, err } img, _, err := image.Decode(imgF) return img, err }