155 lines
3.9 KiB
Go
155 lines
3.9 KiB
Go
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)
|
||
}
|