WiP: gradients and others
This commit is contained in:
parent
fd89b9b5f6
commit
22c2fca7be
12
composer.lock
generated
12
composer.lock
generated
|
@ -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": [],
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
15
src/GradientSlice.php
Normal file
15
src/GradientSlice.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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>|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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
|
Loading…
Reference in a new issue