mirror of
https://github.com/lukaszraczylo/mcp-captcharesolver.git
synced 2026-10-12 00:45:28 +00:00
139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
package imageutil
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
_ "image/jpeg" // register JPEG decoder
|
|
"image/png"
|
|
|
|
xdraw "golang.org/x/image/draw"
|
|
"golang.org/x/image/font"
|
|
"golang.org/x/image/font/basicfont"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// Upscale decodes a PNG/JPEG image, scales it by factor (clamped to 1..6)
|
|
// using high-quality resampling, and returns a PNG re-encode plus "image/png".
|
|
func Upscale(data []byte, factor int) ([]byte, string, error) {
|
|
if factor < 1 {
|
|
factor = 1
|
|
}
|
|
if factor > 6 {
|
|
factor = 6
|
|
}
|
|
|
|
src, _, err := image.Decode(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("upscale: decode: %w", err)
|
|
}
|
|
|
|
bounds := src.Bounds()
|
|
newW := bounds.Dx() * factor
|
|
newH := bounds.Dy() * factor
|
|
dst := image.NewRGBA(image.Rect(0, 0, newW, newH))
|
|
|
|
xdraw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), xdraw.Over, nil)
|
|
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, dst); err != nil {
|
|
return nil, "", fmt.Errorf("upscale: encode: %w", err)
|
|
}
|
|
return buf.Bytes(), "image/png", nil
|
|
}
|
|
|
|
// AnnotateGrid decodes the image and overlays a rows x cols grid: thin lines
|
|
// on cell boundaries and the 0-based row-major cell index drawn in the
|
|
// top-left of each cell, returning a PNG. Helps a vision model reference cells
|
|
// unambiguously. If rows<=0 or cols<=0, returns the original re-encoded PNG.
|
|
func AnnotateGrid(data []byte, rows, cols int) ([]byte, error) {
|
|
src, _, err := image.Decode(bytes.NewReader(data))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("annotategrid: decode: %w", err)
|
|
}
|
|
|
|
bounds := src.Bounds()
|
|
dst := image.NewRGBA(bounds)
|
|
draw.Draw(dst, bounds, src, bounds.Min, draw.Src)
|
|
|
|
if rows <= 0 || cols <= 0 {
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, dst); err != nil {
|
|
return nil, fmt.Errorf("annotategrid: encode: %w", err)
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
w := bounds.Dx()
|
|
h := bounds.Dy()
|
|
lineColor := color.RGBA{R: 220, G: 30, B: 30, A: 160}
|
|
|
|
// Draw vertical grid lines.
|
|
for c := 1; c < cols; c++ {
|
|
x := c * w / cols
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
dst.SetRGBA(x, y, lineColor)
|
|
if x+1 < bounds.Max.X {
|
|
dst.SetRGBA(x+1, y, lineColor)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw horizontal grid lines.
|
|
for r := 1; r < rows; r++ {
|
|
y := r * h / rows
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
dst.SetRGBA(x, y, lineColor)
|
|
if y+1 < bounds.Max.Y {
|
|
dst.SetRGBA(x+1, y+1, lineColor)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw cell labels.
|
|
face := basicfont.Face7x13
|
|
ascent := face.Ascent // plain int pixels
|
|
descent := face.Descent // plain int pixels
|
|
advance := face.Advance // plain int pixels per glyph
|
|
labelColor := color.RGBA{R: 255, G: 255, B: 255, A: 255}
|
|
bgColor := color.RGBA{R: 0, G: 0, B: 0, A: 180}
|
|
|
|
for r := 0; r < rows; r++ {
|
|
for c := 0; c < cols; c++ {
|
|
idx := r*cols + c
|
|
label := fmt.Sprintf("%d", idx)
|
|
|
|
cellX := c * w / cols
|
|
cellY := r * h / rows
|
|
|
|
// Label position: 2px padding from cell top-left.
|
|
px := cellX + 2
|
|
py := cellY + ascent + 2
|
|
|
|
labelW := len(label) * advance
|
|
|
|
// Draw dark background rect behind label.
|
|
bgRect := image.Rect(px-1, py-ascent-1, px+labelW+1, py+descent+1)
|
|
draw.Draw(dst, bgRect, &image.Uniform{bgColor}, image.Point{}, draw.Over)
|
|
|
|
// Draw label text.
|
|
d := &font.Drawer{
|
|
Dst: dst,
|
|
Src: image.NewUniform(labelColor),
|
|
Face: face,
|
|
Dot: fixed.P(px, py),
|
|
}
|
|
d.DrawString(label)
|
|
}
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, dst); err != nil {
|
|
return nil, fmt.Errorf("annotategrid: encode: %w", err)
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|