topfans/backend/services/laserCompositor/compositor/finish.go
2026-06-03 22:19:22 +08:00

155 lines
3.9 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 compositor
import (
"image"
"math"
)
// DrawMetallicSheen 绘制对角金属反光梯度screen 混合)
// 复刻 laserBatchExport.js 中 drawMetallicSheen
func DrawMetallicSheen(dst *image.NRGBA, cfg GratingConfig) {
cw := dst.Bounds().Dx()
ch := dst.Bounds().Dy()
deg := cfg.SheenBandAngle
if deg == 0 {
deg = 135
}
// 与色带垂直方向
deg += 90
rad := deg * math.Pi / 180
diag := math.Sqrt(float64(cw*cw + ch*ch))
cx := float64(cw) / 2
cy := float64(ch) / 2
x0 := cx - math.Cos(rad)*diag/2
y0 := cy - math.Sin(rad)*diag/2
x1 := cx + math.Cos(rad)*diag/2
y1 := cy + math.Sin(rad)*diag/2
alpha := 0.22
for y := 0; y < ch; y++ {
for x := 0; x < cw; x++ {
// 计算投影位置 0-1
t := projectPointOnLine(float64(x), float64(y), x0, y0, x1, y1)
// 中间带高亮,两端透明
var brightness float64
if t > 0.30 && t < 0.70 {
// 高亮区域:余弦波
brightness = math.Cos((t-0.50)/0.20*math.Pi) * 0.5
if brightness < 0 {
brightness = 0
}
}
if brightness < 0.01 {
continue
}
applyScreenPixel(dst, x, y, 220.0/255.0, 230.0/255.0, 240.0/255.0, brightness*alpha)
}
}
}
func projectPointOnLine(px, py, lx0, ly0, lx1, ly1 float64) float64 {
dx := lx1 - lx0
dy := ly1 - ly0
lenSq := dx*dx + dy*dy
if lenSq < 0.001 {
return 0.5
}
t := ((px-lx0)*dx + (py-ly0)*dy) / lenSq
return clamp01(t)
}
// DrawFrostMatte 磨砂哑光层
// 复刻 laserBatchExport.js 中 drawFrostMatte
func DrawFrostMatte(dst *image.NRGBA, variantIndex int) {
cw := dst.Bounds().Dx()
ch := dst.Bounds().Dy()
frost := 0.18 // 固定强度
// 颗粒噪声
seed := variantIndex*1201 + 77
n := int(3500 + frost*7500)
for k := 0; k < n; k++ {
seed = (seed*9301 + 49297) % 233280
x := seed % cw
seed = (seed*9301 + 49297) % 233280
y := seed % ch
seed = (seed*9301 + 49297) % 233280
v := 190 + seed%65
a := 0.022 + float64(seed%55)/1000.0
idx := dst.PixOffset(x, y)
// soft-light 混合简化
factor := a * (0.35 + frost*0.25)
dst.Pix[idx] = uint8(clamp01(float64(dst.Pix[idx])/255.0 + float64(v)*factor/255.0) * 255)
dst.Pix[idx+1] = uint8(clamp01(float64(dst.Pix[idx+1])/255.0 + float64(v)*factor/255.0) * 255)
dst.Pix[idx+2] = uint8(clamp01(float64(dst.Pix[idx+2])/255.0 + float64(v+6)*factor/255.0) * 255)
}
}
// DrawFilmGrain 胶片颗粒
// 复刻 laserBatchExport.js 中 drawFilmGrain
func DrawFilmGrain(dst *image.NRGBA, variantIndex int) {
cw := dst.Bounds().Dx()
ch := dst.Bounds().Dy()
maxDots := 8
seed := variantIndex*313 + 17
for i := 0; i < maxDots; i++ {
seed = (seed*1103515245 + 12345) & 0x7fffffff
fx := float64(seed) / float64(0x7fffffff) * float64(cw)
seed = (seed*1103515245 + 12345) & 0x7fffffff
fy := float64(seed) / float64(0x7fffffff) * float64(ch)
x := int(fx)
y := int(fy)
if x < 0 || x >= cw || y < 0 || y >= ch {
continue
}
seed = (seed*1103515245 + 12345) & 0x7fffffff
radius := float64(seed)/float64(0x7fffffff)*1.5 + 0.3
alpha := 0.08 + float64(seed%12)/100.0
drawSoftDot(dst, x, y, int(radius), alpha)
}
}
func drawSoftDot(dst *image.NRGBA, cx, cy, radius int, alpha float64) {
for dy := -radius; dy <= radius; dy++ {
for dx := -radius; dx <= radius; dx++ {
if dx*dx+dy*dy > radius*radius {
continue
}
px, py := cx+dx, cy+dy
if px < 0 || px >= dst.Bounds().Dx() || py < 0 || py >= dst.Bounds().Dy() {
continue
}
dist := math.Sqrt(float64(dx*dx + dy*dy))
fade := 1 - dist/float64(radius)
idx := dst.PixOffset(px, py)
sc := alpha * fade
dst.Pix[idx] = uint8(clamp01(float64(dst.Pix[idx])/255.0 + 1.0*sc) * 255)
dst.Pix[idx+1] = uint8(clamp01(float64(dst.Pix[idx+1])/255.0 + 1.0*sc) * 255)
dst.Pix[idx+2] = uint8(clamp01(float64(dst.Pix[idx+2])/255.0 + 1.0*sc) * 255)
}
}
}
// DrawGratingFinish 光栅 finish颗粒 + 磨砂
func DrawGratingFinish(dst *image.NRGBA, variantIndex int) {
DrawFilmGrain(dst, variantIndex)
DrawFrostMatte(dst, variantIndex)
}