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(), }, } }