package render import ( "bytes" "image" "image/color" "image/jpeg" "math" ) // RenderJPG rasterizes a page's strokes at 300 DPI as JPEG. func RenderJPG(pageSize string, strokes []Stroke, quality int) ([]byte, error) { w, h := pageSizeCanonical(pageSize) img := image.NewRGBA(image.Rect(0, 0, w, h)) // White background for y := 0; y < h; y++ { for x := 0; x < w; x++ { img.Set(x, y, color.White) } } // Draw strokes for _, s := range strokes { points, err := decodePoints(s.PointData) if err != nil { continue } if len(points) < 4 { continue } c := argbToColor(s.Color) penRadius := float64(s.PenSize) / 2.0 for i := 0; i < len(points)-3; i += 2 { x0, y0 := points[i], points[i+1] x1, y1 := points[i+2], points[i+3] drawLine(img, x0, y0, x1, y1, penRadius, c) } } var buf bytes.Buffer if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}); err != nil { return nil, err } return buf.Bytes(), nil } func pageSizeCanonical(pageSize string) (int, int) { switch pageSize { case "LARGE": return 3300, 5100 default: return 2550, 3300 } } func argbToColor(argb int32) color.RGBA { return color.RGBA{ R: uint8((argb >> 16) & 0xFF), G: uint8((argb >> 8) & 0xFF), B: uint8(argb & 0xFF), A: uint8((argb >> 24) & 0xFF), } } // drawLine draws a line with the given pen radius using Bresenham's. func drawLine(img *image.RGBA, x0, y0, x1, y1, radius float64, c color.RGBA) { dx := x1 - x0 dy := y1 - y0 dist := math.Sqrt(dx*dx + dy*dy) if dist < 1 { fillCircle(img, int(x0), int(y0), int(radius), c) return } steps := int(dist) + 1 for i := 0; i <= steps; i++ { t := float64(i) / float64(steps) x := x0 + t*dx y := y0 + t*dy fillCircle(img, int(x), int(y), int(radius), c) } } func fillCircle(img *image.RGBA, cx, cy, r int, c color.RGBA) { if r < 1 { r = 1 } bounds := img.Bounds() for dy := -r; dy <= r; dy++ { for dx := -r; dx <= r; dx++ { if dx*dx+dy*dy <= r*r { px, py := cx+dx, cy+dy if px >= bounds.Min.X && px < bounds.Max.X && py >= bounds.Min.Y && py < bounds.Max.Y { img.Set(px, py, c) } } } } }