feat: add mosaic screenshot annotations
This commit is contained in:
@@ -81,6 +81,10 @@ func ApplyAnnotationsWithScale(pngBytes []byte, annotations []Annotation, scaleX
|
||||
drawEllipseOutline(dst, ann.Points, scaleX, scaleY, width, c)
|
||||
case "text":
|
||||
drawTextAnnotation(dst, ann, scaleX, scaleY, c)
|
||||
case "mosaic-brush":
|
||||
applyMosaicBrush(dst, ann.Points, scaleX, scaleY, width)
|
||||
case "mosaic-rect":
|
||||
applyMosaicRect(dst, ann.Points, scaleX, scaleY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +95,93 @@ func ApplyAnnotationsWithScale(pngBytes []byte, annotations []Annotation, scaleX
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
const mosaicBlockSize = 10
|
||||
|
||||
func applyMosaicBrush(img *image.RGBA, points []Point, scaleX, scaleY float64, width int) {
|
||||
if len(points) == 0 {
|
||||
return
|
||||
}
|
||||
mask := image.NewAlpha(img.Bounds())
|
||||
maskColor := color.RGBA{A: 255}
|
||||
if len(points) == 1 {
|
||||
drawDotMask(mask, scalePoint(points[0], scaleX, scaleY), width, maskColor)
|
||||
} else {
|
||||
for i := 1; i < len(points); i++ {
|
||||
drawMaskLine(mask, scalePoint(points[i-1], scaleX, scaleY), scalePoint(points[i], scaleX, scaleY), width, maskColor)
|
||||
}
|
||||
}
|
||||
applyPixelation(img, img.Bounds(), mask)
|
||||
}
|
||||
|
||||
func applyMosaicRect(img *image.RGBA, points []Point, scaleX, scaleY float64) {
|
||||
if len(points) < 2 {
|
||||
return
|
||||
}
|
||||
a := scalePoint(points[0], scaleX, scaleY)
|
||||
b := scalePoint(points[len(points)-1], scaleX, scaleY)
|
||||
x1, x2 := ordered(a.X, b.X)
|
||||
y1, y2 := ordered(a.Y, b.Y)
|
||||
r := image.Rect(int(math.Floor(x1)), int(math.Floor(y1)), int(math.Ceil(x2)), int(math.Ceil(y2))).Intersect(img.Bounds())
|
||||
applyPixelation(img, r, nil)
|
||||
}
|
||||
|
||||
func applyPixelation(img *image.RGBA, region image.Rectangle, mask *image.Alpha) {
|
||||
if region.Empty() {
|
||||
return
|
||||
}
|
||||
for y := region.Min.Y; y < region.Max.Y; y += mosaicBlockSize {
|
||||
for x := region.Min.X; x < region.Max.X; x += mosaicBlockSize {
|
||||
block := image.Rect(x, y, min(x+mosaicBlockSize, region.Max.X), min(y+mosaicBlockSize, region.Max.Y))
|
||||
var r, g, b, a, count uint64
|
||||
for py := block.Min.Y; py < block.Max.Y; py++ {
|
||||
for px := block.Min.X; px < block.Max.X; px++ {
|
||||
c := img.RGBAAt(px, py)
|
||||
r += uint64(c.R)
|
||||
g += uint64(c.G)
|
||||
b += uint64(c.B)
|
||||
a += uint64(c.A)
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
continue
|
||||
}
|
||||
avg := color.RGBA{uint8(r / count), uint8(g / count), uint8(b / count), uint8(a / count)}
|
||||
for py := block.Min.Y; py < block.Max.Y; py++ {
|
||||
for px := block.Min.X; px < block.Max.X; px++ {
|
||||
if mask == nil || mask.AlphaAt(px, py).A > 0 {
|
||||
img.SetRGBA(px, py, avg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawMaskLine(mask *image.Alpha, a, b Point, width int, c color.RGBA) {
|
||||
dx, dy := b.X-a.X, b.Y-a.Y
|
||||
steps := int(math.Max(math.Abs(dx), math.Abs(dy)))
|
||||
if steps == 0 {
|
||||
drawDotMask(mask, a, width, c)
|
||||
return
|
||||
}
|
||||
for i := 0; i <= steps; i++ {
|
||||
t := float64(i) / float64(steps)
|
||||
drawDotMask(mask, Point{X: a.X + dx*t, Y: a.Y + dy*t}, width, c)
|
||||
}
|
||||
}
|
||||
|
||||
func drawDotMask(mask *image.Alpha, p Point, width int, c color.RGBA) {
|
||||
radius := float64(width) / 2
|
||||
for y := int(math.Floor(p.Y - radius)); y <= int(math.Ceil(p.Y+radius)); y++ {
|
||||
for x := int(math.Floor(p.X - radius)); x <= int(math.Ceil(p.X+radius)); x++ {
|
||||
if image.Pt(x, y).In(mask.Bounds()) && math.Hypot(float64(x)-p.X, float64(y)-p.Y) <= radius {
|
||||
mask.SetAlpha(x, y, color.Alpha{A: c.A})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseHexColor(hex string) (color.RGBA, error) {
|
||||
value := strings.TrimPrefix(strings.TrimSpace(hex), "#")
|
||||
if len(value) != 6 {
|
||||
|
||||
Reference in New Issue
Block a user