WiP: gradients and others

This commit is contained in:
DataHoarder 2023-08-03 01:48:26 +02:00
parent fd89b9b5f6
commit 22c2fca7be
23 changed files with 333 additions and 82 deletions

12
composer.lock generated
View file

@ -51,16 +51,16 @@
}, },
{ {
"name": "markrogoyski/math-php", "name": "markrogoyski/math-php",
"version": "v2.5.0", "version": "v2.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/markrogoyski/math-php.git", "url": "https://github.com/markrogoyski/math-php.git",
"reference": "ca71ca97dc136e7bb9e9e1fe05782f343a5692d4" "reference": "85d7d7fe205a6df2b20f956720e25341f3b1462a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/markrogoyski/math-php/zipball/ca71ca97dc136e7bb9e9e1fe05782f343a5692d4", "url": "https://api.github.com/repos/markrogoyski/math-php/zipball/85d7d7fe205a6df2b20f956720e25341f3b1462a",
"reference": "ca71ca97dc136e7bb9e9e1fe05782f343a5692d4", "reference": "85d7d7fe205a6df2b20f956720e25341f3b1462a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -120,9 +120,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/markrogoyski/math-php/issues", "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": [], "packages-dev": [],

View file

@ -15,21 +15,21 @@ function outputFrame($frame, $endFrame, $frameDurationMs) {
$shape = $path->commands; $shape = $path->commands;
$line->tags = [ $line->tags = [
new \swf2ass\ass\borderTag(0), new \swf2ass\ass\borderTag(new \swf2ass\Vector2(0, 0)),
new \swf2ass\ass\shadowTag(0), new \swf2ass\ass\shadowTag(0),
\swf2ass\ass\fillColorTag::fromStyleRecord($path->style) \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); $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); $line->tags[] = new \swf2ass\ass\drawTag($shape, 1);
echo $line->encode($frameDurationMs) . PHP_EOL; echo $line->encode($frameDurationMs) . PHP_EOL;
} }
} }
$fps = 30000 / 1001;//30; $fps = 30;//30000 / 1001;//30;
$colorN = 64; $colorN = 2;
$pframeColorDistance = 16; $pframeColorDistance = 16;
if (is_dir($argv[1])) { if (is_dir($argv[1])) {
@ -43,7 +43,7 @@ sort($frames);
$frameBuffer = null; $frameBuffer = null;
$quantizedFrameBuffer = $frameBuffer; $quantizedFrameBuffer = $frameBuffer;
$dynamicPalette = true; $dynamicPalette = false;
$palette = []; $palette = [];
for ($i = 0; $i < $colorN; ++$i) { 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)))); $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; $frameNumber = 1;
$endFrame = 100; $endFrame = 6522;
$lastFrameNumber = null; $lastFrameNumber = null;
$currentFrames = []; $currentFrames = [];

View file

@ -3,7 +3,7 @@
namespace swf2ass; namespace swf2ass;
class Circle extends Oval { class Circle extends Ellipse {
public function __construct(Vector2 $center, float $radius) { public function __construct(Vector2 $center, float $radius) {
parent::__construct($center, new Vector2($radius, $radius)); parent::__construct($center, new Vector2($radius, $radius));
} }

View file

@ -27,7 +27,29 @@ class Color {
return new Color($element["red"], $element["green"], $element["blue"], $element["alpha"] ?? 255); 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})"; 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,
);
}
} }

View file

@ -3,7 +3,7 @@
namespace swf2ass; namespace swf2ass;
class Oval implements ComplexShape { class Ellipse implements ComplexShape {
protected const c = 0.55228474983; // (4/3) * (sqrt(2) - 1) protected const c = 0.55228474983; // (4/3) * (sqrt(2) - 1)
//protected const c = 0.551915024494; // https://spencermortensen.com/articles/bezier-circle/ //protected const c = 0.551915024494; // https://spencermortensen.com/articles/bezier-circle/
@ -28,9 +28,9 @@ class Oval implements ComplexShape {
public function draw(): array { public function draw(): array {
return [ return [
$this->getQuarter(new Vector2(-$this->radius->x, $this->radius->y)), $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)), $this->getQuarter(new Vector2(-$this->radius->x, -$this->radius->y))->reverse(),
]; ];
} }
} }

View file

@ -7,7 +7,10 @@ class FillStyleRecord implements StyleRecord {
/** @var Gradient|Color */ /** @var Gradient|Color */
public $fill; public $fill;
public function __construct($fill) { public ?LineStyleRecord $border = null;
public function __construct($fill, ?LineStyleRecord $border = null) {
$this->fill = $fill; $this->fill = $fill;
$this->border = $border;
} }
} }

View file

@ -3,12 +3,31 @@
namespace swf2ass; namespace swf2ass;
interface Gradient { 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[] * @return GradientItem[]
*/ */
public function getItems(): array; public function getItems(): array;
public function getInterpolatedDrawPaths(int $overlap = 0, int $slices = self::AUTO_SLICES): DrawPathList;
public function getMatrixTransform(): MatrixTransform; public function getMatrixTransform(): MatrixTransform;
public function applyColorTransform(ColorTransform $transform): Gradient; public function applyColorTransform(ColorTransform $transform): Gradient;

15
src/GradientSlice.php Normal file
View file

@ -0,0 +1,15 @@
<?php
namespace swf2ass;
class GradientSlice {
public Color $color;
public float $startRatio;
public float $endRatio;
public function __construct(Color $color, float $startRatio, float $endRatio){
$this->color = $color;
$this->startRatio = $startRatio;
$this->endRatio = $endRatio;
}
}

View file

@ -3,23 +3,61 @@
namespace swf2ass; namespace swf2ass;
class LinearGradient implements Gradient { class LinearGradient implements Gradient {
/** @var GradientItem[] */ /** @var GradientItem[] */
public array $colors; public array $colors;
public MatrixTransform $transform; public MatrixTransform $transform;
public int $spreadMode;
public int $interpolationMode;
/** /**
* @param GradientItem[] $colors * @param GradientItem[] $colors
* @param MatrixTransform $transform * @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->colors = $colors;
$this->transform = $transform; $this->transform = $transform;
$this->spreadMode = $spreadMode;
$this->interpolationMode = $interpolationMode;
} }
public function getItems(): array { public function getItems(): array {
return $this->colors; 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 { public function getMatrixTransform(): MatrixTransform {
return $this->transform; return $this->transform;
@ -33,7 +71,7 @@ class LinearGradient implements Gradient {
//TODO: interpolationMode, spreadMode //TODO: interpolationMode, spreadMode
return new LinearGradient($colors, $transform); return new LinearGradient($colors, $transform, $element["spreadMode"], $element["interpolationMode"]);
} }
public function applyColorTransform(ColorTransform $transform): Gradient{ public function applyColorTransform(ColorTransform $transform): Gradient{

View file

@ -123,11 +123,11 @@ class MorphShapeDefinition implements ObjectDefinition {
//No need to convert types! //No need to convert types!
if($r1 instanceof LineRecord and $r2 instanceof LineRecord){ 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){ }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){ }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{ }else{
var_dump($records1); var_dump($records1);
var_dump($records2); var_dump($records2);
@ -138,17 +138,17 @@ class MorphShapeDefinition implements ObjectDefinition {
//TODO: morph styles properly //TODO: morph styles properly
if($c1->style instanceof FillStyleRecord and $c2->style instanceof FillStyleRecord){ if($c1->style instanceof FillStyleRecord and $c2->style instanceof FillStyleRecord){
if($c1->style->fill instanceof Color){ 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){ }else if($c1->style->fill instanceof Gradient){
//TODO: proper gradients //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{ }else{
var_dump($c1->style); var_dump($c1->style);
var_dump($c2->style); var_dump($c2->style);
throw new \Exception(); throw new \Exception();
} }
}else if($c1->style instanceof LineStyleRecord and $c2->style instanceof LineStyleRecord){ }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{ }else{
var_dump($c1->style); var_dump($c1->style);
var_dump($c2->style); var_dump($c2->style);
@ -159,22 +159,6 @@ class MorphShapeDefinition implements ObjectDefinition {
return $drawPathList; 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 { static function fromArray(array $element): MorphShapeDefinition {
$styles = MorphStyleList::fromArray($element); $styles = MorphStyleList::fromArray($element);

View file

@ -8,19 +8,57 @@ class RadialGradient implements Gradient {
public MatrixTransform $transform; public MatrixTransform $transform;
public int $spreadMode;
public int $interpolationMode;
/** /**
* @param GradientItem[] $colors * @param GradientItem[] $colors
* @param MatrixTransform $transform * @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->colors = $colors;
$this->transform = $transform; $this->transform = $transform;
$this->spreadMode = $spreadMode;
$this->interpolationMode = $interpolationMode;
} }
public function getItems(): array { public function getItems(): array {
return $this->colors; 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 { public function getMatrixTransform(): MatrixTransform {
return $this->transform; return $this->transform;
} }
@ -33,7 +71,7 @@ class RadialGradient implements Gradient {
//TODO: interpolationMode, spreadMode //TODO: interpolationMode, spreadMode
return new RadialGradient($colors, $transform); return new RadialGradient($colors, $transform, $element["spreadMode"], $element["interpolationMode"]);
} }
public function applyColorTransform(ColorTransform $transform): Gradient{ public function applyColorTransform(ColorTransform $transform): Gradient{

View file

@ -69,14 +69,23 @@ class SWFProcessor extends SWFTreeProcessor {
return $node["tagType"]; return $node["tagType"];
case "SoundStreamHead": case "SoundStreamHead":
case "SoundStreamHead2": case "SoundStreamHead2":
if($this->loops > 0){
break;
}
$this->audio = AudioStream::fromSoundStreamHeadTag($node); $this->audio = AudioStream::fromSoundStreamHeadTag($node);
return $node["tagType"]; return $node["tagType"];
case "DefineSound": case "DefineSound":
if($this->loops > 0){
break;
}
$this->audio = new AudioStream(0, 0, 0, 0); $this->audio = new AudioStream(0, 0, 0, 0);
$this->audio->setStartFrame($this->getFrame()); $this->audio->setStartFrame($this->getFrame());
//TODO $this->audio = (object)["node" => $node, "start" => $this->getFrame(), "content" => []]; //TODO $this->audio = (object)["node" => $node, "start" => $this->getFrame(), "content" => []];
return $node["tagType"]; return $node["tagType"];
case "SoundStreamBlock": case "SoundStreamBlock":
if($this->loops > 0){
break;
}
if($this->audio !== null){ if($this->audio !== null){
if($this->audio->getStartFrame() === null){ if($this->audio->getStartFrame() === null){
$this->audio->setStartFrame($this->getFrame()); $this->audio->setStartFrame($this->getFrame());

View file

@ -59,6 +59,9 @@ class SWFTreeProcessor {
switch ($node["tagType"]) { switch ($node["tagType"]) {
case "DefineMorphShape": case "DefineMorphShape":
case "DefineMorphShape2": case "DefineMorphShape2":
if($this->loops > 0){
break;
}
$shape = MorphShapeDefinition::fromArray($node); $shape = MorphShapeDefinition::fromArray($node);
$this->objects->add($shape); $this->objects->add($shape);
break; break;
@ -67,11 +70,17 @@ class SWFTreeProcessor {
case "DefineShape3": case "DefineShape3":
case "DefineShape4": case "DefineShape4":
case "DefineShape5": case "DefineShape5":
if($this->loops > 0){
break;
}
$shape = ShapeDefinition::fromArray($node); $shape = ShapeDefinition::fromArray($node);
$this->objects->add($shape); $this->objects->add($shape);
break; break;
case "DefineSprite": case "DefineSprite":
if($this->loops > 0){
break;
}
$objectID = $node["spriteId"]; $objectID = $node["spriteId"];
$framesCount = $node["frameCount"]; $framesCount = $node["frameCount"];
@ -81,6 +90,9 @@ class SWFTreeProcessor {
break; break;
case "DefineBitsLossless": case "DefineBitsLossless":
case "DefineBitsLossless2": case "DefineBitsLossless2":
if($this->loops > 0){
break;
}
break; //TODO break; //TODO
$bitmap = BitmapDefinition::fromArray($node); $bitmap = BitmapDefinition::fromArray($node);
@ -88,6 +100,9 @@ class SWFTreeProcessor {
break; break;
case "DefineBitsJPEG2": case "DefineBitsJPEG2":
case "DefineBitsJPEG3": case "DefineBitsJPEG3":
if($this->loops > 0){
break;
}
break; //TODO break; //TODO
$bitmap = JPEGBitmapDefinition::fromArray($node); $bitmap = JPEGBitmapDefinition::fromArray($node);
$this->objects->add($bitmap); $this->objects->add($bitmap);

View file

@ -38,27 +38,6 @@ class Shape {
return $this->start()->equals($this->end()); 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[] * @return Record[]
*/ */
@ -66,16 +45,6 @@ class Shape {
return $this->edges; 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 { public function merge(Shape $shape): Shape {
$newShape = new Shape([]); $newShape = new Shape([]);
$newShape->edges = array_merge($this->edges, $shape->edges); $newShape->edges = array_merge($this->edges, $shape->edges);

View file

@ -30,7 +30,8 @@ class SpriteDefinition implements MultiFrameObjectDefinition {
} }
public function nextFrame(): ViewFrame { 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{ public function getSafeObject() : SpriteDefinition{

View file

@ -17,4 +17,89 @@ abstract class Utils {
static function binary2dec($bin) { static function binary2dec($bin) {
return gmp_intval(gmp_init($bin, 2)); 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>|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;
}
}
} }

View file

@ -2,7 +2,12 @@
namespace swf2ass\ass; namespace swf2ass\ass;
use swf2ass\ClipPath;
use swf2ass\DrawPath;
use swf2ass\FillStyleRecord;
use swf2ass\FrameInformation; use swf2ass\FrameInformation;
use swf2ass\Gradient;
use swf2ass\MatrixTransform;
use swf2ass\RenderedFrame; use swf2ass\RenderedFrame;
use swf2ass\RenderedObject; use swf2ass\RenderedObject;
@ -96,7 +101,6 @@ class ASSLine {
$line->objectId = $object->objectId; $line->objectId = $object->objectId;
$line->start = $information->getFrameNumber(); $line->start = $information->getFrameNumber();
$line->end = $information->getFrameNumber(); $line->end = $information->getFrameNumber();
//TODO: do gradient splitting here
$line->tags[] = containerTag::fromPathEntry($drawPath, $object->clip, $object->colorTransform, $object->matrixTransform, $bakeTransforms); $line->tags[] = containerTag::fromPathEntry($drawPath, $object->clip, $object->colorTransform, $object->matrixTransform, $bakeTransforms);
$line->name = "o:{$object->objectId} d:" . implode(".", array_slice($object->depth, 1)); $line->name = "o:{$object->objectId} d:" . implode(".", array_slice($object->depth, 1));
$lines[] = $line; $lines[] = $line;

View file

@ -2,7 +2,13 @@
namespace swf2ass\ass; namespace swf2ass\ass;
use swf2ass\ClipPath;
use swf2ass\Constants;
use swf2ass\DrawPath;
use swf2ass\DrawPathList;
use swf2ass\FillStyleRecord;
use swf2ass\FrameInformation; use swf2ass\FrameInformation;
use swf2ass\Gradient;
use swf2ass\MatrixTransform; use swf2ass\MatrixTransform;
use swf2ass\Rectangle; use swf2ass\Rectangle;
use swf2ass\RenderedFrame; use swf2ass\RenderedFrame;
@ -29,7 +35,7 @@ class ASSRenderer {
$display = $viewPort->toPixel(); $display = $viewPort->toPixel();
$width = $display->getWidth() * self::getSetting("videoScaleMultiplier", 1); $width = $display->getWidth() * self::getSetting("videoScaleMultiplier", 1);
$height = $display->getHeight() * self::getSetting("videoScaleMultiplier", 1); $height = $display->getHeight() * self::getSetting("videoScaleMultiplier", 1);
$ar = $width / $height; $ar = sprintf("%.6F", $width / $height);
/*if(($frameRate * 2) <= 60){ /*if(($frameRate * 2) <= 60){
$frameRate *= 2; $frameRate *= 2;
@ -42,6 +48,7 @@ class ASSRenderer {
[Script Info] [Script Info]
; Script generated by swf2ass ASSRenderer ; Script generated by swf2ass ASSRenderer
; https://git.gammaspectra.live/WeebDataHoarder/swf2ass ; https://git.gammaspectra.live/WeebDataHoarder/swf2ass
Title: swf2ass
ScriptType: v4.00+ ScriptType: v4.00+
; TODO: maybe set WrapStyle: 2 ; TODO: maybe set WrapStyle: 2
WrapStyle: 0 WrapStyle: 0
@ -55,10 +62,12 @@ Timer: {$timerPrecision}
Last Style Storage: f Last Style Storage: f
Video File: ?dummy:{$frameRate}:10000:{$width}:{$height}:160:160:160:c Video File: ?dummy:{$frameRate}:10000:{$width}:{$height}:160:160:160:c
Video AR Value: {$ar} Video AR Value: {$ar}
Active Line: 0
Video Zoom Percent: 2.000000
[V4+ Styles] [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 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] [Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 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 { public function renderFrame(FrameInformation $information, RenderedFrame $frame): \Generator {
if ($this->header !== null) { if ($this->header !== null) {
foreach (explode("\n", $this->header) as $line) { foreach (explode("\n", $this->header) as $line) {
@ -83,7 +121,7 @@ ASSHEADER;
$animated = 0; $animated = 0;
foreach ($objects as $object) { foreach ($objects as $object) {
$object = clone $object; $object = clone self::bakeGradients($object);
$object->matrixTransform = $scale->multiply($object->matrixTransform); //TODO order? $object->matrixTransform = $scale->multiply($object->matrixTransform); //TODO order?
$depth = $object->getDepth(); $depth = $object->getDepth();

View file

@ -3,6 +3,7 @@
namespace swf2ass\ass; namespace swf2ass\ass;
use swf2ass\Constants; use swf2ass\Constants;
use swf2ass\FillStyleRecord;
use swf2ass\LineStyleRecord; use swf2ass\LineStyleRecord;
use swf2ass\StyleRecord; use swf2ass\StyleRecord;
use swf2ass\Vector2; use swf2ass\Vector2;
@ -30,6 +31,8 @@ class borderTag implements ASSStyleTag {
public static function fromStyleRecord(StyleRecord $record): ?borderTag { public static function fromStyleRecord(StyleRecord $record): ?borderTag {
if ($record instanceof LineStyleRecord) { if ($record instanceof LineStyleRecord) {
return new borderTag(new Vector2($record->width / Constants::TWIP_SIZE, $record->width / Constants::TWIP_SIZE)); 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)); return new borderTag(new Vector2(0, 0));
} }

View file

@ -7,6 +7,8 @@ use MathPHP\LinearAlgebra\MatrixFactory;
use swf2ass\ClipPath; use swf2ass\ClipPath;
use swf2ass\ColorTransform; use swf2ass\ColorTransform;
use swf2ass\DrawPath; use swf2ass\DrawPath;
use swf2ass\FillStyleRecord;
use swf2ass\Gradient;
use swf2ass\LineStyleRecord; use swf2ass\LineStyleRecord;
use swf2ass\MatrixTransform; use swf2ass\MatrixTransform;
use swf2ass\Shape; use swf2ass\Shape;

View file

@ -17,6 +17,8 @@ class fillColorTag extends colorTag {
$color = $record->fill; $color = $record->fill;
} else if ($record->fill instanceof Gradient) { //TODO: split this elsewhere } else if ($record->fill instanceof Gradient) { //TODO: split this elsewhere
$color = $record->fill->getItems()[0]->color; $color = $record->fill->getItems()[0]->color;
var_dump($record->fill);
throw new \Exception("Invalid Gradient Fill record");
} else { } else {
throw new \Exception("Invalid Fill record"); throw new \Exception("Invalid Fill record");
} }

View file

@ -3,6 +3,7 @@
namespace swf2ass\ass; namespace swf2ass\ass;
use swf2ass\Color; use swf2ass\Color;
use swf2ass\FillStyleRecord;
use swf2ass\LineStyleRecord; use swf2ass\LineStyleRecord;
use swf2ass\StyleRecord; use swf2ass\StyleRecord;
use swf2ass\Utils; use swf2ass\Utils;
@ -11,6 +12,8 @@ class lineColorTag extends colorTag {
public static function fromStyleRecord(StyleRecord $record): ?lineColorTag { public static function fromStyleRecord(StyleRecord $record): ?lineColorTag {
if ($record instanceof LineStyleRecord) { if ($record instanceof LineStyleRecord) {
return new lineColorTag($record->color, $record->color); 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); return new lineColorTag(null, null);
} }

View file

@ -11,6 +11,7 @@ $settings = [
"timerSpeed" => 100, //NOTE: libass does not implement "Timer:", which is used by this setting. Leave at 100 by default "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, "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 "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]); $swfContent = file_get_contents($argv[1]);