166 lines
4.7 KiB
Go
166 lines
4.7 KiB
Go
package shapes
|
|
|
|
import (
|
|
swfsubtypes "git.gammaspectra.live/WeebDataHoarder/swf-go/subtypes"
|
|
swftypes "git.gammaspectra.live/WeebDataHoarder/swf-go/types"
|
|
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/settings"
|
|
math2 "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
|
"math"
|
|
"slices"
|
|
)
|
|
|
|
type Gradient struct {
|
|
Records []GradientItem
|
|
|
|
Transform math2.MatrixTransform
|
|
SpreadMode swfsubtypes.GradientSpreadMode
|
|
InterpolationMode swfsubtypes.GradientInterpolationMode
|
|
|
|
Interpolation func(self Gradient, overlap, blur float64, gradientSlices int, bb Rectangle[float64]) DrawPathList
|
|
}
|
|
|
|
func (g Gradient) GetItems() []GradientItem {
|
|
return g.Records
|
|
}
|
|
|
|
func (g Gradient) GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int, bb Rectangle[float64]) DrawPathList {
|
|
return g.Interpolation(g, overlap, blur, gradientSlices, bb).ApplyMatrixTransform(g.Transform, true).(DrawPathList)
|
|
}
|
|
|
|
func (g Gradient) ApplyMatrixTransform(transform math2.MatrixTransform, applyTranslation bool) Fillable {
|
|
if transform.IsIdentity() {
|
|
return g
|
|
}
|
|
g2 := g
|
|
if !applyTranslation {
|
|
panic("not supported")
|
|
}
|
|
g2.Transform = transform.Combine(g2.Transform)
|
|
return g2
|
|
}
|
|
|
|
func (g Gradient) ApplyColorTransform(transform math2.ColorTransform) Fillable {
|
|
g2 := g
|
|
g2.Records = slices.Clone(g2.Records)
|
|
for i, g := range g2.Records {
|
|
g2.Records[i] = GradientItem{
|
|
Ratio: g.Ratio,
|
|
Color: transform.ApplyToColor(g.Color),
|
|
}
|
|
}
|
|
return g2
|
|
}
|
|
|
|
func (g Gradient) Fill(shape Shape) DrawPathList {
|
|
bb := Rectangle[float64]{}
|
|
if inverse := g.Transform.Inverse(); inverse != nil {
|
|
//calculate inverse of shape to get bounds gradient has to match to
|
|
bb = shape.ApplyMatrixTransform(*inverse, true).BoundingBox()
|
|
}
|
|
return g.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices, bb).Fill(shape)
|
|
}
|
|
|
|
type GradientItem struct {
|
|
Ratio uint8
|
|
Color math2.Color
|
|
}
|
|
|
|
type GradientSlice struct {
|
|
Start, End float64
|
|
Color math2.Color
|
|
}
|
|
|
|
const GradientBoundsMin swftypes.Twip = math.MinInt16 / 2
|
|
const GradientBoundsMax = -GradientBoundsMin
|
|
|
|
var GradientBounds = Rectangle[float64]{
|
|
TopLeft: math2.NewVector2[float64](GradientBoundsMin.Float64(), GradientBoundsMin.Float64()),
|
|
BottomRight: math2.NewVector2[float64](GradientBoundsMax.Float64(), GradientBoundsMax.Float64()),
|
|
}
|
|
|
|
const GradientRatioDivisor = math.MaxUint8
|
|
|
|
func (g Gradient) Interpolate(gradientSlices int) (result []GradientSlice) {
|
|
items := g.GetItems()
|
|
//TODO: spread modes
|
|
|
|
interpolationMode := g.InterpolationMode
|
|
|
|
first := items[0]
|
|
last := items[len(items)-1]
|
|
if first.Ratio != 0 {
|
|
first = GradientItem{
|
|
Ratio: 0,
|
|
Color: first.Color,
|
|
}
|
|
items = slices.Insert(items, 0, first)
|
|
}
|
|
|
|
if last.Ratio != GradientRatioDivisor {
|
|
last = GradientItem{
|
|
Ratio: GradientRatioDivisor,
|
|
Color: last.Color,
|
|
}
|
|
items = append(items, last)
|
|
}
|
|
|
|
prevItem := items[0]
|
|
for _, item := range items[1:] {
|
|
prevColor := prevItem.Color
|
|
currentColor := item.Color
|
|
if interpolationMode == swfsubtypes.GradientInterpolationLinearRGB {
|
|
prevColor = prevColor.ToLinearRGB()
|
|
currentColor = currentColor.ToLinearRGB()
|
|
}
|
|
|
|
maxColorDistance := max(math.Abs(float64(prevColor.R)-float64(currentColor.R)), math.Abs(float64(prevColor.G)-float64(currentColor.G)), math.Abs(float64(prevColor.B)-float64(currentColor.B)), math.Abs(float64(prevColor.Alpha)-float64(currentColor.Alpha)))
|
|
|
|
prevPosition := float64(prevItem.Ratio) / GradientRatioDivisor
|
|
currentPosition := float64(item.Ratio) / GradientRatioDivisor
|
|
distance := math.Abs(currentPosition - prevPosition)
|
|
|
|
var partitions int
|
|
if maxColorDistance < math.SmallestNonzeroFloat64 {
|
|
partitions = 1
|
|
} else if gradientSlices == settings.GradientAutoSlices {
|
|
//TODO: better heuristic for change including distance in ratio
|
|
partitions = max(1, int(math.Ceil(min(GradientRatioDivisor/float64(len(items)+1), max(1, math.Ceil(maxColorDistance))))))
|
|
} else {
|
|
partitions = max(1, int(math.Ceil(distance*float64(gradientSlices))))
|
|
}
|
|
|
|
fromPos := prevPosition
|
|
for i := 1; i <= partitions; i++ {
|
|
ratio := float64(i) / float64(partitions)
|
|
color := math2.LerpColor(prevColor, currentColor, ratio)
|
|
|
|
if interpolationMode == swfsubtypes.GradientInterpolationLinearRGB {
|
|
color = color.ToSRGB()
|
|
}
|
|
|
|
toPos := math2.Lerp(prevPosition, currentPosition, ratio)
|
|
|
|
result = append(result, GradientSlice{
|
|
Start: fromPos,
|
|
End: toPos,
|
|
Color: color,
|
|
})
|
|
fromPos = toPos
|
|
}
|
|
prevItem = item
|
|
}
|
|
return result
|
|
}
|
|
|
|
func GradientItemFromSWF(ratio uint8, color swftypes.Color) GradientItem {
|
|
return GradientItem{
|
|
Ratio: ratio,
|
|
Color: math2.Color{
|
|
R: color.R(),
|
|
G: color.G(),
|
|
B: color.B(),
|
|
Alpha: color.A(),
|
|
},
|
|
}
|
|
}
|