package itemimage import ( "bytes" "fmt" "image" "image/jpeg" "math" "golang.org/x/image/draw" _ "golang.org/x/image/webp" "printer.backend/internal/svgtemplate" ) // EmbedDPI matches plate PDF rasterization; embedded pixels need not exceed this resolution. const EmbedDPI = 300 const jpegEmbedQuality = 88 // prepareRaster decodes and downscales image data so the resulting SVG stays small enough for rsvg-convert. func prepareRaster(data []byte, d svgtemplate.Data) ([]byte, string, error) { img, _, err := image.Decode(bytes.NewReader(data)) if err != nil { return nil, "", fmt.Errorf("decode image: %w", err) } maxW := mmToPx(d.OuterWidth) maxH := mmToPx(d.OuterHeight) if maxW < 1 { maxW = 1 } if maxH < 1 { maxH = 1 } covered := scaleCover(img, maxW, maxH) rgba := image.NewNRGBA(image.Rect(0, 0, maxW, maxH)) draw.CatmullRom.Scale(rgba, rgba.Bounds(), covered, covered.Bounds(), draw.Over, nil) var buf bytes.Buffer if err := jpeg.Encode(&buf, rgba, &jpeg.Options{Quality: jpegEmbedQuality}); err != nil { return nil, "", fmt.Errorf("encode jpeg: %w", err) } return buf.Bytes(), "image/jpeg", nil } func mmToPx(mm float64) int { return int(math.Ceil(mm / 25.4 * EmbedDPI)) } // scaleCover returns an image scaled to cover dw×dh (center crop), for preserveAspectRatio slice. func scaleCover(src image.Image, dw, dh int) image.Image { sb := src.Bounds() sw, sh := sb.Dx(), sb.Dy() if sw <= 0 || sh <= 0 { return image.NewNRGBA(image.Rect(0, 0, dw, dh)) } scale := math.Max(float64(dw)/float64(sw), float64(dh)/float64(sh)) nw := int(math.Ceil(float64(sw) * scale)) nh := int(math.Ceil(float64(sh) * scale)) scaled := image.NewNRGBA(image.Rect(0, 0, nw, nh)) draw.CatmullRom.Scale(scaled, scaled.Bounds(), src, sb, draw.Over, nil) x0 := (nw - dw) / 2 y0 := (nh - dh) / 2 if x0 < 0 { x0 = 0 } if y0 < 0 { y0 = 0 } if nw < dw { dw = nw } if nh < dh { dh = nh } cropped := image.NewNRGBA(image.Rect(0, 0, dw, dh)) draw.Draw(cropped, cropped.Bounds(), scaled, image.Point{x0, y0}, draw.Src) return cropped }