Properly implement morphing of shapes, gradients, styles, fix KERNINGRECORD / PlaceObject, avoid panic on invalid PlaceObject of font object

This commit is contained in:
DataHoarder 2023-11-26 10:38:47 +01:00
parent ce5902c4b8
commit c09f81ee4e
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
13 changed files with 406 additions and 374 deletions

View file

@ -6,10 +6,10 @@ import (
)
type KERNINGRECORD struct {
KerningCodeLeft8 uint8 `swfCond:"!Flag.WideCodes"`
KerningCodeLeft16 uint16 `swfCond:"Flag.WideCodes"`
KerningCodeRight8 uint8 `swfCond:"!Flag.WideCodes"`
KerningCodeRight16 uint16 `swfCond:"Flag.WideCodes"`
KerningCodeLeft8 uint8 `swfCondition:"!Flag.WideCodes"`
KerningCodeLeft16 uint16 `swfCondition:"Flag.WideCodes"`
KerningCodeRight8 uint8 `swfCondition:"!Flag.WideCodes"`
KerningCodeRight16 uint16 `swfCondition:"Flag.WideCodes"`
KerningAdjustment int16
}

View file

@ -2,7 +2,6 @@ package types
import (
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
math2 "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/records"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/shapes"
"math"
@ -35,36 +34,7 @@ func (d *MorphShapeDefinition) GetShapeList(p shapes.ObjectProperties) (list sha
var shape shapes.Shape
for _, recordPair := range shapes.IterateMorphShape(c1.Commands, c2.Commands) {
startEdge := recordPair[0]
endEdge := recordPair[1]
//No need to convert types!
aLineRecord, aIsLineRecord := startEdge.(*records.LineRecord)
aMoveRecord, aIsMoveRecord := startEdge.(*records.MoveRecord)
aQuadraticCurveRecord, aIsQuadraticCurveRecord := startEdge.(*records.QuadraticCurveRecord)
bLineRecord, bIsLineRecord := endEdge.(*records.LineRecord)
bMoveRecord, bIsMoveRecord := endEdge.(*records.MoveRecord)
bQuadraticCurveRecord, bIsQuadraticCurveRecord := endEdge.(*records.QuadraticCurveRecord)
if aIsLineRecord && bIsLineRecord {
shape.AddRecord(&records.LineRecord{
To: math2.LerpVector2(aLineRecord.To, bLineRecord.To, p.Ratio),
Start: math2.LerpVector2(aLineRecord.Start, bLineRecord.Start, p.Ratio),
})
} else if aIsQuadraticCurveRecord && bIsQuadraticCurveRecord {
shape.AddRecord(&records.QuadraticCurveRecord{
Control: math2.LerpVector2(aQuadraticCurveRecord.Control, bQuadraticCurveRecord.Control, p.Ratio),
Anchor: math2.LerpVector2(aQuadraticCurveRecord.Anchor, bQuadraticCurveRecord.Anchor, p.Ratio),
Start: math2.LerpVector2(aQuadraticCurveRecord.Start, bQuadraticCurveRecord.Start, p.Ratio),
})
} else if aIsMoveRecord && bIsMoveRecord {
shape.AddRecord(&records.MoveRecord{
To: math2.LerpVector2(aMoveRecord.To, bMoveRecord.To, p.Ratio),
Start: math2.LerpVector2(aMoveRecord.Start, bMoveRecord.Start, p.Ratio),
})
} else {
panic("unsupported")
}
shape.AddRecord(records.LerpRecord(recordPair[0], recordPair[1], p.Ratio))
}
//TODO: morph styles properly
@ -74,26 +44,9 @@ func (d *MorphShapeDefinition) GetShapeList(p shapes.ObjectProperties) (list sha
c2LineStyle, c2IsLineStyle := c2.Style.(*shapes.LineStyleRecord)
if c1IsFillStyle && c2IsFillStyle {
if c1Color, ok := c1FillStyle.Fill.(math2.Color); ok {
list = append(list, shapes.DrawPathFill(&shapes.FillStyleRecord{
Fill: math2.LerpColor(c1Color, c2FillStyle.Fill.(math2.Color), p.Ratio),
Border: c1FillStyle.Border,
}, &shape, nil))
} else if c1Gradient, ok := c1FillStyle.Fill.(shapes.Gradient); ok {
//TODO: proper gradients
list = append(list, shapes.DrawPathFill(&shapes.FillStyleRecord{
Fill: math2.LerpColor(c1Gradient.GetItems()[0].Color, c2FillStyle.Fill.(shapes.Gradient).GetItems()[0].Color, p.Ratio),
Border: c1FillStyle.Border,
}, &shape, nil))
} else {
panic("unsupported")
}
list = append(list, shapes.DrawPathFill(shapes.LerpFillStyle(c1FillStyle, c2FillStyle, p.Ratio), &shape, c1.Clip))
} else if c1IsLineStyle && c2IsLineStyle {
list = append(list, shapes.DrawPathStroke(&shapes.LineStyleRecord{
Width: math2.Lerp(c1LineStyle.Width, c2LineStyle.Width, p.Ratio),
Color: math2.LerpColor(c1LineStyle.Color, c2LineStyle.Color, p.Ratio),
//TODO: blur
}, &shape, nil))
list = append(list, shapes.DrawPathStroke(shapes.LerpLineStyle(c1LineStyle, c2LineStyle, p.Ratio), &shape, c1.Clip))
} else {
panic("unsupported")
}
@ -109,10 +62,7 @@ func (d *MorphShapeDefinition) GetSafeObject() shapes.ObjectDefinition {
func MorphShapeDefinitionFromSWF(collection shapes.ObjectCollection, shapeId uint16, startBounds, endBounds shapes.Rectangle[float64], startRecords, endRecords subtypes.SHAPERECORDS, fillStyles subtypes.MORPHFILLSTYLEARRAY, lineStyles subtypes.MORPHLINESTYLEARRAY) *MorphShapeDefinition {
startStyles, endStyles := shapes.StyleListFromSWFMorphItems(collection, fillStyles, lineStyles)
start := shapes.DrawPathListFromSWFMorph(collection, startRecords, endRecords, startStyles, false)
//TODO: morph styles properly
_ = endStyles
end := shapes.DrawPathListFromSWFMorph(collection, startRecords, endRecords, startStyles, true)
start, end := shapes.DrawPathListFromSWFMorph(collection, startRecords, endRecords, startStyles, endStyles)
if len(start) != len(end) {
panic("length does not match")

View file

@ -417,9 +417,7 @@ func (p *SWFTreeProcessor) process(actions ActionList) (tag swftag.Tag, newActio
case *swftag.PlaceObject:
var object shapes.ObjectDefinition
if vl := p.Layout.Get(node.Depth); vl != nil {
object = vl.Object
}
p.Objects.Get(node.CharacterId)
var transform *math.MatrixTransform
if t := math.MatrixTransformFromSWF(node.Matrix); !t.IsIdentity() {
@ -436,7 +434,7 @@ func (p *SWFTreeProcessor) process(actions ActionList) (tag swftag.Tag, newActio
case *swftag.PlaceObject2:
var object shapes.ObjectDefinition
if node.Flag.HasCharacter {
object = p.Objects[node.CharacterId]
object = p.Objects.Get(node.CharacterId)
} else if vl := p.Layout.Get(node.Depth); vl != nil {
object = vl.Object
}
@ -458,7 +456,7 @@ func (p *SWFTreeProcessor) process(actions ActionList) (tag swftag.Tag, newActio
//TODO: handle extra properties
var object shapes.ObjectDefinition
if node.Flag.HasCharacter {
object = p.Objects[node.CharacterId]
object = p.Objects.Get(node.CharacterId)
} else {
object = p.Layout.Get(node.Depth).Object
}

58
types/records/lerp.go Normal file
View file

@ -0,0 +1,58 @@
package records
import "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
func LerpRecord(start, end Record, ratio float64) Record {
if start.SameType(end) {
switch s := start.(type) {
case *LineRecord:
return &LineRecord{
To: math.LerpVector2(s.To, end.(*LineRecord).To, ratio),
Start: math.LerpVector2(s.Start, end.(*LineRecord).Start, ratio),
}
case *MoveRecord:
return &MoveRecord{
To: math.LerpVector2(s.To, end.(*MoveRecord).To, ratio),
Start: math.LerpVector2(s.Start, end.(*MoveRecord).Start, ratio),
}
case *QuadraticCurveRecord:
return &QuadraticCurveRecord{
Control: math.LerpVector2(s.Control, end.(*QuadraticCurveRecord).Control, ratio),
Anchor: math.LerpVector2(s.Anchor, end.(*QuadraticCurveRecord).Anchor, ratio),
Start: math.LerpVector2(s.Start, end.(*QuadraticCurveRecord).Start, ratio),
}
case *CubicCurveRecord:
return &CubicCurveRecord{
Control1: math.LerpVector2(s.Control1, end.(*CubicCurveRecord).Control1, ratio),
Control2: math.LerpVector2(s.Control2, end.(*CubicCurveRecord).Control2, ratio),
Anchor: math.LerpVector2(s.Anchor, end.(*CubicCurveRecord).Anchor, ratio),
Start: math.LerpVector2(s.Start, end.(*CubicCurveRecord).Start, ratio),
}
default:
panic("not supported")
}
} else {
startLine, startLineOk := start.(*LineRecord)
startQuadratic, startQuadraticOk := start.(*QuadraticCurveRecord)
endLine, endLineOk := end.(*LineRecord)
endQuadratic, endQuadraticOk := end.(*QuadraticCurveRecord)
if startLineOk && endQuadraticOk {
startQuadratic = QuadraticCurveFromLineRecord(startLine)
return &QuadraticCurveRecord{
Control: math.LerpVector2(startQuadratic.Control, endQuadratic.Control, ratio),
Anchor: math.LerpVector2(startQuadratic.Anchor, endQuadratic.Anchor, ratio),
Start: math.LerpVector2(startQuadratic.Start, endQuadratic.Start, ratio),
}
} else if startQuadraticOk && endLineOk {
endQuadratic = QuadraticCurveFromLineRecord(endLine)
return &QuadraticCurveRecord{
Control: math.LerpVector2(startQuadratic.Control, endQuadratic.Control, ratio),
Anchor: math.LerpVector2(startQuadratic.Anchor, endQuadratic.Anchor, ratio),
Start: math.LerpVector2(startQuadratic.Start, endQuadratic.Start, ratio),
}
} else {
panic("not supported")
}
}
}

32
types/shapes/Bitmap.go Normal file
View file

@ -0,0 +1,32 @@
package shapes
import (
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
math2 "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
)
type Bitmap struct {
List DrawPathList
Transform math2.MatrixTransform
}
func (b Bitmap) ApplyColorTransform(transform math2.ColorTransform) Fillable {
b2 := b
b2.List = b.List.ApplyColorTransform(transform).(DrawPathList)
b2.Transform = b.Transform
return b2
}
func (b Bitmap) Fill(shape *Shape) DrawPathList {
return b.List.Fill(shape)
}
func BitmapFillFromSWF(l DrawPathList, transform types.MATRIX) Bitmap {
// shape is already in pixel world, but matrix comes as twip
baseScale := math2.ScaleTransform(math2.NewVector2[float64](1./types.TwipFactor, 1./types.TwipFactor))
return Bitmap{
List: l,
Transform: math2.MatrixTransformFromSWF(transform).Multiply(baseScale),
}
}

View file

@ -2,7 +2,6 @@ package shapes
import (
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
)
@ -76,23 +75,13 @@ func (l DrawPathList) ApplyMatrixTransform(transform math.MatrixTransform, apply
return r
}
func DrawPathListFillFromSWF(l DrawPathList, transform types.MATRIX) DrawPathList {
// shape is already in pixel world, but matrix comes as twip
baseScale := math.ScaleTransform(math.NewVector2[float64](1./types.TwipFactor, 1./types.TwipFactor))
t := math.MatrixTransformFromSWF(transform).Multiply(baseScale)
return l.ApplyMatrixTransform(t, true)
}
func DrawPathListFromSWF(collection ObjectCollection, records subtypes.SHAPERECORDS, styles StyleList) DrawPathList {
converter := NewShapeConverter(collection, records, styles)
converter.Convert(false)
return converter.Commands
return NewShapeConverter(collection, styles).Convert(records)
}
func DrawPathListFromSWFMorph(collection ObjectCollection, startRecords, endRecords subtypes.SHAPERECORDS, styles StyleList, flip bool) DrawPathList {
converter := NewMorphShapeConverter(collection, startRecords, endRecords, styles)
converter.Convert(flip)
func DrawPathListFromSWFMorph(collection ObjectCollection, startRecords, endRecords subtypes.SHAPERECORDS, startStyles, endStyles StyleList) (DrawPathList, DrawPathList) {
converter := NewMorphShapeConverter(collection, startStyles)
startRecords, endRecords = converter.ConvertMorph(startRecords, endRecords)
return converter.Commands
return converter.Convert(startRecords), NewMorphShapeConverter(collection, endStyles).Convert(endRecords)
}

View file

@ -9,14 +9,50 @@ import (
"slices"
)
type Gradient interface {
Fillable
GetSpreadMode() swfsubtypes.GradientSpreadMode
GetInterpolationMode() swfsubtypes.GradientInterpolationMode
GetItems() []GradientItem
GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int) DrawPathList
GetMatrixTransform() math2.MatrixTransform
ApplyColorTransform(transform math2.ColorTransform) Fillable
type Gradient struct {
Records []GradientItem
Transform math2.MatrixTransform
SpreadMode swfsubtypes.GradientSpreadMode
InterpolationMode swfsubtypes.GradientInterpolationMode
Interpolation func(self Gradient, overlap, blur float64, gradientSlices int) DrawPathList
}
func (g Gradient) GetSpreadMode() swfsubtypes.GradientSpreadMode {
return g.SpreadMode
}
func (g Gradient) GetInterpolationMode() swfsubtypes.GradientInterpolationMode {
return g.InterpolationMode
}
func (g Gradient) GetItems() []GradientItem {
return g.Records
}
func (g Gradient) GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int) DrawPathList {
return g.Interpolation(g, overlap, blur, gradientSlices)
}
func (g Gradient) GetMatrixTransform() math2.MatrixTransform {
return g.Transform
}
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 {
return g.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices).Fill(shape)
}
type GradientItem struct {
@ -39,7 +75,7 @@ var GradientBounds = Rectangle[float64]{
const GradientRatioDivisor = math.MaxUint8
func LerpGradient(gradient Gradient, gradientSlices int) (result []GradientSlice) {
func InterpolateGradient(gradient Gradient, gradientSlices int) (result []GradientSlice) {
items := gradient.GetItems()
//TODO: spread modes

View file

@ -1,77 +1,12 @@
package shapes
import (
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/settings"
swfsubtypes "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
"slices"
)
type LinearGradient struct {
Colors []GradientItem
Transform math.MatrixTransform
SpreadMode swfsubtypes.GradientSpreadMode
InterpolationMode swfsubtypes.GradientInterpolationMode
}
func (g LinearGradient) GetSpreadMode() swfsubtypes.GradientSpreadMode {
return g.SpreadMode
}
func (g LinearGradient) GetInterpolationMode() swfsubtypes.GradientInterpolationMode {
return g.InterpolationMode
}
func (g LinearGradient) GetItems() []GradientItem {
return g.Colors
}
func (g LinearGradient) GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int) DrawPathList {
//items is max size 8 to 15 depending on SWF version
size := GradientBounds.Width()
//TODO spreadMode
var paths DrawPathList
for _, item := range LerpGradient(g, gradientSlices) {
paths = append(paths, DrawPathFill(
&FillStyleRecord{
Fill: item.Color,
Blur: blur,
},
NewShape(Rectangle[float64]{
TopLeft: math.NewVector2(GradientBounds.TopLeft.X+item.Start*size-overlap/2, GradientBounds.TopLeft.Y),
BottomRight: math.NewVector2(GradientBounds.TopLeft.X+item.End*size+overlap/2, GradientBounds.BottomRight.Y),
}.Draw()),
nil, //TODO: clip here instead of outside
).ApplyMatrixTransform(g.Transform, true))
}
return paths
}
func (g LinearGradient) GetMatrixTransform() math.MatrixTransform {
return g.Transform
}
func (g LinearGradient) ApplyColorTransform(transform math.ColorTransform) Fillable {
g2 := g
g2.Colors = slices.Clone(g2.Colors)
for i, g := range g2.Colors {
g2.Colors[i] = GradientItem{
Ratio: g.Ratio,
Color: transform.ApplyToColor(g.Color),
}
}
return &g2
}
func (g LinearGradient) Fill(shape *Shape) DrawPathList {
return g.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices).Fill(shape)
}
func LinearGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MATRIX, spreadMode swfsubtypes.GradientSpreadMode, interpolationMode swfsubtypes.GradientInterpolationMode) DrawPathList {
func LinearGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MATRIX, spreadMode swfsubtypes.GradientSpreadMode, interpolationMode swfsubtypes.GradientInterpolationMode) Gradient {
items := make([]GradientItem, 0, len(records))
for _, r := range records {
items = append(items, GradientItemFromSWF(r.Ratio, r.Color))
@ -79,11 +14,33 @@ func LinearGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MAT
//TODO: interpolationMode, spreadMode
return LinearGradient{
Colors: items,
return Gradient{
Records: items,
//TODO: do we need to scale this to pixel world from twips?
Transform: math.MatrixTransformFromSWF(transform),
SpreadMode: spreadMode,
InterpolationMode: interpolationMode,
}.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices)
Interpolation: func(self Gradient, overlap, blur float64, gradientSlices int) DrawPathList {
//items is max size 8 to 15 depending on SWF version
size := GradientBounds.Width()
//TODO spreadMode
var paths DrawPathList
for _, item := range InterpolateGradient(self, gradientSlices) {
paths = append(paths, DrawPathFill(
&FillStyleRecord{
Fill: item.Color,
Blur: blur,
},
NewShape(Rectangle[float64]{
TopLeft: math.NewVector2(GradientBounds.TopLeft.X+item.Start*size-overlap/2, GradientBounds.TopLeft.Y),
BottomRight: math.NewVector2(GradientBounds.TopLeft.X+item.End*size+overlap/2, GradientBounds.BottomRight.Y),
}.Draw()),
nil, //TODO: clip here instead of outside
).ApplyMatrixTransform(self.Transform, true))
}
return paths
},
}
}

View file

@ -1,85 +1,12 @@
package shapes
import (
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/settings"
swfsubtypes "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
"slices"
)
type RadialGradient struct {
Colors []GradientItem
Transform math.MatrixTransform
SpreadMode swfsubtypes.GradientSpreadMode
InterpolationMode swfsubtypes.GradientInterpolationMode
}
func (g RadialGradient) GetSpreadMode() swfsubtypes.GradientSpreadMode {
return g.SpreadMode
}
func (g RadialGradient) GetInterpolationMode() swfsubtypes.GradientInterpolationMode {
return g.InterpolationMode
}
func (g RadialGradient) GetItems() []GradientItem {
return g.Colors
}
func (g RadialGradient) GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int) DrawPathList {
//items is max size 8 to 15 depending on SWF version
size := GradientBounds.Width()
//TODO spreadMode
var paths DrawPathList
for _, item := range LerpGradient(g, gradientSlices) {
//Create concentric circles to cut out a shape
var shape Shape
radiusStart := (item.Start*size)/2 - overlap/4
radiusEnd := (item.End*size)/2 + overlap/4
start := NewCircle(math.NewVector2[float64](0, 0), radiusStart).Draw()
if radiusStart <= 0 {
start = nil
}
end := NewCircle(math.NewVector2[float64](0, 0), radiusEnd).Draw()
shape.Edges = append(shape.Edges, end...)
shape.Edges = append(shape.Edges, NewShape(start).Reverse().Edges...)
paths = append(paths, DrawPathFill(
&FillStyleRecord{
Fill: item.Color,
Blur: blur,
},
&shape,
nil, //TODO: clip here instead of outside
))
}
return paths.ApplyMatrixTransform(g.Transform, true)
}
func (g RadialGradient) GetMatrixTransform() math.MatrixTransform {
return g.Transform
}
func (g RadialGradient) ApplyColorTransform(transform math.ColorTransform) Fillable {
g2 := g
g2.Colors = slices.Clone(g2.Colors)
for i, g := range g2.Colors {
g2.Colors[i] = GradientItem{
Ratio: g.Ratio,
Color: transform.ApplyToColor(g.Color),
}
}
return &g2
}
func (g RadialGradient) Fill(shape *Shape) DrawPathList {
return g.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices).Fill(shape)
}
func RadialGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MATRIX, spreadMode swfsubtypes.GradientSpreadMode, interpolationMode swfsubtypes.GradientInterpolationMode) DrawPathList {
func RadialGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MATRIX, spreadMode swfsubtypes.GradientSpreadMode, interpolationMode swfsubtypes.GradientInterpolationMode) Gradient {
items := make([]GradientItem, 0, len(records))
for _, r := range records {
items = append(items, GradientItemFromSWF(r.Ratio, r.Color))
@ -87,11 +14,41 @@ func RadialGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MAT
//TODO: interpolationMode, spreadMode
return RadialGradient{
Colors: items,
return Gradient{
Records: items,
//TODO: do we need to scale this to pixel world from twips?
Transform: math.MatrixTransformFromSWF(transform),
SpreadMode: spreadMode,
InterpolationMode: interpolationMode,
}.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices)
Interpolation: func(self Gradient, overlap, blur float64, gradientSlices int) DrawPathList {
//items is max size 8 to 15 depending on SWF version
size := GradientBounds.Width()
//TODO spreadMode
var paths DrawPathList
for _, item := range InterpolateGradient(self, gradientSlices) {
//Create concentric circles to cut out a shape
var shape Shape
radiusStart := (item.Start*size)/2 - overlap/4
radiusEnd := (item.End*size)/2 + overlap/4
start := NewCircle(math.NewVector2[float64](0, 0), radiusStart).Draw()
if radiusStart <= 0 {
start = nil
}
end := NewCircle(math.NewVector2[float64](0, 0), radiusEnd).Draw()
shape.Edges = append(shape.Edges, end...)
shape.Edges = append(shape.Edges, NewShape(start).Reverse().Edges...)
paths = append(paths, DrawPathFill(
&FillStyleRecord{
Fill: item.Color,
Blur: blur,
},
&shape,
nil, //TODO: clip here instead of outside
))
}
return paths.ApplyMatrixTransform(self.Transform, true)
},
}
}

View file

@ -23,159 +23,139 @@ type ShapeConverter struct {
Commands DrawPathList
Finished bool
FirstElement, SecondElement subtypes.SHAPERECORDS
}
func NewShapeConverter(collection ObjectCollection, element subtypes.SHAPERECORDS, styles StyleList) *ShapeConverter {
func NewShapeConverter(collection ObjectCollection, styles StyleList) *ShapeConverter {
return &ShapeConverter{
Collection: collection,
Styles: styles,
Position: math.NewVector2[types.Twip](0, 0),
Fills: make(PendingPathMap),
Strokes: make(PendingPathMap),
FirstElement: element,
Collection: collection,
Styles: styles,
Position: math.NewVector2[types.Twip](0, 0),
Fills: make(PendingPathMap),
Strokes: make(PendingPathMap),
}
}
func NewMorphShapeConverter(collection ObjectCollection, firstElement, secondElement subtypes.SHAPERECORDS, styles StyleList) *ShapeConverter {
func NewMorphShapeConverter(collection ObjectCollection, styles StyleList) *ShapeConverter {
return &ShapeConverter{
Collection: collection,
Styles: styles,
Position: math.NewVector2[types.Twip](0, 0),
Fills: make(PendingPathMap),
Strokes: make(PendingPathMap),
FirstElement: firstElement,
SecondElement: secondElement,
Collection: collection,
Styles: styles,
Position: math.NewVector2[types.Twip](0, 0),
Fills: make(PendingPathMap),
Strokes: make(PendingPathMap),
}
}
func (c *ShapeConverter) Convert(flipElements bool) {
func (c *ShapeConverter) Convert(elements subtypes.SHAPERECORDS) DrawPathList {
if c.Finished {
return
return nil
}
firstElement := c.FirstElement
secondElement := c.SecondElement
for {
var a, b subtypes.SHAPERECORD
if len(secondElement) > 0 {
b = secondElement[0]
}
if len(firstElement) > 0 {
a = firstElement[0]
} else {
if b != nil {
panic("a finished, b did not")
}
break
}
if b == nil {
if flipElements {
panic("b finished, a did not")
}
c.HandleNode(a)
firstElement = firstElement[1:]
continue
}
if c.Finished {
panic("more paths after end")
}
if a.RecordType() == b.RecordType() {
switch a := a.(type) {
case *subtypes.StyleChangeRecord:
bCopy := *b.(*subtypes.StyleChangeRecord)
aCopy := *a
if aCopy.Flag.NewStyles {
bCopy.Flag.NewStyles = aCopy.Flag.NewStyles
bCopy.FillStyles = aCopy.FillStyles
bCopy.LineStyles = aCopy.LineStyles
}
if aCopy.Flag.LineStyle {
bCopy.Flag.LineStyle = aCopy.Flag.LineStyle
bCopy.LineStyle = aCopy.LineStyle
}
if aCopy.Flag.FillStyle0 {
bCopy.Flag.FillStyle0 = aCopy.Flag.FillStyle0
bCopy.FillStyle0 = aCopy.FillStyle0
}
if aCopy.Flag.FillStyle1 {
bCopy.Flag.FillStyle1 = aCopy.Flag.FillStyle1
bCopy.FillStyle1 = aCopy.FillStyle1
}
if !flipElements && !aCopy.Flag.MoveTo && bCopy.Flag.MoveTo {
aCopy.Flag.MoveTo = bCopy.Flag.MoveTo
aCopy.MoveDeltaX = c.Position.X
aCopy.MoveDeltaY = c.Position.Y
}
if flipElements && aCopy.Flag.MoveTo && !bCopy.Flag.MoveTo {
bCopy.Flag.MoveTo = aCopy.Flag.MoveTo
bCopy.MoveDeltaX = c.Position.X
bCopy.MoveDeltaY = c.Position.Y
}
if flipElements {
c.HandleNode(&bCopy)
} else {
c.HandleNode(&aCopy)
}
}
firstElement = firstElement[1:]
secondElement = secondElement[1:]
} else if aStyleChange, ok := a.(*subtypes.StyleChangeRecord); ok {
bCopy := *aStyleChange
if bCopy.Flag.MoveTo {
bCopy.MoveDeltaX = c.Position.X
bCopy.MoveDeltaY = c.Position.Y
}
if flipElements {
c.HandleNode(&bCopy)
} else {
c.HandleNode(aStyleChange)
}
firstElement = firstElement[1:]
} else if bStyleChange, ok := b.(*subtypes.StyleChangeRecord); ok {
aCopy := *bStyleChange
if aCopy.Flag.MoveTo {
aCopy.MoveDeltaX = c.Position.X
aCopy.MoveDeltaY = c.Position.Y
}
if flipElements {
c.HandleNode(bStyleChange)
} else {
c.HandleNode(&aCopy)
}
secondElement = secondElement[1:]
} else {
//Curve/line records can differ
if flipElements {
c.HandleNode(b)
} else {
c.HandleNode(a)
}
firstElement = firstElement[1:]
secondElement = secondElement[1:]
}
for _, e := range elements {
c.HandleNode(e)
}
c.FlushLayer()
return c.Commands
}
// ConvertMorph
// We step through both the start records and end records, interpolating edges pairwise.
// Fill style/line style changes should only appear in the start records.
// However, StyleChangeRecord move_to can appear it both start and end records,
// and not necessarily in matching pairs; therefore, we have to keep track of the pen position
// in case one side is missing a move_to; it will implicitly use the last pen position.
func (c *ShapeConverter) ConvertMorph(start, end subtypes.SHAPERECORDS) (startList, endList subtypes.SHAPERECORDS) {
if c.Finished {
return nil, nil
}
var startPos, endPos math.Vector2[types.Twip]
updatePos := func(v math.Vector2[types.Twip], s subtypes.SHAPERECORD) math.Vector2[types.Twip] {
switch s := s.(type) {
case *subtypes.StraightEdgeRecord:
v = v.AddVector(math.NewVector2(s.DeltaX, s.DeltaY))
case *subtypes.CurvedEdgeRecord:
v = v.AddVector(math.NewVector2(s.ControlDeltaX+s.AnchorDeltaX, s.ControlDeltaY+s.AnchorDeltaY))
case *subtypes.StyleChangeRecord:
if s.Flag.MoveTo {
v = math.NewVector2(s.MoveDeltaX, s.MoveDeltaY)
}
}
return v
}
for len(start) > 0 {
startPtr := start[0]
endPtr := end[0]
if startPtr.RecordType() == endPtr.RecordType() {
switch s := startPtr.(type) {
case *subtypes.StyleChangeRecord:
if s.Flag.MoveTo {
startPos = math.NewVector2(s.MoveDeltaX, s.MoveDeltaY)
}
e := endPtr.(*subtypes.StyleChangeRecord)
endRecord := *s
endRecord.Flag.MoveTo = e.Flag.MoveTo
endRecord.MoveDeltaX = e.MoveDeltaX
endRecord.MoveDeltaY = e.MoveDeltaY
if e.Flag.MoveTo {
endPos = math.NewVector2(e.MoveDeltaX, e.MoveDeltaY)
}
startList = append(startList, s)
endList = append(endList, &endRecord)
start = start[1:]
end = end[1:]
default:
startList = append(startList, startPtr)
endList = append(endList, endPtr)
start = start[1:]
end = end[1:]
}
} else {
if s, ok := startPtr.(*subtypes.StyleChangeRecord); ok {
endRecord := *s
if s.Flag.MoveTo {
startPos = math.NewVector2(s.MoveDeltaX, s.MoveDeltaY)
endRecord.MoveDeltaX = endPos.X
endRecord.MoveDeltaY = endPos.Y
}
startList = append(startList, startPtr)
endList = append(endList, &endRecord)
startPos = updatePos(startPos, startPtr)
start = start[1:]
} else if e, ok := endPtr.(*subtypes.StyleChangeRecord); ok {
startRecord := *e
if e.Flag.MoveTo {
endPos = math.NewVector2(e.MoveDeltaX, e.MoveDeltaY)
startRecord.MoveDeltaX = startPos.X
startRecord.MoveDeltaY = startPos.Y
}
startList = append(startList, &startRecord)
endList = append(endList, endPtr)
endPos = updatePos(endPos, startPtr)
end = end[1:]
} else {
startList = append(startList, startPtr)
endList = append(endList, endPtr)
startPos = updatePos(startPos, startPtr)
endPos = updatePos(endPos, endPtr)
start = start[1:]
end = end[1:]
}
}
}
if len(end) > 0 {
panic("did not complete")
}
return
}
func (c *ShapeConverter) HandleNode(node subtypes.SHAPERECORD) {

View file

@ -140,7 +140,7 @@ func FillStyleRecordFromSWF(collection ObjectCollection, fillType swfsubtypes.Fi
blurFactor := 1.0
//TODO: extend color
return &FillStyleRecord{
Fill: DrawPathListFillFromSWF(bitmap.GetShapeList(ObjectProperties{}).ApplyFunction(func(p DrawPath) DrawPath {
Fill: BitmapFillFromSWF(bitmap.GetShapeList(ObjectProperties{}).ApplyFunction(func(p DrawPath) DrawPath {
if fillStyle, ok := p.Style.(*FillStyleRecord); ok {
return DrawPathFill(&FillStyleRecord{
Fill: fillStyle.Fill,
@ -176,7 +176,7 @@ func FillStyleRecordFromSWF(collection ObjectCollection, fillType swfsubtypes.Fi
}
//TODO: extend color
return &FillStyleRecord{
Fill: DrawPathListFillFromSWF(bitmap.GetShapeList(ObjectProperties{}), bitmapMatrix),
Fill: BitmapFillFromSWF(bitmap.GetShapeList(ObjectProperties{}), bitmapMatrix),
}
//TODO other styles
}

74
types/shapes/lerp.go Normal file
View file

@ -0,0 +1,74 @@
package shapes
import (
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
)
func LerpFillStyle(start, end *FillStyleRecord, ratio float64) *FillStyleRecord {
if start == nil || end == nil {
return nil
}
return &FillStyleRecord{
Fill: LerpFillable(start.Fill, end.Fill, ratio),
Border: LerpLineStyle(start.Border, end.Border, ratio),
Blur: math.Lerp(start.Blur, end.Blur, ratio),
}
}
func LerpLineStyle(start, end *LineStyleRecord, ratio float64) *LineStyleRecord {
if start == nil || end == nil {
return nil
}
return &LineStyleRecord{
Width: math.Lerp(start.Width, end.Width, ratio),
Color: math.LerpColor(start.Color, end.Color, ratio),
Blur: math.Lerp(start.Blur, end.Blur, ratio),
}
}
func LerpFillable(start, end any, ratio float64) any {
switch s := start.(type) {
case math.Color:
return math.LerpColor(s, end.(math.Color), ratio)
case Bitmap:
return Bitmap{
List: s.List,
Transform: math.LerpMatrix(s.Transform, end.(Bitmap).Transform, ratio),
}
case Gradient:
return LerpGradient(s, end.(Gradient), ratio)
case DrawPathList:
return start
//TODO: focal gradient
default:
panic("not supported")
}
}
func LerpGradient(start, end Gradient, ratio float64) Gradient {
startRecords := start.GetItems()
endRecords := end.GetItems()
if len(startRecords) != len(endRecords) {
panic("not supported")
}
records := make([]GradientItem, 0, len(startRecords))
for i := range startRecords {
records = append(records, GradientItem{
Ratio: math.Lerp(startRecords[i].Ratio, endRecords[i].Ratio, ratio),
Color: math.LerpColor(startRecords[i].Color, endRecords[i].Color, ratio),
})
}
return Gradient{
Records: records,
Transform: math.LerpMatrix(start.Transform, end.Transform, ratio),
SpreadMode: start.SpreadMode,
InterpolationMode: start.InterpolationMode,
Interpolation: start.Interpolation,
}
}

View file

@ -1,6 +1,7 @@
package types
import (
"fmt"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
@ -23,7 +24,7 @@ func (d *FontDefinition) GetObjectId() uint16 {
}
func (d *FontDefinition) GetShapeList(p shapes.ObjectProperties) (list shapes.DrawPathList) {
panic("todo")
fmt.Printf("something is trying to place a Font as a character!!!!!!")
return nil
}