Files
image2api/backend/internal/service/deai.go
T
2026-07-09 23:48:58 +08:00

96 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"bytes"
"image"
"image/color"
"image/jpeg"
"image/png"
"math/rand"
"time"
_ "image/gif"
_ "golang.org/x/image/webp"
)
// applyDeAI post-processes a generated image to weaken AI-detection artifacts:
// a small edge crop (kills border fingerprints), per-pixel low-amplitude noise,
// a subtle brightness/contrast jitter, and a JPEG re-encode round-trip that
// both introduces natural compression statistics and strips any embedded
// metadata/watermark chunks. Output is PNG (matching the stored content type).
func applyDeAI(b []byte) ([]byte, error) {
src, _, err := image.Decode(bytes.NewReader(b))
if err != nil {
return nil, err
}
bounds := src.Bounds()
w, h := bounds.Dx(), bounds.Dy()
// Crop a sliver off each edge (~0.3% of the dimension, 212px).
cropX := clampInt(w*3/1000, 2, 12)
cropY := clampInt(h*3/1000, 2, 12)
if w <= cropX*4 || h <= cropY*4 {
cropX, cropY = 0, 0
}
x0, y0 := bounds.Min.X+cropX, bounds.Min.Y+cropY
x1, y1 := bounds.Max.X-cropX, bounds.Max.Y-cropY
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
// Subtle tone jitter: contrast ±2%, brightness ±2 levels.
contrast := 1.0 + (rng.Float64()-0.5)*0.04
brightness := (rng.Float64() - 0.5) * 4.0
out := image.NewRGBA(image.Rect(0, 0, x1-x0, y1-y0))
for y := y0; y < y1; y++ {
for x := x0; x < x1; x++ {
r, g, bl, a := src.At(x, y).RGBA()
out.SetRGBA(x-x0, y-y0, color.RGBA{
R: jitterChannel(r, contrast, brightness, rng),
G: jitterChannel(g, contrast, brightness, rng),
B: jitterChannel(bl, contrast, brightness, rng),
A: uint8(a >> 8),
})
}
}
// JPEG round-trip: natural compression statistics + strips metadata.
var jbuf bytes.Buffer
if err := jpeg.Encode(&jbuf, out, &jpeg.Options{Quality: 93}); err != nil {
return nil, err
}
rt, err := jpeg.Decode(bytes.NewReader(jbuf.Bytes()))
if err != nil {
return nil, err
}
var pbuf bytes.Buffer
if err := png.Encode(&pbuf, rt); err != nil {
return nil, err
}
return pbuf.Bytes(), nil
}
// jitterChannel applies contrast/brightness around mid-gray plus ±2 noise to a
// 16-bit color channel, returning the clamped 8-bit value.
func jitterChannel(v uint32, contrast, brightness float64, rng *rand.Rand) uint8 {
f := float64(v>>8)
f = (f-128.0)*contrast + 128.0 + brightness + (rng.Float64()-0.5)*4.0
if f < 0 {
f = 0
}
if f > 255 {
f = 255
}
return uint8(f + 0.5)
}
func clampInt(n, lo, hi int) int {
if n < lo {
return lo
}
if n > hi {
return hi
}
return n
}