From 22c2fca7be650e025b8262b0222c39ecc55ab817 Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Thu, 3 Aug 2023 01:48:26 +0200 Subject: [PATCH] WiP: gradients and others --- composer.lock | 12 ++--- image2ass.php | 16 +++---- src/Circle.php | 2 +- src/Color.php | 24 +++++++++- src/{Oval.php => Ellipse.php} | 6 +-- src/FillStyleRecord.php | 5 ++- src/Gradient.php | 19 ++++++++ src/GradientSlice.php | 15 +++++++ src/LinearGradient.php | 42 ++++++++++++++++- src/MorphShapeDefinition.php | 28 +++--------- src/RadialGradient.php | 42 ++++++++++++++++- src/SWFProcessor.php | 9 ++++ src/SWFTreeProcessor.php | 15 +++++++ src/Shape.php | 31 ------------- src/SpriteDefinition.php | 3 +- src/Utils.php | 85 +++++++++++++++++++++++++++++++++++ src/ass/ASSLine.php | 6 ++- src/ass/ASSRenderer.php | 44 ++++++++++++++++-- src/ass/borderTag.php | 3 ++ src/ass/containerTag.php | 2 + src/ass/fillColorTag.php | 2 + src/ass/lineColorTag.php | 3 ++ swf2ass.php | 1 + 23 files changed, 333 insertions(+), 82 deletions(-) rename src/{Oval.php => Ellipse.php} (88%) create mode 100644 src/GradientSlice.php diff --git a/composer.lock b/composer.lock index 4a63c57..6c3a5ae 100644 --- a/composer.lock +++ b/composer.lock @@ -51,16 +51,16 @@ }, { "name": "markrogoyski/math-php", - "version": "v2.5.0", + "version": "v2.6.0", "source": { "type": "git", "url": "https://github.com/markrogoyski/math-php.git", - "reference": "ca71ca97dc136e7bb9e9e1fe05782f343a5692d4" + "reference": "85d7d7fe205a6df2b20f956720e25341f3b1462a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/markrogoyski/math-php/zipball/ca71ca97dc136e7bb9e9e1fe05782f343a5692d4", - "reference": "ca71ca97dc136e7bb9e9e1fe05782f343a5692d4", + "url": "https://api.github.com/repos/markrogoyski/math-php/zipball/85d7d7fe205a6df2b20f956720e25341f3b1462a", + "reference": "85d7d7fe205a6df2b20f956720e25341f3b1462a", "shasum": "" }, "require": { @@ -120,9 +120,9 @@ ], "support": { "issues": "https://github.com/markrogoyski/math-php/issues", - "source": "https://github.com/markrogoyski/math-php/tree/v2.5.0" + "source": "https://github.com/markrogoyski/math-php/tree/v2.6.0" }, - "time": "2021-11-22T05:14:07+00:00" + "time": "2022-04-10T05:15:37+00:00" } ], "packages-dev": [], diff --git a/image2ass.php b/image2ass.php index 016f815..e8de38d 100644 --- a/image2ass.php +++ b/image2ass.php @@ -15,21 +15,21 @@ function outputFrame($frame, $endFrame, $frameDurationMs) { $shape = $path->commands; $line->tags = [ - new \swf2ass\ass\borderTag(0), + new \swf2ass\ass\borderTag(new \swf2ass\Vector2(0, 0)), new \swf2ass\ass\shadowTag(0), \swf2ass\ass\fillColorTag::fromStyleRecord($path->style) ]; - if($shape->getRecords()[0] instanceof \swf2ass\MoveRecord){ + /*if($shape->getRecords()[0] instanceof \swf2ass\MoveRecord){ $shape = (new \swf2ass\MatrixTransform(null, null, $shape->getRecords()[0]->to->multiply(-1)))->applyToShape($shape); - } - $line->tags[] = new \swf2ass\ass\positionTag($shape->getRecords()[0]->getStart()->divide(-\swf2ass\Constants::TWIP_SIZE), $shape->getRecords()[0]->getStart()->divide(-\swf2ass\Constants::TWIP_SIZE), $endFrame, $endFrame); + }*/ + $line->tags[] = new \swf2ass\ass\positionTag(new \swf2ass\Vector2(0, 0), new \swf2ass\Vector2(0, 0), 1, 1); $line->tags[] = new \swf2ass\ass\drawTag($shape, 1); echo $line->encode($frameDurationMs) . PHP_EOL; } } -$fps = 30000 / 1001;//30; -$colorN = 64; +$fps = 30;//30000 / 1001;//30; +$colorN = 2; $pframeColorDistance = 16; if (is_dir($argv[1])) { @@ -43,7 +43,7 @@ sort($frames); $frameBuffer = null; $quantizedFrameBuffer = $frameBuffer; -$dynamicPalette = true; +$dynamicPalette = false; $palette = []; for ($i = 0; $i < $colorN; ++$i) { $palette[] = new \swf2ass\Color((int)round($i * (255 / ($colorN - 1))), (int)round($i * (255 / ($colorN - 1))), (int)round($i * (255 / ($colorN - 1)))); @@ -51,7 +51,7 @@ for ($i = 0; $i < $colorN; ++$i) { $frameNumber = 1; -$endFrame = 100; +$endFrame = 6522; $lastFrameNumber = null; $currentFrames = []; diff --git a/src/Circle.php b/src/Circle.php index c85c06f..9f6d99d 100644 --- a/src/Circle.php +++ b/src/Circle.php @@ -3,7 +3,7 @@ namespace swf2ass; -class Circle extends Oval { +class Circle extends Ellipse { public function __construct(Vector2 $center, float $radius) { parent::__construct($center, new Vector2($radius, $radius)); } diff --git a/src/Color.php b/src/Color.php index ea38d98..8fdee77 100644 --- a/src/Color.php +++ b/src/Color.php @@ -27,7 +27,29 @@ class Color { return new Color($element["red"], $element["green"], $element["blue"], $element["alpha"] ?? 255); } - public function toString($noAlpha = false){ + public function __toString() : string{ + return $this->toString(false); + } + + public function toString(bool $noAlpha = false) : string{ return $noAlpha ? "rgb({$this->r},{$this->g},{$this->b})" : "rgba({$this->r},{$this->g},{$this->b},{$this->alpha})"; } + + public function toLinearRGB() : Color{ + return new Color( + pow($this->r / 255, 2.2) * 255, + pow($this->g / 255, 2.2) * 255, + pow($this->b / 255, 2.2) * 255, + pow($this->alpha / 255, 2.2) * 255, + ); + } + + public function tosRGB() : Color{ + return new Color( + pow($this->r / 255, 0.4545) * 255, + pow($this->g / 255, 0.4545) * 255, + pow($this->b / 255, 0.4545) * 255, + pow($this->alpha / 255, 0.4545) * 255, + ); + } } \ No newline at end of file diff --git a/src/Oval.php b/src/Ellipse.php similarity index 88% rename from src/Oval.php rename to src/Ellipse.php index eb69500..f05b408 100644 --- a/src/Oval.php +++ b/src/Ellipse.php @@ -3,7 +3,7 @@ namespace swf2ass; -class Oval implements ComplexShape { +class Ellipse implements ComplexShape { protected const c = 0.55228474983; // (4/3) * (sqrt(2) - 1) //protected const c = 0.551915024494; // https://spencermortensen.com/articles/bezier-circle/ @@ -28,9 +28,9 @@ class Oval implements ComplexShape { public function draw(): array { return [ $this->getQuarter(new Vector2(-$this->radius->x, $this->radius->y)), - $this->getQuarter($this->radius), + $this->getQuarter($this->radius)->reverse(), //Reverse so paths connect $this->getQuarter(new Vector2($this->radius->x, -$this->radius->y)), - $this->getQuarter(new Vector2(-$this->radius->x, -$this->radius->y)), + $this->getQuarter(new Vector2(-$this->radius->x, -$this->radius->y))->reverse(), ]; } } diff --git a/src/FillStyleRecord.php b/src/FillStyleRecord.php index 721e08b..1aa5bbf 100644 --- a/src/FillStyleRecord.php +++ b/src/FillStyleRecord.php @@ -7,7 +7,10 @@ class FillStyleRecord implements StyleRecord { /** @var Gradient|Color */ public $fill; - public function __construct($fill) { + public ?LineStyleRecord $border = null; + + public function __construct($fill, ?LineStyleRecord $border = null) { $this->fill = $fill; + $this->border = $border; } } \ No newline at end of file diff --git a/src/Gradient.php b/src/Gradient.php index 91db468..ad51234 100644 --- a/src/Gradient.php +++ b/src/Gradient.php @@ -3,12 +3,31 @@ namespace swf2ass; interface Gradient { + const AUTO_SLICES = -1; + + const SPREAD_PAD = 0; + const SPREAD_REFLECT = 1; + const SPREAD_REPEAT = 2; + const SPREAD_RESERVED = 3; + //TODO + + const INTERPOLATE_NORMAL_RGB = 0; + const INTERPOLATE_LINEAR_RGB = 1; + const INTERPOLATE_RESERVED1 = 2; + const INTERPOLATE_RESERVED2 = 3; + + public function getSpreadMode() : int; + + public function getInterpolationMode() : int; /** * @return GradientItem[] */ public function getItems(): array; + + public function getInterpolatedDrawPaths(int $overlap = 0, int $slices = self::AUTO_SLICES): DrawPathList; + public function getMatrixTransform(): MatrixTransform; public function applyColorTransform(ColorTransform $transform): Gradient; diff --git a/src/GradientSlice.php b/src/GradientSlice.php new file mode 100644 index 0000000..216f929 --- /dev/null +++ b/src/GradientSlice.php @@ -0,0 +1,15 @@ +color = $color; + $this->startRatio = $startRatio; + $this->endRatio = $endRatio; + } +} \ No newline at end of file diff --git a/src/LinearGradient.php b/src/LinearGradient.php index 6ae5289..aa29f29 100644 --- a/src/LinearGradient.php +++ b/src/LinearGradient.php @@ -3,23 +3,61 @@ namespace swf2ass; class LinearGradient implements Gradient { + + + /** @var GradientItem[] */ public array $colors; public MatrixTransform $transform; + public int $spreadMode; + public int $interpolationMode; + /** * @param GradientItem[] $colors * @param MatrixTransform $transform */ - public function __construct(array $colors, MatrixTransform $transform) { + public function __construct(array $colors, MatrixTransform $transform, int $spreadMode, int $interpolationMode) { $this->colors = $colors; $this->transform = $transform; + $this->spreadMode = $spreadMode; + $this->interpolationMode = $interpolationMode; } public function getItems(): array { return $this->colors; } + public function getSpreadMode() : int{ + return $this->spreadMode; + } + + public function getInterpolationMode() : int{ + return $this->interpolationMode; + } + + + public function getInterpolatedDrawPaths(int $overlap = 0, int $slices = self::AUTO_SLICES): DrawPathList{ + //items is max size 8 to 15 depending on SWF version + $min = -16384; + $max = 16384; + $diff = $max - $min; + + //TODO spreadMode + + $paths = new DrawPathList(); + + foreach (Utils::lerpGradient($this, $slices) as $item){ + $paths->commands[] = DrawPath::fill( + new FillStyleRecord($item->color), + $this->getMatrixTransform()->applyToShape(new Shape((new Rectangle( + new Vector2($min + ($item->startRatio / 255) * $diff - $overlap / 2, $min), + new Vector2($min + ($item->endRatio / 255) * $diff + $overlap / 2, $max) + ))->draw()))); + } + + return $paths; + } public function getMatrixTransform(): MatrixTransform { return $this->transform; @@ -33,7 +71,7 @@ class LinearGradient implements Gradient { //TODO: interpolationMode, spreadMode - return new LinearGradient($colors, $transform); + return new LinearGradient($colors, $transform, $element["spreadMode"], $element["interpolationMode"]); } public function applyColorTransform(ColorTransform $transform): Gradient{ diff --git a/src/MorphShapeDefinition.php b/src/MorphShapeDefinition.php index f2dbc8c..e47ed31 100644 --- a/src/MorphShapeDefinition.php +++ b/src/MorphShapeDefinition.php @@ -123,11 +123,11 @@ class MorphShapeDefinition implements ObjectDefinition { //No need to convert types! if($r1 instanceof LineRecord and $r2 instanceof LineRecord){ - $shape->addRecord(new LineRecord(self::lerpVector2($r1->to, $r2->to, $ratio), self::lerpVector2($r1->start, $r2->start, $ratio))); + $shape->addRecord(new LineRecord(Utils::lerpVector2($r1->to, $r2->to, $ratio), Utils::lerpVector2($r1->start, $r2->start, $ratio))); }else if($r1 instanceof QuadraticCurveRecord and $r2 instanceof QuadraticCurveRecord){ - $shape->addRecord(new QuadraticCurveRecord(self::lerpVector2($r1->control, $r2->control, $ratio), self::lerpVector2($r1->anchor, $r2->anchor, $ratio), self::lerpVector2($r1->start, $r2->start, $ratio))); + $shape->addRecord(new QuadraticCurveRecord(Utils::lerpVector2($r1->control, $r2->control, $ratio), Utils::lerpVector2($r1->anchor, $r2->anchor, $ratio), Utils::lerpVector2($r1->start, $r2->start, $ratio))); }else if($r1 instanceof MoveRecord and $r2 instanceof MoveRecord){ - $shape->addRecord(new MoveRecord(self::lerpVector2($r1->to, $r2->to, $ratio), self::lerpVector2($r1->start, $r2->start, $ratio))); + $shape->addRecord(new MoveRecord(Utils::lerpVector2($r1->to, $r2->to, $ratio), Utils::lerpVector2($r1->start, $r2->start, $ratio))); }else{ var_dump($records1); var_dump($records2); @@ -138,17 +138,17 @@ class MorphShapeDefinition implements ObjectDefinition { //TODO: morph styles properly if($c1->style instanceof FillStyleRecord and $c2->style instanceof FillStyleRecord){ if($c1->style->fill instanceof Color){ - $drawPathList->commands[] = DrawPath::fill(new FillStyleRecord(self::lerpColor($c1->style->fill, $c2->style->fill, $ratio)), $shape); + $drawPathList->commands[] = DrawPath::fill(new FillStyleRecord(Utils::lerpColor($c1->style->fill, $c2->style->fill, $ratio)), $shape); }else if($c1->style->fill instanceof Gradient){ //TODO: proper gradients - $drawPathList->commands[] = DrawPath::fill(new FillStyleRecord(self::lerpColor($c1->style->fill->getItems()[0]->color, $c2->style->fill->getItems()[0]->color, $ratio)), $shape); + $drawPathList->commands[] = DrawPath::fill(new FillStyleRecord(Utils::lerpColor($c1->style->fill->getItems()[0]->color, $c2->style->fill->getItems()[0]->color, $ratio)), $shape); }else{ var_dump($c1->style); var_dump($c2->style); throw new \Exception(); } }else if($c1->style instanceof LineStyleRecord and $c2->style instanceof LineStyleRecord){ - $drawPathList->commands[] = DrawPath::stroke(new LineStyleRecord(self::lerpInteger($c1->style->width, $c2->style->width, $ratio), self::lerpColor($c1->style->color, $c2->style->color, $ratio)), $shape); + $drawPathList->commands[] = DrawPath::stroke(new LineStyleRecord(Utils::lerpInteger($c1->style->width, $c2->style->width, $ratio), Utils::lerpColor($c1->style->color, $c2->style->color, $ratio)), $shape); }else{ var_dump($c1->style); var_dump($c2->style); @@ -159,22 +159,6 @@ class MorphShapeDefinition implements ObjectDefinition { return $drawPathList; } - private static function lerpInteger(int $start, int $end, float $ratio): int { - return $start + ($end - $start) * $ratio; - } - - private static function lerpFloat(float $start, float $end, float $ratio): float { - return $start + ($end - $start) * $ratio; - } - - private static function lerpVector2(Vector2 $start, Vector2 $end, float $ratio): Vector2 { - return $start->add($end->sub($start)->multiply($ratio)); - } - - private static function lerpColor(Color $start, Color $end, float $ratio): Color { - return new Color(self::lerpInteger($start->r, $end->r, $ratio), self::lerpInteger($start->g, $end->g, $ratio), self::lerpInteger($start->b, $end->b, $ratio), self::lerpInteger($start->alpha, $end->alpha, $ratio)); - } - static function fromArray(array $element): MorphShapeDefinition { $styles = MorphStyleList::fromArray($element); diff --git a/src/RadialGradient.php b/src/RadialGradient.php index a6dac8a..20e1f0c 100644 --- a/src/RadialGradient.php +++ b/src/RadialGradient.php @@ -8,19 +8,57 @@ class RadialGradient implements Gradient { public MatrixTransform $transform; + public int $spreadMode; + public int $interpolationMode; + /** * @param GradientItem[] $colors * @param MatrixTransform $transform */ - public function __construct(array $colors, MatrixTransform $transform) { + public function __construct(array $colors, MatrixTransform $transform, int $spreadMode, int $interpolationMode) { $this->colors = $colors; $this->transform = $transform; + $this->spreadMode = $spreadMode; + $this->interpolationMode = $interpolationMode; } public function getItems(): array { return $this->colors; } + public function getSpreadMode() : int{ + return $this->spreadMode; + } + + public function getInterpolationMode() : int{ + return $this->interpolationMode; + } + + + public function getInterpolatedDrawPaths(int $overlap = 0, int $slices = self::AUTO_SLICES): DrawPathList{ + //items is max size 8 to 15 depending on SWF version + $min = -16384; + $max = 16384; + $diff = $max - $min; + + //TODO spreadMode + + $paths = new DrawPathList(); + + foreach (Utils::lerpGradient($this, $slices) as $item){ + $shape = new Shape(); + //Create concentric circles to cut out a shape + $shape = $shape->merge(new Shape((new Circle(new Vector2(0, 0), (($item->endRatio / 255) * $diff) / 2 + $overlap / 4))->draw())); + $shape = $shape->merge(new Shape((new Circle(new Vector2(0, 0), (($item->startRatio / 255) * $diff) / 2 - $overlap / 4))->draw())); + + $paths->commands[] = DrawPath::fill( + new FillStyleRecord($item->color), + $this->getMatrixTransform()->applyToShape($shape)); + } + + return $paths; + } + public function getMatrixTransform(): MatrixTransform { return $this->transform; } @@ -33,7 +71,7 @@ class RadialGradient implements Gradient { //TODO: interpolationMode, spreadMode - return new RadialGradient($colors, $transform); + return new RadialGradient($colors, $transform, $element["spreadMode"], $element["interpolationMode"]); } public function applyColorTransform(ColorTransform $transform): Gradient{ diff --git a/src/SWFProcessor.php b/src/SWFProcessor.php index 987ca29..dfecb28 100644 --- a/src/SWFProcessor.php +++ b/src/SWFProcessor.php @@ -69,14 +69,23 @@ class SWFProcessor extends SWFTreeProcessor { return $node["tagType"]; case "SoundStreamHead": case "SoundStreamHead2": + if($this->loops > 0){ + break; + } $this->audio = AudioStream::fromSoundStreamHeadTag($node); return $node["tagType"]; case "DefineSound": + if($this->loops > 0){ + break; + } $this->audio = new AudioStream(0, 0, 0, 0); $this->audio->setStartFrame($this->getFrame()); //TODO $this->audio = (object)["node" => $node, "start" => $this->getFrame(), "content" => []]; return $node["tagType"]; case "SoundStreamBlock": + if($this->loops > 0){ + break; + } if($this->audio !== null){ if($this->audio->getStartFrame() === null){ $this->audio->setStartFrame($this->getFrame()); diff --git a/src/SWFTreeProcessor.php b/src/SWFTreeProcessor.php index a667850..9e7bd86 100644 --- a/src/SWFTreeProcessor.php +++ b/src/SWFTreeProcessor.php @@ -59,6 +59,9 @@ class SWFTreeProcessor { switch ($node["tagType"]) { case "DefineMorphShape": case "DefineMorphShape2": + if($this->loops > 0){ + break; + } $shape = MorphShapeDefinition::fromArray($node); $this->objects->add($shape); break; @@ -67,11 +70,17 @@ class SWFTreeProcessor { case "DefineShape3": case "DefineShape4": case "DefineShape5": + if($this->loops > 0){ + break; + } $shape = ShapeDefinition::fromArray($node); $this->objects->add($shape); break; case "DefineSprite": + if($this->loops > 0){ + break; + } $objectID = $node["spriteId"]; $framesCount = $node["frameCount"]; @@ -81,6 +90,9 @@ class SWFTreeProcessor { break; case "DefineBitsLossless": case "DefineBitsLossless2": + if($this->loops > 0){ + break; + } break; //TODO $bitmap = BitmapDefinition::fromArray($node); @@ -88,6 +100,9 @@ class SWFTreeProcessor { break; case "DefineBitsJPEG2": case "DefineBitsJPEG3": + if($this->loops > 0){ + break; + } break; //TODO $bitmap = JPEGBitmapDefinition::fromArray($node); $this->objects->add($bitmap); diff --git a/src/Shape.php b/src/Shape.php index 37da8d7..a7974fe 100644 --- a/src/Shape.php +++ b/src/Shape.php @@ -38,27 +38,6 @@ class Shape { return $this->start()->equals($this->end()); } - - /** - * @return Vector2[] - * @throws \Exception - */ - public function toPoints() : array{ - $self = $this->flatten(); - $points = []; - - foreach ($self->getRecords() as $record){ - if($record instanceof LineRecord){ - $points[] = $record->start; - }else{ - var_dump($record); - throw new \Exception("Found record of type " . get_class($record)); - } - } - - return $points; - } - /** * @return Record[] */ @@ -66,16 +45,6 @@ class Shape { return $this->edges; } - public function getArea() : float{ - $area = 0; - $points = $this->toPoints(); - foreach ($points as $i => $p1){ - $p2 = $points[($i + 1) % count($points)]; - $area += $p1->x * $p2->y - $p1->y * $p2->x; - } - return $area / 2; - } - public function merge(Shape $shape): Shape { $newShape = new Shape([]); $newShape->edges = array_merge($this->edges, $shape->edges); diff --git a/src/SpriteDefinition.php b/src/SpriteDefinition.php index e25b00b..0fb9546 100644 --- a/src/SpriteDefinition.php +++ b/src/SpriteDefinition.php @@ -30,7 +30,8 @@ class SpriteDefinition implements MultiFrameObjectDefinition { } public function nextFrame(): ViewFrame { - return $this->currentFrame = $this->swf->nextFrame(); + //TODO: figure out why this can return null. missing shapes? + return $this->currentFrame = $this->swf->nextFrame() ?? new ViewFrame($this->getObjectId(), new DrawPathList()); } public function getSafeObject() : SpriteDefinition{ diff --git a/src/Utils.php b/src/Utils.php index b000ac3..50bea12 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -17,4 +17,89 @@ abstract class Utils { static function binary2dec($bin) { return gmp_intval(gmp_init($bin, 2)); } + + public static function lerpInteger(int $start, int $end, float $ratio): int { + return $start + ($end - $start) * $ratio; + } + + public static function lerpFloat(float $start, float $end, float $ratio): float { + return $start + ($end - $start) * $ratio; + } + + public static function lerpVector2(Vector2 $start, Vector2 $end, float $ratio): Vector2 { + return $start->add($end->sub($start)->multiply($ratio)); + } + + public static function lerpColor(Color $start, Color $end, float $ratio): Color { + return new Color(self::lerpInteger($start->r, $end->r, $ratio), self::lerpInteger($start->g, $end->g, $ratio), self::lerpInteger($start->b, $end->b, $ratio), self::lerpInteger($start->alpha, $end->alpha, $ratio)); + } + + /** + * @param Gradient $gradient + * @param int $slices + * @return \Iterator|GradientSlice[] + */ + public static function lerpGradient(Gradient $gradient, int $slices = Gradient::AUTO_SLICES): \Iterator{ + $items = $gradient->getItems(); + + //TODO: spread modes + $first = reset($items); + $last = end($items); + + if($first->ratio !== 0){ + $first = clone $first; + $first->ratio = 0; + array_unshift($items, $first); + } + if($last->ratio !== 255){ + $last = clone $last; + $last->ratio = 255; + array_push($items, $last); + } + + $prevItem = null; + foreach ($items as $item){ + if($prevItem !== null){ + if($gradient->getInterpolationMode() === Gradient::INTERPOLATE_LINEAR_RGB){ + $prevColor = $prevItem->color->toLinearRGB(); + $currentColor = $item->color->toLinearRGB(); + }else{ + $prevColor = $prevItem->color; + $currentColor = $item->color; + } + + $maxColorDistance = max(abs($prevColor->r - $currentColor->r), abs($prevColor->g - $currentColor->g), abs($prevColor->b - $currentColor->b), abs($prevColor->alpha - $currentColor->alpha)); + $prevPosition = $prevItem->ratio; + $currentPosition = $item->ratio; + $distance = abs($prevPosition - $currentPosition); + + if($maxColorDistance < Constants::EPSILON){ + $partitions = 1; + }else if($slices === Gradient::AUTO_SLICES){ + $partitions = min(255 / (count($items) + 1), max(1, ceil($maxColorDistance))); + }else{ + $partitions = ($distance / 255) * $slices; + } + $partitions = max(1, ceil($partitions)); + + + $fromPos = $prevPosition; + for($i = 1; $i <= $partitions; ++$i){ + $ratio = $i / $partitions; + $color = Utils::lerpColor($prevColor, $currentColor, $ratio); + + if($gradient->getInterpolationMode() === Gradient::INTERPOLATE_LINEAR_RGB){ + $color = $color->tosRGB(); + } + + $toPos = Utils::lerpFloat($prevPosition, $currentPosition, $ratio); + + yield new GradientSlice($color, $fromPos, $toPos); + + $fromPos = $toPos; + } + } + $prevItem = $item; + } + } } \ No newline at end of file diff --git a/src/ass/ASSLine.php b/src/ass/ASSLine.php index bcd3b98..e6b0bcb 100644 --- a/src/ass/ASSLine.php +++ b/src/ass/ASSLine.php @@ -2,7 +2,12 @@ namespace swf2ass\ass; +use swf2ass\ClipPath; +use swf2ass\DrawPath; +use swf2ass\FillStyleRecord; use swf2ass\FrameInformation; +use swf2ass\Gradient; +use swf2ass\MatrixTransform; use swf2ass\RenderedFrame; use swf2ass\RenderedObject; @@ -96,7 +101,6 @@ class ASSLine { $line->objectId = $object->objectId; $line->start = $information->getFrameNumber(); $line->end = $information->getFrameNumber(); - //TODO: do gradient splitting here $line->tags[] = containerTag::fromPathEntry($drawPath, $object->clip, $object->colorTransform, $object->matrixTransform, $bakeTransforms); $line->name = "o:{$object->objectId} d:" . implode(".", array_slice($object->depth, 1)); $lines[] = $line; diff --git a/src/ass/ASSRenderer.php b/src/ass/ASSRenderer.php index 4e71c41..3895b7b 100644 --- a/src/ass/ASSRenderer.php +++ b/src/ass/ASSRenderer.php @@ -2,7 +2,13 @@ namespace swf2ass\ass; +use swf2ass\ClipPath; +use swf2ass\Constants; +use swf2ass\DrawPath; +use swf2ass\DrawPathList; +use swf2ass\FillStyleRecord; use swf2ass\FrameInformation; +use swf2ass\Gradient; use swf2ass\MatrixTransform; use swf2ass\Rectangle; use swf2ass\RenderedFrame; @@ -29,7 +35,7 @@ class ASSRenderer { $display = $viewPort->toPixel(); $width = $display->getWidth() * self::getSetting("videoScaleMultiplier", 1); $height = $display->getHeight() * self::getSetting("videoScaleMultiplier", 1); - $ar = $width / $height; + $ar = sprintf("%.6F", $width / $height); /*if(($frameRate * 2) <= 60){ $frameRate *= 2; @@ -42,6 +48,7 @@ class ASSRenderer { [Script Info] ; Script generated by swf2ass ASSRenderer ; https://git.gammaspectra.live/WeebDataHoarder/swf2ass +Title: swf2ass ScriptType: v4.00+ ; TODO: maybe set WrapStyle: 2 WrapStyle: 0 @@ -55,10 +62,12 @@ Timer: {$timerPrecision} Last Style Storage: f Video File: ?dummy:{$frameRate}:10000:{$width}:{$height}:160:160:160:c Video AR Value: {$ar} +Active Line: 0 +Video Zoom Percent: 2.000000 [V4+ Styles] Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding -Style: f,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1 +Style: f,Arial,20,&H00000000,&H00000000,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1 [Events] Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text @@ -66,6 +75,35 @@ ASSHEADER; } + /** + * @param RenderedObject $object + * @return RenderedObject + */ + public static function bakeGradients(RenderedObject $object) : RenderedObject{ + $baked = null; + $drawPathList = new DrawPathList(); + foreach ($object->drawPathList->commands as $command){ + if($command->style instanceof FillStyleRecord and $command->style->fill instanceof Gradient){ + $baked = $baked ?? new RenderedObject($object->depth, $object->objectId, $drawPathList, $object->colorTransform, $object->matrixTransform, $object->clip); + + $gradientClip = new ClipPath($command->commands); + //Convert gradients to many tags + foreach ($command->style->fill->getInterpolatedDrawPaths(0, self::getSetting("gradientSlices", Gradient::AUTO_SLICES))->commands as $gradientPath){ + //echo (new drawTag($gradientPath->commands, 1))->encode(new ASSEventTime(1, 1, 1)) . "\n"; + $newPath = DrawPath::fill($gradientPath->style, $gradientClip->intersect(new ClipPath($gradientPath->commands))->getShape()); + //echo (new drawTag($newPath->commands, 1))->encode(new ASSEventTime(1, 1, 1)) . "\n\n"; + if(count($newPath->commands->getRecords()) === 0){ + continue; + } + $drawPathList->commands[] = $newPath; + } + }else{ + $drawPathList->commands[] = $command; + } + } + return $baked ?? $object; + } + public function renderFrame(FrameInformation $information, RenderedFrame $frame): \Generator { if ($this->header !== null) { foreach (explode("\n", $this->header) as $line) { @@ -83,7 +121,7 @@ ASSHEADER; $animated = 0; foreach ($objects as $object) { - $object = clone $object; + $object = clone self::bakeGradients($object); $object->matrixTransform = $scale->multiply($object->matrixTransform); //TODO order? $depth = $object->getDepth(); diff --git a/src/ass/borderTag.php b/src/ass/borderTag.php index b734092..dd23f9f 100644 --- a/src/ass/borderTag.php +++ b/src/ass/borderTag.php @@ -3,6 +3,7 @@ namespace swf2ass\ass; use swf2ass\Constants; +use swf2ass\FillStyleRecord; use swf2ass\LineStyleRecord; use swf2ass\StyleRecord; use swf2ass\Vector2; @@ -30,6 +31,8 @@ class borderTag implements ASSStyleTag { public static function fromStyleRecord(StyleRecord $record): ?borderTag { if ($record instanceof LineStyleRecord) { return new borderTag(new Vector2($record->width / Constants::TWIP_SIZE, $record->width / Constants::TWIP_SIZE)); + }else if ($record instanceof FillStyleRecord and $record->border !== null) { + return new borderTag(new Vector2($record->border->width / Constants::TWIP_SIZE, $record->border->width / Constants::TWIP_SIZE)); } return new borderTag(new Vector2(0, 0)); } diff --git a/src/ass/containerTag.php b/src/ass/containerTag.php index 15eb06d..44c9e6d 100644 --- a/src/ass/containerTag.php +++ b/src/ass/containerTag.php @@ -7,6 +7,8 @@ use MathPHP\LinearAlgebra\MatrixFactory; use swf2ass\ClipPath; use swf2ass\ColorTransform; use swf2ass\DrawPath; +use swf2ass\FillStyleRecord; +use swf2ass\Gradient; use swf2ass\LineStyleRecord; use swf2ass\MatrixTransform; use swf2ass\Shape; diff --git a/src/ass/fillColorTag.php b/src/ass/fillColorTag.php index 0660b54..b2b4a34 100644 --- a/src/ass/fillColorTag.php +++ b/src/ass/fillColorTag.php @@ -17,6 +17,8 @@ class fillColorTag extends colorTag { $color = $record->fill; } else if ($record->fill instanceof Gradient) { //TODO: split this elsewhere $color = $record->fill->getItems()[0]->color; + var_dump($record->fill); + throw new \Exception("Invalid Gradient Fill record"); } else { throw new \Exception("Invalid Fill record"); } diff --git a/src/ass/lineColorTag.php b/src/ass/lineColorTag.php index dd3d1d0..6d57412 100644 --- a/src/ass/lineColorTag.php +++ b/src/ass/lineColorTag.php @@ -3,6 +3,7 @@ namespace swf2ass\ass; use swf2ass\Color; +use swf2ass\FillStyleRecord; use swf2ass\LineStyleRecord; use swf2ass\StyleRecord; use swf2ass\Utils; @@ -11,6 +12,8 @@ class lineColorTag extends colorTag { public static function fromStyleRecord(StyleRecord $record): ?lineColorTag { if ($record instanceof LineStyleRecord) { return new lineColorTag($record->color, $record->color); + }else if ($record instanceof FillStyleRecord and $record->border !== null){ + return new lineColorTag($record->border->color, $record->border->color); } return new lineColorTag(null, null); } diff --git a/swf2ass.php b/swf2ass.php index 0724f06..30b4585 100644 --- a/swf2ass.php +++ b/swf2ass.php @@ -11,6 +11,7 @@ $settings = [ "timerSpeed" => 100, //NOTE: libass does not implement "Timer:", which is used by this setting. Leave at 100 by default "timePrecision" => 2, //NOTE: libass does not implement anything different from 2. Leave at 2 by default, "smoothTransitions" => false, //All transitions will happen smoothly from start till finish instead of happening instantly on frame thresholds. NOTE: this can break objects that did not get selected to be animated, or matrix transforms + "gradientSlices" => /*24,*/\swf2ass\Gradient::AUTO_SLICES, ]; $swfContent = file_get_contents($argv[1]);