Refactor files : new ViewLayout based system
This commit is contained in:
parent
d0265ca581
commit
b4087dd93e
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,6 +6,8 @@
|
|||
/*.zip
|
||||
/*.mp4
|
||||
/*.mp3
|
||||
/*.flac
|
||||
/*.xml
|
||||
/*.webm
|
||||
/*.mkv
|
||||
/*.swf
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "weebdatahoarder/swf2ass",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"swf2ass\\": "src/"
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
require_once __DIR__ . "/vendor/autoload.php";
|
||||
|
||||
const SWFMILL_BINARY = "swfmill";
|
||||
const TWIP_SIZE = 20;
|
||||
const ADVANCED_MOTION_INTERPOLATION = true;
|
||||
|
||||
|
||||
function normalizePath($path) {
|
||||
$newPath = [];
|
||||
$lastCursor = new \swf2ass\Coordinate(0, 0);
|
||||
$lastCursor = new \swf2ass\Vector2(0, 0);
|
||||
foreach ($path as $e) {
|
||||
/** @var \swf2ass\LineRecord|\swf2ass\MoveRecord $e */
|
||||
|
||||
|
@ -127,12 +126,12 @@ function cleanupPath(array $path) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param \swf2ass\Coordinate $corner
|
||||
* @param \swf2ass\Vector2 $corner
|
||||
* @param \swf2ass\LineRecord[] $path
|
||||
*
|
||||
* @return ?\swf2ass\LineRecord
|
||||
*/
|
||||
function findNextCorner(\swf2ass\Coordinate $corner, array &$path, array &$cornerMap): ?\swf2ass\LineRecord {
|
||||
function findNextCorner(\swf2ass\Vector2 $corner, array &$path, array &$cornerMap): ?\swf2ass\LineRecord {
|
||||
foreach ($cornerMap[$corner->x . "_" . $corner->y] as $i => $p) {
|
||||
unset($path[$i]);
|
||||
unset($cornerMap[$p->start->x . "_" . $p->start->y][$i]);
|
||||
|
@ -147,7 +146,7 @@ function findNextCorner(\swf2ass\Coordinate $corner, array &$path, array &$corne
|
|||
return null;
|
||||
}
|
||||
|
||||
function offsetPath($path, \swf2ass\Coordinate $offset): array {
|
||||
function offsetPath($path, \swf2ass\Vector2 $offset): array {
|
||||
$newPath = [];
|
||||
foreach ($path as $e) {
|
||||
|
||||
|
@ -164,7 +163,7 @@ function offsetPath($path, \swf2ass\Coordinate $offset): array {
|
|||
}
|
||||
|
||||
function getBoundingBox($path) {
|
||||
$bb = new \swf2ass\Rectangle(new \swf2ass\Coordinate(PHP_INT_MAX, PHP_INT_MAX), new \swf2ass\Coordinate(PHP_INT_MIN, PHP_INT_MIN));
|
||||
$bb = new \swf2ass\Rectangle(new \swf2ass\Vector2(PHP_INT_MAX, PHP_INT_MAX), new \swf2ass\Vector2(PHP_INT_MIN, PHP_INT_MIN));
|
||||
/*if(count($path) > 0){
|
||||
$first = reset($path);
|
||||
if($first instanceof MoveRecord or $first instanceof LineRecord){
|
||||
|
@ -197,9 +196,9 @@ function getBoundingBox($path) {
|
|||
|
||||
function calculateAreaOfPath($path) {
|
||||
$totalArea = 0;
|
||||
/** @var \swf2ass\Coordinate[][] $subPolygons */
|
||||
/** @var \swf2ass\Vector2[][] $subPolygons */
|
||||
$subPolygons = [];
|
||||
/** @var \swf2ass\Coordinate[] $currentPolygon */
|
||||
/** @var \swf2ass\Vector2[] $currentPolygon */
|
||||
$currentPolygon = [];
|
||||
foreach ($path as $n) {
|
||||
if ($n instanceof \swf2ass\MoveRecord) {
|
||||
|
@ -264,7 +263,7 @@ function renderFrame(array $pixelMatrix, int $resX, int $resY, bool $doFullFrame
|
|||
$colors[$k] = $color;
|
||||
$paths[$k] = [];
|
||||
}
|
||||
foreach ((new \swf2ass\Square(new \swf2ass\Coordinate($x, $y), 1))->draw() as $p) {
|
||||
foreach ((new \swf2ass\Square(new \swf2ass\Vector2($x, $y), 1))->draw() as $p) {
|
||||
$paths[$k][] = $p;
|
||||
}
|
||||
}
|
||||
|
@ -288,7 +287,7 @@ function renderFrame(array $pixelMatrix, int $resX, int $resY, bool $doFullFrame
|
|||
$k0 = array_key_first($optimizedPaths);
|
||||
$path = $optimizedPaths[$k0];
|
||||
$optimizedPaths[$k0] = [];
|
||||
foreach ((new \swf2ass\Rectangle(new \swf2ass\Coordinate(0, 0), new \swf2ass\Coordinate($resX, $resY)))->draw() as $p) {
|
||||
foreach ((new \swf2ass\Rectangle(new \swf2ass\Vector2(0, 0), new \swf2ass\Vector2($resX, $resY)))->draw() as $p) {
|
||||
$optimizedPaths[$k0][] = $p;
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +295,7 @@ function renderFrame(array $pixelMatrix, int $resX, int $resY, bool $doFullFrame
|
|||
$assLines = [];
|
||||
|
||||
foreach ($optimizedPaths as $k => $path) {
|
||||
$pos = new \swf2ass\Coordinate(0, 0);
|
||||
$pos = new \swf2ass\Vector2(0, 0);
|
||||
$bb = getBoundingBox($path);
|
||||
$pos = $bb->topLeft;
|
||||
/*$first = reset($path);
|
||||
|
|
9
src/ActionList.php
Normal file
9
src/ActionList.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class ActionList {
|
||||
|
||||
|
||||
}
|
|
@ -11,14 +11,14 @@ class ActivePath {
|
|||
|
||||
/**
|
||||
* @param $styleId
|
||||
* @param Coordinate $start
|
||||
* @param Vector2 $start
|
||||
*/
|
||||
public function __construct($styleId, Coordinate $start) {
|
||||
public function __construct($styleId, Vector2 $start) {
|
||||
$this->style = $styleId;
|
||||
$this->segment = new PathSegment($start);
|
||||
}
|
||||
|
||||
public function add_point(VisitedPoint $point){
|
||||
public function add_point(VisitedPoint $point) {
|
||||
$this->segment->add_point($point);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,18 +6,18 @@ namespace swf2ass;
|
|||
class BitmapConverter {
|
||||
/** @var Color[][] */
|
||||
private array $pixelMatrix;
|
||||
private Coordinate $resolution;
|
||||
private Vector2 $resolution;
|
||||
|
||||
public function __construct(array $pixelMatrix) {
|
||||
$this->pixelMatrix = $pixelMatrix;
|
||||
$this->resolution = new Coordinate(count($pixelMatrix[0]), count($pixelMatrix));
|
||||
$this->resolution = new Vector2(count($pixelMatrix[0]), count($pixelMatrix));
|
||||
}
|
||||
|
||||
private static function calculateAreaOfPath($path) {
|
||||
$totalArea = 0;
|
||||
/** @var Coordinate[][] $subPolygons */
|
||||
/** @var Vector2[][] $subPolygons */
|
||||
$subPolygons = [];
|
||||
/** @var Coordinate[] $currentPolygon */
|
||||
/** @var Vector2[] $currentPolygon */
|
||||
$currentPolygon = [];
|
||||
foreach ($path as $n) {
|
||||
if ($n instanceof MoveRecord) {
|
||||
|
@ -65,7 +65,7 @@ class BitmapConverter {
|
|||
|
||||
private static function normalizePath($path) {
|
||||
$newPath = [];
|
||||
$lastCursor = new Coordinate(0, 0);
|
||||
$lastCursor = new Vector2(0, 0);
|
||||
foreach ($path as $e) {
|
||||
/** @var LineRecord|MoveRecord $e */
|
||||
|
||||
|
@ -178,12 +178,12 @@ class BitmapConverter {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param Coordinate $corner
|
||||
* @param Vector2 $corner
|
||||
* @param LineRecord[] $path
|
||||
*
|
||||
* @return ?LineRecord
|
||||
*/
|
||||
private static function findNextCorner(Coordinate $corner, array &$path, array &$cornerMap): ?LineRecord {
|
||||
private static function findNextCorner(Vector2 $corner, array &$path, array &$cornerMap): ?LineRecord {
|
||||
foreach ($cornerMap[$corner->x . "_" . $corner->y] as $i => $p) {
|
||||
unset($path[$i]);
|
||||
unset($cornerMap[$p->start->x . "_" . $p->start->y][$i]);
|
||||
|
@ -200,7 +200,7 @@ class BitmapConverter {
|
|||
|
||||
|
||||
private static function getBoundingBox($path) {
|
||||
$bb = new Rectangle(new \swf2ass\Coordinate(PHP_INT_MAX, PHP_INT_MAX), new Coordinate(PHP_INT_MIN, PHP_INT_MIN));
|
||||
$bb = new Rectangle(new \swf2ass\Vector2(PHP_INT_MAX, PHP_INT_MAX), new Vector2(PHP_INT_MIN, PHP_INT_MIN));
|
||||
/*if(count($path) > 0){
|
||||
$first = reset($path);
|
||||
if($first instanceof MoveRecord or $first instanceof LineRecord){
|
||||
|
@ -241,7 +241,7 @@ class BitmapConverter {
|
|||
$colors[$k] = $color;
|
||||
$paths[$k] = [];
|
||||
}
|
||||
foreach ((new Square(new Coordinate($x, $y), 1))->draw() as $p) {
|
||||
foreach ((new Square(new Vector2($x, $y), 1))->draw() as $p) {
|
||||
$paths[$k][] = $p;
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +265,7 @@ class BitmapConverter {
|
|||
$k0 = array_key_first($optimizedPaths);
|
||||
$path = $optimizedPaths[$k0];
|
||||
$optimizedPaths[$k0] = [];
|
||||
foreach ((new Rectangle(new Coordinate(0, 0), $this->resolution))->draw() as $p) {
|
||||
foreach ((new Rectangle(new Vector2(0, 0), $this->resolution))->draw() as $p) {
|
||||
$optimizedPaths[$k0][] = $p;
|
||||
}
|
||||
}
|
||||
|
@ -273,19 +273,19 @@ class BitmapConverter {
|
|||
/** @var DrawPath[] $commands */
|
||||
$commands = [];
|
||||
|
||||
foreach ($optimizedPaths as $k => $path){
|
||||
foreach ($optimizedPaths as $k => $path) {
|
||||
$edges = [];
|
||||
$bb = self::getBoundingBox($path);
|
||||
$pos = $bb->topLeft->multiply(TWIP_SIZE);
|
||||
$pos = $bb->topLeft->multiply(Constants::TWIP_SIZE);
|
||||
|
||||
$edges[] = new MoveRecord($pos, new Coordinate(0, 0));
|
||||
$edges[] = new MoveRecord($pos, new Vector2(0, 0));
|
||||
|
||||
foreach ($path as $edge){
|
||||
if($edge instanceof MoveRecord){
|
||||
$edges[] = new MoveRecord($edge->coord->multiply(TWIP_SIZE), $edge->start->multiply(TWIP_SIZE));
|
||||
}else if($edge instanceof LineRecord){
|
||||
$edges[] = new LineRecord($edge->coord->multiply(TWIP_SIZE), $edge->start->multiply(TWIP_SIZE));
|
||||
}else{
|
||||
foreach ($path as $edge) {
|
||||
if ($edge instanceof MoveRecord) {
|
||||
$edges[] = new MoveRecord($edge->coord->multiply(Constants::TWIP_SIZE), $edge->start->multiply(Constants::TWIP_SIZE));
|
||||
} else if ($edge instanceof LineRecord) {
|
||||
$edges[] = new LineRecord($edge->coord->multiply(Constants::TWIP_SIZE), $edge->start->multiply(Constants::TWIP_SIZE));
|
||||
} else {
|
||||
var_dump($edge);
|
||||
throw new \Exception();
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@ namespace swf2ass;
|
|||
|
||||
class BitmapDefinition implements ObjectDefinition {
|
||||
|
||||
public $id;
|
||||
public Coordinate $size;
|
||||
public int $id;
|
||||
public Vector2 $size;
|
||||
/** @var Color[][] */
|
||||
public $pixels;
|
||||
private DrawPathList $drawPathList;
|
||||
|
||||
public function __construct($id, Coordinate $size, array $pixels) {
|
||||
public function __construct(int $id, Vector2 $size, array $pixels) {
|
||||
$this->id = $id;
|
||||
$this->size = $size;
|
||||
$this->pixels = $pixels;
|
||||
|
@ -19,16 +19,17 @@ class BitmapDefinition implements ObjectDefinition {
|
|||
$this->drawPathList = $converter->render(true);
|
||||
}
|
||||
|
||||
public function getId(){
|
||||
public function getObjectId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
public function getShapeList() : DrawPathList{
|
||||
|
||||
public function getShapeList(): DrawPathList {
|
||||
return $this->drawPathList;
|
||||
}
|
||||
|
||||
static function fromXML(\DOMElement $element): BitmapDefinition {
|
||||
//Utils::dump_element($element);
|
||||
$size = new Coordinate((int)$element->getAttribute("width"), (int)$element->getAttribute("height"));
|
||||
$size = new Vector2((int)$element->getAttribute("width"), (int)$element->getAttribute("height"));
|
||||
$pixels = array_fill(0, $size->y, array_fill(0, $size->x, new Color(0, 0, 0)));
|
||||
$format = (int)$element->getAttribute("format");
|
||||
switch ($format) {
|
||||
|
@ -70,7 +71,7 @@ class BitmapDefinition implements ObjectDefinition {
|
|||
return new BitmapDefinition($element->getAttribute("objectID"), $size, $pixels);
|
||||
}
|
||||
|
||||
public function getPixel(Coordinate $c) {
|
||||
public function getPixel(Vector2 $c) {
|
||||
return $this->pixels[$c->y][$c->x];
|
||||
}
|
||||
|
||||
|
@ -82,7 +83,7 @@ class BitmapDefinition implements ObjectDefinition {
|
|||
$iterator = $im->getPixelIterator();
|
||||
foreach ($iterator as $row => $pixels) {
|
||||
foreach ($pixels as $col => $pixel) {
|
||||
$c = $this->getPixel(new Coordinate($col, $row));
|
||||
$c = $this->getPixel(new Vector2($col, $row));
|
||||
$colorStr = "#" . bin2hex(chr($c->r)) . bin2hex(chr($c->g)) . bin2hex(chr($c->b)) . bin2hex(chr(255 - ($c->a ?? 0)));
|
||||
$pixel->setColor($colorStr);
|
||||
}
|
||||
|
|
17
src/ClippingViewLayout.php
Normal file
17
src/ClippingViewLayout.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class ClippingViewLayout extends ViewLayout {
|
||||
private int $clipDepth;
|
||||
|
||||
public function __construct(int $clipDepth, int $objectId, ?ObjectDefinition $object, ?ViewLayout $parent = null) {
|
||||
$this->clipDepth = $clipDepth;
|
||||
parent::__construct($objectId, $object, $parent);
|
||||
}
|
||||
|
||||
public function getClipDepth(): int {
|
||||
return $this->clipDepth;
|
||||
}
|
||||
}
|
|
@ -8,24 +8,24 @@ class Color {
|
|||
public $b;
|
||||
public $alpha;
|
||||
|
||||
public function __construct($r, $g, $b, $alpha = null) {
|
||||
public function __construct($r, $g, $b, $alpha = 0) {
|
||||
$this->r = $r;
|
||||
$this->g = $g;
|
||||
$this->b = $b;
|
||||
$this->alpha = $alpha;
|
||||
}
|
||||
|
||||
public function equals(?Color $other): bool {
|
||||
return $other !== null and $other->r === $this->r and $other->g === $this->g and $other->b === $this->b and $other->alpha == $this->alpha;
|
||||
public function equals(?Color $other, $alpha = true): bool {
|
||||
return $other !== null and $other->r === $this->r and $other->g === $this->g and $other->b === $this->b and (!$alpha or $other->alpha == $this->alpha);
|
||||
}
|
||||
|
||||
public function distance(Color $color): float {
|
||||
return sqrt(pow($color->r - $this->r, 2) + pow($color->g - $this->g, 2) + pow($color->b - $this->b, 2));
|
||||
public function distance(Color $color, $alpha = false): float {
|
||||
return sqrt(pow($color->r - $this->r, 2) + pow($color->g - $this->g, 2) + pow($color->b - $this->b, 2) + ($alpha ? pow($color->alpha - $this->alpha, 2) : 0));
|
||||
}
|
||||
|
||||
public function toString($nullAlpha = false) {
|
||||
$c = "\\1c&H" . strtoupper(Utils::padHex(dechex($this->b ?? 0))) . strtoupper(Utils::padHex(dechex($this->g ?? 0))) . strtoupper(Utils::padHex(dechex($this->r ?? 0))) . "&";
|
||||
if (($this->alpha === null or $this->alpha === 0) and $nullAlpha) {
|
||||
if (($this->alpha === 0) and $nullAlpha) {
|
||||
|
||||
} else {
|
||||
$c .= "\\1a&H" . strtoupper(Utils::padHex(dechex($this->alpha ?? 0))) . "&";
|
||||
|
@ -34,11 +34,7 @@ class Color {
|
|||
return $c;
|
||||
}
|
||||
|
||||
public function applyTransform(ColorTransform $transform): Color {
|
||||
return new Color(max(0, min((($this->r * ($transform->mult->r ?? 256)) / 256) + ($transform->add->r ?? 0), 255)), max(0, min((($this->g * ($transform->mult->g ?? 256)) / 256) + ($transform->add->g ?? 0), 255)), max(0, min((($this->b * ($transform->mult->b ?? 256)) / 256) + ($transform->add->b ?? 0), 255)), max(0, min((($this->alpha * ($transform->mult->alpha ?? 256)) / 256) + ($transform->add->alpha ?? 0), 255)),);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element): Color {
|
||||
return new Color($element->getAttribute("red"), $element->getAttribute("green"), $element->getAttribute("blue"), $element->hasAttribute("alpha") ? $element->getAttribute("alpha") : null);
|
||||
return new Color((int)$element->getAttribute("red"), (int)$element->getAttribute("green"), (int)$element->getAttribute("blue"), $element->hasAttribute("alpha") ? (int)$element->getAttribute("alpha") : 0);
|
||||
}
|
||||
}
|
|
@ -4,14 +4,18 @@ namespace swf2ass;
|
|||
|
||||
|
||||
class ColorTransform {
|
||||
public $mult;
|
||||
public $add;
|
||||
public Color $mult;
|
||||
public Color $add;
|
||||
|
||||
public function __construct(Color $mult = null, Color $add = null) {
|
||||
public function __construct(Color $mult, Color $add) {
|
||||
$this->mult = $mult;
|
||||
$this->add = $add;
|
||||
}
|
||||
|
||||
public static function identity(): ColorTransform {
|
||||
return new ColorTransform(new Color(256, 256, 256, 256), new Color(0, 0, 0));
|
||||
}
|
||||
|
||||
public function applyToStyleContainer(StyleContainer $styles): StyleContainer {
|
||||
if ($styles->original_color1 instanceof Color) {
|
||||
$styles->color1 = $styles->original_color1->applyTransform($this);
|
||||
|
@ -23,9 +27,30 @@ class ColorTransform {
|
|||
return $styles;
|
||||
}
|
||||
|
||||
public function applyMultiplyToColor(Color $color): Color {
|
||||
return new Color(max(0, min(($this->mult->r * $color->r) / 256, 255)), max(0, min(($this->mult->g * $color->g) / 256, 255)), max(0, min(($this->mult->b * $color->b) / 256, 255)), max(0, min(($this->mult->alpha * $color->alpha) / 256, 255)),);
|
||||
}
|
||||
|
||||
public function applyAdditionToColor(Color $color): Color {
|
||||
return new Color(max(0, min($this->add->r + $color->r, 255)), max(0, min($this->add->g + $color->g, 255)), max(0, min($this->add->b + $color->b, 255)), max(0, min($this->add->alpha + $color->alpha, 255)),);
|
||||
}
|
||||
|
||||
public function applyToColor(Color $color): Color {
|
||||
return new Color(max(0, min((($this->mult->r * $color->r) / 256) + $this->add->r, 255)), max(0, min((($this->mult->g * $color->g) / 256) + $this->add->g, 255)), max(0, min((($this->mult->b * $color->b) / 256) + $this->add->b, 255)), max(0, min((($this->mult->alpha * $color->alpha) / 256) + $this->add->alpha, 255)),);
|
||||
}
|
||||
|
||||
public function combine(ColorTransform $transform) {
|
||||
//TODO: maybe these get altered all at once?
|
||||
return new ColorTransform($this->applyMultiplyToColor($transform->mult), $this->applyAdditionToColor($transform->add));
|
||||
}
|
||||
|
||||
public function equals(ColorTransform $other, $epsilon = Constants::EPSILON): bool {
|
||||
return ($this->mult === $other or ($this->mult !== null and $this->mult->equals($other->mult))) and ($this->add === $other or ($this->add !== null and $this->add->equals($other->add)));
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element): ColorTransform {
|
||||
$add = new Color(null, null, null, null);
|
||||
$mult = new Color(null, null, null, null);
|
||||
$add = new Color(0, 0, 0, 0);
|
||||
$mult = new Color(256, 256, 256, 256);
|
||||
if ($element->hasAttribute("factorRed")) {
|
||||
$mult->r = (int)$element->getAttribute("factorRed");
|
||||
$mult->g = (int)$element->getAttribute("factorGreen");
|
||||
|
|
8
src/Constants.php
Normal file
8
src/Constants.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
abstract class Constants {
|
||||
const TWIP_SIZE = 20;
|
||||
const EPSILON = 0.000001;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
class Coordinate {
|
||||
public $x;
|
||||
public $y;
|
||||
|
||||
public function __construct($x, $y) {
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
}
|
||||
|
||||
|
||||
public function equals(Coordinate $b): bool {
|
||||
return $b->x === $this->x and $b->y === $this->y;
|
||||
}
|
||||
|
||||
public function distance(Coordinate $b): float {
|
||||
return sqrt(pow($this->x - $b->x, 2) + pow($this->y - $b->y, 2));
|
||||
}
|
||||
|
||||
public function applyTransform(MatrixTransform $transform): Coordinate {
|
||||
return new Coordinate($this->x * ($transform->scaleX ?? 1) + $this->y * ($transform->skewY ?? 0) + ($transform->transX ?? 0), $this->y * ($transform->scaleY ?? 1) + $this->x * ($transform->skewX ?? 0) + ($transform->transY ?? 0));
|
||||
}
|
||||
|
||||
public function add(Coordinate $b): Coordinate {
|
||||
return new Coordinate($this->x + $b->x, $this->y + $b->y);
|
||||
}
|
||||
|
||||
public function sub(Coordinate $b): Coordinate {
|
||||
return new Coordinate($this->x - $b->x, $this->y - $b->y);
|
||||
}
|
||||
|
||||
public function multiply($size): Coordinate {
|
||||
return new Coordinate($this->x * $size, $this->y * $size);
|
||||
}
|
||||
|
||||
public function divide($size): Coordinate {
|
||||
return new Coordinate($this->x / $size, $this->y / $size);
|
||||
}
|
||||
|
||||
public function toPixel($twipSize = TWIP_SIZE): Coordinate {
|
||||
return $this->divide($twipSize);
|
||||
}
|
||||
}
|
|
@ -3,19 +3,19 @@
|
|||
namespace swf2ass;
|
||||
|
||||
class CubicCurveRecord implements Record {
|
||||
public Coordinate $start;
|
||||
public Coordinate $control1;
|
||||
public Coordinate $control2;
|
||||
public Coordinate $anchor;
|
||||
public Vector2 $start;
|
||||
public Vector2 $control1;
|
||||
public Vector2 $control2;
|
||||
public Vector2 $anchor;
|
||||
|
||||
public function __construct(Coordinate $control1, Coordinate $control2, Coordinate $anchor, Coordinate $start) {
|
||||
public function __construct(Vector2 $control1, Vector2 $control2, Vector2 $anchor, Vector2 $start) {
|
||||
$this->control1 = $control1;
|
||||
$this->control2 = $control2;
|
||||
$this->anchor = $anchor;
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function getStart(): Coordinate {
|
||||
public function getStart(): Vector2 {
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,11 @@ class CubicCurveRecord implements Record {
|
|||
return new CubicCurveRecord($this->control2, $this->control1, $this->start, $this->anchor);
|
||||
}
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): CubicCurveRecord {
|
||||
return new CubicCurveRecord($transform->applyToVector($this->control1), $transform->applyToVector($this->control2), $transform->applyToVector($this->anchor), $transform->applyToVector($this->start));
|
||||
}
|
||||
|
||||
public static function fromQuadraticRecord(QuadraticCurveRecord $q): CubicCurveRecord {
|
||||
return new CubicCurveRecord(
|
||||
$q->start->add($q->control->multiply(2))->divide(3),
|
||||
$q->anchor->add($q->control->multiply(2))->divide(3),
|
||||
$q->anchor,
|
||||
$q->start
|
||||
);
|
||||
return new CubicCurveRecord($q->start->add($q->control->multiply(2))->divide(3), $q->anchor->add($q->control->multiply(2))->divide(3), $q->anchor, $q->start);
|
||||
}
|
||||
}
|
46
src/CubicSplineCurveRecord.php
Normal file
46
src/CubicSplineCurveRecord.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
class CubicSplineCurveRecord implements Record {
|
||||
public Vector2 $start;
|
||||
/** @var Vector2[] */
|
||||
public array $control;
|
||||
public Vector2 $anchor;
|
||||
|
||||
/**
|
||||
* @param Vector2[] $control
|
||||
* @param Vector2 $anchor
|
||||
* @param Vector2 $start
|
||||
*/
|
||||
public function __construct(array $control, Vector2 $anchor, Vector2 $start) {
|
||||
$this->control = $control;
|
||||
$this->anchor = $anchor;
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function getStart(): Vector2 {
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
public function reverse(): CubicSplineCurveRecord {
|
||||
return new CubicSplineCurveRecord(array_reverse($this->control), $this->start, $this->anchor);
|
||||
}
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): CubicSplineCurveRecord {
|
||||
$control = [];
|
||||
foreach ($this->control as $c) {
|
||||
$control[] = $transform->applyToVector($c);
|
||||
}
|
||||
|
||||
return new CubicSplineCurveRecord($control, $transform->applyToVector($this->anchor), $transform->applyToVector($this->start));
|
||||
}
|
||||
|
||||
public function append(Record $record): ?CubicSplineCurveRecord {
|
||||
if ($record instanceof CubicCurveRecord) {
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ class CurrentState {
|
|||
$this->frameRate = 60;
|
||||
$this->frameCount = 100;
|
||||
$this->frameNumber = 0;
|
||||
$this->viewPort = new Rectangle(new Coordinate(0, 0), new Coordinate(480, 360));
|
||||
$this->viewPort = new Rectangle(new Vector2(0, 0), new Vector2(480, 360));
|
||||
$this->objects = [];
|
||||
$this->displayList = [];
|
||||
$this->clipList = [];
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class DisplayEntry {
|
||||
public $object;
|
||||
public $currentColorTransform = null;
|
||||
public $currentTransform = null;
|
||||
/** @var FrameInformation[] */
|
||||
public $frames = [];
|
||||
public $startFrame;
|
||||
public $depth;
|
||||
public $clipDepth = 0;
|
||||
|
||||
public function getLines(CurrentState $currentState) {
|
||||
if ($this->clipDepth > 0) {
|
||||
return [];
|
||||
}
|
||||
if ($this->depth != 2) {
|
||||
//return [];
|
||||
}
|
||||
|
||||
if($currentState->frameOffset === null){
|
||||
return [];
|
||||
}
|
||||
$dialogue = [];
|
||||
|
||||
$startFrame = $this->startFrame - $currentState->frameOffset;
|
||||
|
||||
$frameList = [];
|
||||
foreach ($this->frames as $frame => $info){
|
||||
$fn = $frame - $currentState->frameOffset;
|
||||
if($fn < 0){
|
||||
continue;
|
||||
}
|
||||
$frameList[$fn] = clone $info;
|
||||
$frameList[$fn]->frame = $fn;
|
||||
}
|
||||
|
||||
if(count($frameList) === 0){
|
||||
return [];
|
||||
}
|
||||
|
||||
$startTime = $startFrame * (1 / $currentState->frameRate);
|
||||
$endTime = ($startFrame + 1) * (1 / $currentState->frameRate);
|
||||
|
||||
|
||||
if ($this->object instanceof ObjectDefinition) {
|
||||
$shapeDef = $this->object;
|
||||
|
||||
foreach ($shapeDef->getShapeList()->commands as $path){
|
||||
$currentFrameStyles = [new StyleContainer()];
|
||||
|
||||
if($path->style instanceof LineStyleRecord){ //Stroke
|
||||
$currentFrameStyles[0]->original_color3 = $currentFrameStyles[0]->color3 = $path->style->color;
|
||||
$currentFrameStyles[0]->bord = round($path->style->width / TWIP_SIZE, 1);
|
||||
}else if($path->style instanceof FillStyleRecord){ //Fill
|
||||
$currentFrameStyles[0]->original_color1 = $currentFrameStyles[0]->color1 = $path->style->fill instanceof Gradient ? $path->style->fill->getItems()[0]->color : $path->style->fill; //TODO: gradient?
|
||||
}
|
||||
|
||||
$currentFrameStyles[0]->shad = 0;
|
||||
|
||||
$assLine = "{\\an7\\shad0\\p1}";
|
||||
|
||||
foreach ($path->commands->edges as $edge) {
|
||||
if ($edge instanceof MoveRecord) {
|
||||
$coords = $edge->coord->toPixel();
|
||||
$assLine .= "m " . round($coords->x, 2) . " " . round($coords->y, 2) . " ";
|
||||
} else if ($edge instanceof LineRecord) {
|
||||
$coords = $edge->coord->toPixel();
|
||||
$assLine .= "l " . round($coords->x, 2) . " " . round($coords->y, 2) . " ";
|
||||
} else if ($edge instanceof QuadraticCurveRecord) {
|
||||
$edge = CubicCurveRecord::fromQuadraticRecord($edge);
|
||||
|
||||
$control1 = $edge->control1->toPixel();
|
||||
$control2 = $edge->control2->toPixel();
|
||||
$anchor = $edge->anchor->toPixel();
|
||||
$assLine .= "b " . round($control1->x, 2) . " " . round($control1->y, 2) . " " . round($control2->x, 2) . " " . round($control2->y, 2) . " " . round($anchor->x, 2) . " " . round($anchor->y, 2) . " ";
|
||||
} else if ($edge instanceof CubicCurveRecord) {
|
||||
$control1 = $edge->control1->toPixel();
|
||||
$control2 = $edge->control2->toPixel();
|
||||
$anchor = $edge->anchor->toPixel();
|
||||
$assLine .= "b " . round($control1->x, 2) . " " . round($control1->y, 2) . " " . round($control2->x, 2) . " " . round($control2->y, 2) . " " . round($anchor->x, 2) . " " . round($anchor->y, 2) . " ";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$assLine .= "{\\p0}";
|
||||
|
||||
$lastLineStart = $startTime;
|
||||
$lastFrameStart = $startFrame;
|
||||
|
||||
$debugInfo = "oid:".$shapeDef->getId();
|
||||
|
||||
$savedFrameStyles = $currentFrameStyles;
|
||||
|
||||
$af = 0;
|
||||
$frames = 0;
|
||||
foreach ($frameList as $frame => $frameInformation) {
|
||||
$currentTime = $frame * (1 / $currentState->frameRate);
|
||||
$endCurrentTime = $currentTime + (1 / $currentState->frameRate);
|
||||
if ($endCurrentTime > $endTime) {
|
||||
$endTime = $endCurrentTime;
|
||||
}
|
||||
|
||||
$lastStyle = end($currentFrameStyles);
|
||||
$frameStyle = clone $lastStyle;
|
||||
$frameStyle->resetTransformedStyles();
|
||||
if ($frameInformation->transform instanceof MatrixTransform) {
|
||||
$frameInformation->transform->applyToStyleContainer($frameStyle);
|
||||
}
|
||||
if ($frameInformation->colorTransform instanceof ColorTransform) {
|
||||
$frameInformation->colorTransform->applyToStyleContainer($frameStyle);
|
||||
}
|
||||
|
||||
$lastSavedStyle = end($savedFrameStyles);
|
||||
$savedStyle = clone $lastSavedStyle;
|
||||
$savedStyle->resetTransformedStyles();
|
||||
if ($frameInformation->transform instanceof MatrixTransform) {
|
||||
$frameInformation->transform->applyToStyleContainer($savedStyle);
|
||||
}
|
||||
if ($frameInformation->colorTransform instanceof ColorTransform) {
|
||||
$frameInformation->colorTransform->applyToStyleContainer($savedStyle);
|
||||
}
|
||||
|
||||
if ($frames === 0) {
|
||||
$currentFrameStyles[array_key_last($currentFrameStyles)] = $frameStyle;
|
||||
} else if (!$lastStyle->isEqualish($frameStyle)) {
|
||||
if (ADVANCED_MOTION_INTERPOLATION) {
|
||||
$transition = $lastStyle->doTransitionTo($frameStyle, $frame, $frame, max(0, $frame - 1), $frame);
|
||||
} else {
|
||||
$transition = $lastStyle->doTransitionTo($frameStyle, $frame, $frame, $frame, $frame);
|
||||
}
|
||||
if ($transition === false) {
|
||||
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($currentTime) . ",f,$debugInfo,0,0,0,af:" . ($af++) . ",";
|
||||
foreach ($currentFrameStyles as $i => $style) {
|
||||
$assHeader .= "{" . $style->toString($lastFrameStart, (1 / $currentState->frameRate), $currentFrameStyles[$i - 1] ?? null, $i === 0) . "}";
|
||||
}
|
||||
$dialogue[] = $assHeader . $assLine;
|
||||
|
||||
$lastLineStart = $currentTime;
|
||||
$lastFrameStart = $frame;
|
||||
$currentFrameStyles = [$savedStyle];
|
||||
} else if ($transition instanceof StyleContainer) {
|
||||
$currentFrameStyles[] = $transition;
|
||||
$savedFrameStyles[] = $savedStyle;
|
||||
}
|
||||
}
|
||||
++$frames;
|
||||
}
|
||||
|
||||
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($endTime) . ",f,$debugInfo,0,0,0,af:" . ($af++) . ",";
|
||||
foreach ($currentFrameStyles as $i => $style) {
|
||||
$assHeader .= "{" . $style->toString($lastFrameStart, (1 / $currentState->frameRate), $currentFrameStyles[$i - 1] ?? null, $i === 0) . "}";
|
||||
}
|
||||
$dialogue[] = $assHeader . $assLine;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $dialogue;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,7 +7,7 @@ class FillStyleRecord implements StyleRecord {
|
|||
/** @var Gradient|Color */
|
||||
public $fill;
|
||||
|
||||
public function __construct($fill){
|
||||
public function __construct($fill) {
|
||||
$this->fill = $fill;
|
||||
}
|
||||
}
|
|
@ -3,11 +3,62 @@
|
|||
namespace swf2ass;
|
||||
|
||||
class FrameInformation {
|
||||
public int $frame;
|
||||
private int $frameNumber;
|
||||
private int $frameOffset = 0;
|
||||
|
||||
/** @var ?ColorTransform */
|
||||
public ?ColorTransform $colorTransform = null;
|
||||
private float $frameRate;
|
||||
|
||||
/** @var ?MatrixTransform */
|
||||
public ?MatrixTransform $transform = null;
|
||||
private ?ViewFrame $frame;
|
||||
|
||||
|
||||
public function __construct(int $frameNumber, float $frameRate, ?ViewFrame $frame) {
|
||||
$this->frameNumber = $frameNumber;
|
||||
$this->frameRate = $frameRate;
|
||||
$this->frame = $frame;
|
||||
}
|
||||
|
||||
public function setFrameOffset(int $frameOffset) {
|
||||
$this->frameOffset = $frameOffset;
|
||||
}
|
||||
|
||||
public function getFrameOffset(): int {
|
||||
return $this->frameOffset;
|
||||
}
|
||||
|
||||
public function getFrame(): ?ViewFrame {
|
||||
return $this->frame;
|
||||
}
|
||||
|
||||
public function getFrameNumber(): int {
|
||||
return $this->frameNumber - $this->frameOffset;
|
||||
}
|
||||
|
||||
public function getStartTimeSeconds(): float {
|
||||
return $this->getFrameNumber() / $this->frameRate;
|
||||
}
|
||||
|
||||
public function getStartTimeMilliSeconds(): int {
|
||||
return $this->getStartTimeSeconds() * 1000;
|
||||
}
|
||||
|
||||
public function getEndTimeSeconds(): float {
|
||||
return $this->getStartTimeSeconds() + $this->getFrameDurationSeconds();
|
||||
}
|
||||
|
||||
public function getEndTimeMilliSeconds(): int {
|
||||
return $this->getEndTimeSeconds() * 1000;
|
||||
}
|
||||
|
||||
public function getFrameDurationSeconds(): float {
|
||||
return 1 / $this->frameRate;
|
||||
}
|
||||
|
||||
public function getFrameDurationMilliSeconds(): int {
|
||||
return $this->getFrameDurationSeconds() * 1000;
|
||||
}
|
||||
|
||||
|
||||
public function diff(FrameInformation $other): FrameInformation {
|
||||
return new FrameInformation($this->getFrameNumber() - $other->getFrameNumber(), $this->frameRate, null);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ interface Gradient {
|
|||
/**
|
||||
* @return GradientItem[]
|
||||
*/
|
||||
public function getItems() : array;
|
||||
public function getMatrixTransform() : ?MatrixTransform;
|
||||
public function getItems(): array;
|
||||
|
||||
public function getMatrixTransform(): ?MatrixTransform;
|
||||
}
|
|
@ -31,7 +31,7 @@ class JPEGBitmapDefinition extends BitmapDefinition {
|
|||
|
||||
$im->transformImageColorspace(\Imagick::COLORSPACE_SRGB);
|
||||
$resolution = $im->getImageGeometry();
|
||||
$size = new Coordinate($resolution["width"], $resolution["height"]);
|
||||
$size = new Vector2($resolution["width"], $resolution["height"]);
|
||||
|
||||
$pixels = array_fill(0, $size->y, array_fill(0, $size->x, new Color(0, 0, 0)));
|
||||
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
namespace swf2ass;
|
||||
|
||||
class LineRecord implements Record {
|
||||
public Coordinate $start;
|
||||
public Coordinate $coord;
|
||||
public Vector2 $start;
|
||||
public Vector2 $coord;
|
||||
|
||||
public function __construct(Coordinate $coord, Coordinate $start) {
|
||||
public function __construct(Vector2 $coord, Vector2 $start) {
|
||||
$this->coord = $coord;
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function getStart(): Coordinate {
|
||||
public function getStart(): Vector2 {
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,11 @@ class LineRecord implements Record {
|
|||
return new LineRecord($this->start, $this->coord);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, Coordinate $cursor): LineRecord {
|
||||
return new LineRecord($cursor->add(new Coordinate((int)$element->getAttribute("x"), (int)$element->getAttribute("y"))), $cursor);
|
||||
public static function fromXML(\DOMElement $element, Vector2 $cursor): LineRecord {
|
||||
return new LineRecord($cursor->add(new Vector2((int)$element->getAttribute("x"), (int)$element->getAttribute("y"))), $cursor);
|
||||
}
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): LineRecord {
|
||||
return new LineRecord($transform->applyToVector($this->coord), $transform->applyToVector($this->start));
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
namespace swf2ass;
|
||||
|
||||
|
||||
class LineStyleRecord implements StyleRecord {
|
||||
class LineStyleRecord implements StyleRecord {
|
||||
public int $width;
|
||||
public Color $color;
|
||||
|
||||
|
@ -12,7 +12,7 @@ class LineStyleRecord implements StyleRecord {
|
|||
//TODO: join/cap/etc style
|
||||
public bool $allow_close = true;
|
||||
|
||||
public function __construct(int $width, Color $color){
|
||||
public function __construct(int $width, Color $color) {
|
||||
$this->width = $width;
|
||||
$this->color = $color;
|
||||
|
||||
|
|
|
@ -12,10 +12,12 @@ class LinearGradient implements Gradient {
|
|||
$this->colors = $colors;
|
||||
$this->transform = $transform;
|
||||
}
|
||||
public function getItems() : array {
|
||||
|
||||
public function getItems(): array {
|
||||
return $this->colors;
|
||||
}
|
||||
public function getMatrixTransform() : ?MatrixTransform{
|
||||
|
||||
public function getMatrixTransform(): ?MatrixTransform {
|
||||
return $this->transform;
|
||||
}
|
||||
|
||||
|
|
203
src/Matrix2D.php
Normal file
203
src/Matrix2D.php
Normal file
|
@ -0,0 +1,203 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class Matrix2D {
|
||||
/** @var \SplFixedArray<numeric> */
|
||||
private \SplFixedArray $data;
|
||||
private Vector2 $size;
|
||||
|
||||
|
||||
/**
|
||||
* @param Vector2 $size
|
||||
* @param \SplFixedArray<numeric> $values
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(Vector2 $size, ?\SplFixedArray $initialize = null) {
|
||||
$this->size = $size;
|
||||
$this->data = $initialize !== null ? $initialize : \SplFixedArray::fromArray(array_fill(0, $size->x * $size->y, 0));
|
||||
if ($this->data->getSize() !== $size->x * $size->y) {
|
||||
throw new \InvalidArgumentException("Wrong sized Matrix initialization");
|
||||
}
|
||||
}
|
||||
|
||||
public static function from2DArray(array $data): Matrix2D {
|
||||
$size = new Vector2(count($data[0]), count($data));
|
||||
$newData = new \SplFixedArray($size->x * $size->y);
|
||||
foreach ($data as $y => $row) {
|
||||
foreach ($row as $x => $value) {
|
||||
$i = $size->x * $y + $x;
|
||||
$newData->offsetSet($i, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return new Matrix2D($size, $newData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Vector2 $position
|
||||
* @param int|float $value
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function set(Vector2 $position, $value) {
|
||||
$i = $this->size->x * $position->y + $position->x;
|
||||
if (!$this->data->offsetExists($i)) {
|
||||
throw new \OutOfRangeException("Index [{$position->x}, {$position->y}] out of range");
|
||||
}
|
||||
$this->data->offsetSet($i, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Vector2 $position
|
||||
* @return float|int
|
||||
*/
|
||||
public function get(Vector2 $position) {
|
||||
$i = $this->size->x * $position->y + $position->x;
|
||||
if (!$this->data->offsetExists($i)) {
|
||||
throw new \OutOfRangeException("Index [{$position->x}, {$position->y}] out of range");
|
||||
}
|
||||
return $this->data->offsetGet($i);
|
||||
}
|
||||
|
||||
private function internalGet($x, $y) {
|
||||
return $this->data->offsetGet($this->size->x * $y + $x);
|
||||
}
|
||||
|
||||
private function internalSet($x, $y, $value) {
|
||||
$this->data->offsetSet($this->size->x * $y + $x, $value);
|
||||
}
|
||||
|
||||
public function isSquare(): bool {
|
||||
return $this->size->equals($this->size->invert());
|
||||
}
|
||||
|
||||
public function getSize(): Vector2 {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function __clone() {
|
||||
$this->data = clone $this->data;
|
||||
}
|
||||
|
||||
public function add(Matrix2D $matrix): Matrix2D {
|
||||
if (!$this->size->equals($matrix->size)) {
|
||||
throw new \LogicException("Matrix sizes not equal");
|
||||
}
|
||||
|
||||
$result = clone $this;
|
||||
foreach ($result->data as $i => $value) {
|
||||
$result->data->offsetSet($i, $value + $matrix->data->offsetGet($i));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function substract(Matrix2D $matrix): Matrix2D {
|
||||
if (!$this->size->equals($matrix->size)) {
|
||||
throw new \LogicException("Matrix sizes not equal");
|
||||
}
|
||||
|
||||
$result = clone $this;
|
||||
foreach ($result->data as $i => $value) {
|
||||
$result->data->offsetSet($i, $value - $matrix->data->offsetGet($i));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function transpose(): Matrix2D {
|
||||
$result = new Matrix2D($this->size->invert());
|
||||
foreach ($result->data as $i => $value) {
|
||||
$x = $i % $this->size->x;
|
||||
$y = intdiv($i, $this->size->x);
|
||||
$result->internalSet($y, $x, $value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function isMatrixTransform(): bool {
|
||||
return $this->size->x === 3 and $this->size->y === 3 and $this->internalGet(0, 2) === 0 and $this->internalGet(1, 2) === 0 and $this->internalGet(2, 2) === 1;
|
||||
}
|
||||
|
||||
private function isVector2(): bool {
|
||||
return $this->size->x === 3 and $this->size->y === 1 and $this->internalGet(1, 0) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Naive Matrix product, O(n^3), with some special cases optimizations
|
||||
*
|
||||
* @param Matrix2D $matrix
|
||||
* @return Matrix2D
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function product(Matrix2D $matrix): Matrix2D {
|
||||
if ($this->size->x !== $matrix->size->y) {
|
||||
throw new \LogicException("Matrix A columns != B rows");
|
||||
}
|
||||
|
||||
if ($matrix->isMatrixTransform()) {
|
||||
// http://www.senocular.com/flash/tutorials/transformmatrix/
|
||||
if ($this->isMatrixTransform()) {
|
||||
$a1 = $this->internalGet(0, 0);
|
||||
$b1 = $this->internalGet(1, 0);
|
||||
$c1 = $this->internalGet(0, 1);
|
||||
$d1 = $this->internalGet(1, 1);
|
||||
$tx1 = $this->internalGet(0, 2);
|
||||
$ty1 = $this->internalGet(1, 2);
|
||||
|
||||
$a2 = $matrix->internalGet(0, 0);
|
||||
$b2 = $matrix->internalGet(1, 0);
|
||||
$c2 = $matrix->internalGet(0, 1);
|
||||
$d2 = $matrix->internalGet(1, 1);
|
||||
$tx2 = $matrix->internalGet(0, 2);
|
||||
$ty2 = $matrix->internalGet(1, 2);
|
||||
|
||||
return Matrix2D::from2DArray([[$a1 * $a2 + $b1 * $c2, $a1 * $b2 + $b1 * $d2, 0], [$c1 * $a2 + $d1 * $c2, $c1 + $b2 + $d1 * $d2, 0], [$tx1 * $a2 + $ty1 * $c2 + $tx2, $tx1 * $b2 + $ty1 * $d2 + $ty2, 1]]);
|
||||
} elseif ($this->isVector2()) {
|
||||
$x = $this->internalGet(0, 0);
|
||||
$y = $this->internalGet(1, 0);
|
||||
|
||||
$a = $matrix->internalGet(0, 0);
|
||||
$b = $matrix->internalGet(1, 0);
|
||||
$c = $matrix->internalGet(0, 1);
|
||||
$d = $matrix->internalGet(1, 1);
|
||||
$tx = $matrix->internalGet(0, 2);
|
||||
$ty = $matrix->internalGet(1, 2);
|
||||
|
||||
return Matrix2D::from2DArray([[$x * $a + $y * $c + $tx, $x * $b + $y * $d + $ty, 1]]);
|
||||
}
|
||||
}
|
||||
|
||||
//Fallback for all other types
|
||||
|
||||
$result = new Matrix2D(new Vector2($this->size->x, $matrix->size->y));
|
||||
for ($i = 0; $i < $result->size->x; ++$i) {
|
||||
for ($j = 0; $j < $result->size->y; ++$j) {
|
||||
$sum = 0;
|
||||
for ($k = 0; $k < $this->size->y; ++$k) {
|
||||
$sum += $this->internalGet($i, $k) * $matrix->internalGet($k, $j);
|
||||
}
|
||||
$result->internalSet($i, $j, $sum);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function equals(Matrix2D $other, $epsilon = Constants::EPSILON): bool {
|
||||
if (!$this->size->equals($other->size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->data as $i => $value) {
|
||||
if (abs($value - $other->data->offsetGet($i)) > $epsilon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,53 +4,124 @@ namespace swf2ass;
|
|||
|
||||
|
||||
class MatrixTransform {
|
||||
public $scaleX;
|
||||
public $scaleY;
|
||||
public $skewX;
|
||||
public $skewY;
|
||||
public $transX;
|
||||
public $transY;
|
||||
private const IDENTITY = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
|
||||
|
||||
public function __construct($scaleX = 1, $scaleY = 1, $skewX = 0, $skewY = 0, $transX = 0, $transY = 0) {
|
||||
$this->scaleX = $scaleX;
|
||||
$this->scaleY = $scaleY;
|
||||
$this->skewX = $skewX;
|
||||
$this->skewY = $skewY;
|
||||
$this->transX = $transX;
|
||||
$this->transY = $transY;
|
||||
private Matrix2D $matrix;
|
||||
|
||||
public function __construct(?Vector2 $scale, ?Vector2 $rotateSkew, ?Vector2 $translation) {
|
||||
$matrix = self::IDENTITY;
|
||||
|
||||
if ($scale !== null) {
|
||||
$matrix[0][0] = $scale->x;
|
||||
$matrix[1][1] = $scale->y;
|
||||
}
|
||||
if ($rotateSkew !== null) {
|
||||
$matrix[1][0] = $rotateSkew->x;
|
||||
$matrix[0][1] = $rotateSkew->y;
|
||||
}
|
||||
if ($translation !== null) {
|
||||
$matrix[2][0] = $translation->x;
|
||||
$matrix[2][1] = $translation->y;
|
||||
}
|
||||
$this->matrix = Matrix2D::from2DArray($matrix);
|
||||
}
|
||||
|
||||
public static function identity(): MatrixTransform {
|
||||
return new MatrixTransform(new Vector2(1, 1), new Vector2(0, 0), new Vector2(0, 0));
|
||||
}
|
||||
|
||||
public function combine(MatrixTransform $other): MatrixTransform {
|
||||
$result = clone $this;
|
||||
$result->matrix = $this->matrix->product($other->matrix);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getScaleX() {
|
||||
return $this->matrix->get(new Vector2(0, 0));
|
||||
}
|
||||
|
||||
public function getScaleY() {
|
||||
return $this->matrix->get(new Vector2(1, 1));
|
||||
}
|
||||
|
||||
public function getScale(): Vector2 {
|
||||
return new Vector2($this->getScaleX(), $this->getScaleY());
|
||||
}
|
||||
|
||||
public function getRotateSkewX() {
|
||||
return $this->matrix->get(new Vector2(0, 1));
|
||||
}
|
||||
|
||||
public function getRotateSkewY() {
|
||||
return $this->matrix->get(new Vector2(1, 0));
|
||||
}
|
||||
|
||||
public function getRotateSkew(): Vector2 {
|
||||
return new Vector2($this->getRotateSkewX(), $this->getRotateSkewY());
|
||||
}
|
||||
|
||||
public function getTranslationX() {
|
||||
return $this->matrix->get(new Vector2(0, 2));
|
||||
}
|
||||
|
||||
public function getTranslationY() {
|
||||
return $this->matrix->get(new Vector2(1, 2));
|
||||
}
|
||||
|
||||
public function getTranslation(): Vector2 {
|
||||
return new Vector2($this->getTranslationX(), $this->getTranslationY());
|
||||
}
|
||||
|
||||
public function applyToVector(Vector2 $vector): Vector2 {
|
||||
$result = Matrix2D::from2DArray([[$vector->x, $vector->y, 1]])->product($this->matrix);
|
||||
return new Vector2($result->get(new Vector2(0, 0)), $result->get(new Vector2(1, 0)));
|
||||
/*
|
||||
return
|
||||
$vector->vectorMultiply($this->getScale()) //scale
|
||||
->add($vector->vectorMultiply($this->getRotateSkew())->invert()) //skew
|
||||
->add($this->getTranslation()); //translate
|
||||
*/
|
||||
}
|
||||
|
||||
public function applyToShape(Shape $shape): Shape {
|
||||
$newShape = new Shape();
|
||||
foreach ($shape->edges as $edge) {
|
||||
$newShape->edges[] = $edge->applyMatrixTransform($this);
|
||||
}
|
||||
|
||||
return $newShape;
|
||||
}
|
||||
|
||||
public function applyToStyleContainer(StyleContainer $styles): StyleContainer {
|
||||
if ($this->scaleX !== null) {
|
||||
if ($this->scaleX < 0) {
|
||||
if ($this->scale !== null) {
|
||||
|
||||
if ($this->scale->x < 0) {
|
||||
$styles->fry = 180;
|
||||
}
|
||||
$styles->fscx = round(abs($this->scaleX) * 100, 2);
|
||||
$styles->fscx = round(abs($this->scale->x) * 100, 2);
|
||||
|
||||
if ($this->scale->y < 0) {
|
||||
$styles->frx = 180;
|
||||
}
|
||||
$styles->fscy = round(abs($this->scale->y) * 100, 2);
|
||||
|
||||
} else {
|
||||
$styles->fry = null;
|
||||
$styles->fscx = null;
|
||||
}
|
||||
if ($this->scaleY !== null) {
|
||||
if ($this->scaleY < 0) {
|
||||
$styles->frx = 180;
|
||||
}
|
||||
$styles->fscy = round(abs($this->scaleY) * 100, 2);
|
||||
} else {
|
||||
$styles->frx = null;
|
||||
$styles->fscy = null;
|
||||
}
|
||||
if ($this->skewX !== null) {
|
||||
$styles->fax = round($this->skewX, 4);
|
||||
|
||||
if ($this->skew !== null) {
|
||||
$styles->fax = round($this->skew->x, 4);
|
||||
$styles->fay = round($this->skew->y, 4);
|
||||
} else {
|
||||
$styles->fax = null;
|
||||
}
|
||||
if ($this->skewY !== null) {
|
||||
$styles->fay = round($this->skewY, 4);
|
||||
} else {
|
||||
$styles->fay = null;
|
||||
}
|
||||
if ($this->transX !== null) {
|
||||
$styles->pos = new Coordinate((int)$this->transX, (int)$this->transY);
|
||||
|
||||
if ($this->translation !== null) {
|
||||
$styles->pos = ($styles->pos ?? new Vector2(0, 0))->add($this->translation);
|
||||
} else {
|
||||
$styles->pos = null;
|
||||
}
|
||||
|
@ -58,8 +129,12 @@ class MatrixTransform {
|
|||
return $styles;
|
||||
}
|
||||
|
||||
public function equals(MatrixTransform $other, $epsilon = Constants::EPSILON): bool {
|
||||
return $this->matrix->equals($other->matrix, $epsilon);
|
||||
}
|
||||
|
||||
static function fromXML(\DOMElement $element): MatrixTransform {
|
||||
return new MatrixTransform($element->hasAttribute("scaleX") ? $element->getAttribute("scaleX") : null, $element->hasAttribute("scaleY") ? $element->getAttribute("scaleY") : null, $element->hasAttribute("skewX") ? $element->getAttribute("skewX") : null, $element->hasAttribute("skewY") ? $element->getAttribute("skewY") : null, $element->hasAttribute("transX") ? $element->getAttribute("transX") : null, $element->hasAttribute("transY") ? $element->getAttribute("transY") : null);
|
||||
return new MatrixTransform($element->hasAttribute("scaleX") ? new Vector2(floatval($element->getAttribute("scaleX")), floatval($element->getAttribute("scaleY"))) : null, $element->hasAttribute("skewX") ? new Vector2(floatval($element->getAttribute("skewX")), floatval($element->getAttribute("skewY"))) : null, $element->hasAttribute("transX") ? new Vector2(intval($element->getAttribute("transX")), intval($element->getAttribute("transY"))) : null,);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,15 +3,15 @@
|
|||
namespace swf2ass;
|
||||
|
||||
class MoveRecord implements Record {
|
||||
public Coordinate $start;
|
||||
public Coordinate $coord;
|
||||
public Vector2 $start;
|
||||
public Vector2 $coord;
|
||||
|
||||
public function __construct(Coordinate $coord, Coordinate $start) {
|
||||
public function __construct(Vector2 $coord, Vector2 $start) {
|
||||
$this->coord = $coord;
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function getStart(): Coordinate {
|
||||
public function getStart(): Vector2 {
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,11 @@ class MoveRecord implements Record {
|
|||
return new MoveRecord($this->start, $this->coord);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, Coordinate $cursor): MoveRecord {
|
||||
return new MoveRecord(new Coordinate((int)$element->getAttribute("x"), (int)$element->getAttribute("y")), $cursor);
|
||||
public static function fromXML(\DOMElement $element, Vector2 $cursor): MoveRecord {
|
||||
return new MoveRecord(new Vector2((int)$element->getAttribute("x"), (int)$element->getAttribute("y")), $cursor);
|
||||
}
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): MoveRecord {
|
||||
return new MoveRecord($transform->applyToVector($this->coord), $transform->applyToVector($this->start));
|
||||
}
|
||||
}
|
9
src/MultiFrameObjectDefinition.php
Normal file
9
src/MultiFrameObjectDefinition.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
interface MultiFrameObjectDefinition extends ObjectDefinition {
|
||||
public function nextFrame(): ViewFrame;
|
||||
|
||||
public function hasFrame(): bool;
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace swf2ass;
|
|||
|
||||
interface ObjectDefinition {
|
||||
|
||||
public function getId();
|
||||
public function getShapeList() : DrawPathList;
|
||||
public function getObjectId(): int;
|
||||
|
||||
public function getShapeList(): DrawPathList;
|
||||
}
|
|
@ -2,15 +2,12 @@
|
|||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
use mysql_xdevapi\Exception;
|
||||
|
||||
class PathSegment {
|
||||
/** @var VisitedPoint[] */
|
||||
public array $points;
|
||||
|
||||
|
||||
public function __construct(Coordinate $start) {
|
||||
public function __construct(Vector2 $start) {
|
||||
$this->points = [new VisitedPoint($start, false)];
|
||||
}
|
||||
|
||||
|
@ -18,15 +15,15 @@ class PathSegment {
|
|||
$this->points = array_reverse($this->points, false);
|
||||
}
|
||||
|
||||
public function add_point(VisitedPoint $point){
|
||||
public function add_point(VisitedPoint $point) {
|
||||
$this->points[] = $point;
|
||||
}
|
||||
|
||||
public function start(): Coordinate {
|
||||
public function start(): Vector2 {
|
||||
return reset($this->points)->pos;
|
||||
}
|
||||
|
||||
public function end(): Coordinate {
|
||||
public function end(): Vector2 {
|
||||
return end($this->points)->pos;
|
||||
}
|
||||
|
||||
|
@ -38,27 +35,27 @@ class PathSegment {
|
|||
return $this->start()->equals($this->end());
|
||||
}
|
||||
|
||||
public function swap(PathSegment $other){
|
||||
public function swap(PathSegment $other) {
|
||||
[$this->points, $other->points] = [$other->points, $this->points];
|
||||
}
|
||||
|
||||
public function merge(PathSegment $other){
|
||||
public function merge(PathSegment $other) {
|
||||
$this->points = array_merge($this->points, array_slice($other->points, 1));
|
||||
}
|
||||
|
||||
public function try_merge(PathSegment $other, bool $directed) : bool{
|
||||
if($other->end()->equals($this->start())){
|
||||
public function try_merge(PathSegment $other, bool $directed): bool {
|
||||
if ($other->end()->equals($this->start())) {
|
||||
$this->swap($other);
|
||||
$this->merge($other);
|
||||
return true;
|
||||
}else if($this->end()->equals($other->start())){
|
||||
} else if ($this->end()->equals($other->start())) {
|
||||
$this->merge($other);
|
||||
return true;
|
||||
}else if(!$directed and $this->end()->equals($other->end())){
|
||||
} else if (!$directed and $this->end()->equals($other->end())) {
|
||||
$other->flip();
|
||||
$this->merge($other);
|
||||
return true;
|
||||
}else if(!$directed and $this->start()->equals($other->start())){
|
||||
} else if (!$directed and $this->start()->equals($other->start())) {
|
||||
$other->flip();
|
||||
$this->swap($other);
|
||||
$this->merge($other);
|
||||
|
@ -68,25 +65,25 @@ class PathSegment {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function getShape() : Shape {
|
||||
if($this->is_empty()){
|
||||
throw new Exception();
|
||||
public function getShape(): Shape {
|
||||
if ($this->is_empty()) {
|
||||
throw new \Exception();
|
||||
}
|
||||
$shape = new Shape();
|
||||
|
||||
$first = reset($this->points);
|
||||
|
||||
$pos = new Coordinate(0, 0);
|
||||
$pos = new Vector2(0, 0);
|
||||
$shape->edges[] = new MoveRecord($first->pos, $pos);
|
||||
$pos = $first->pos;
|
||||
|
||||
while (($point = next($this->points)) !== false){
|
||||
if(!$point->is_bezier_control){
|
||||
while (($point = next($this->points)) !== false) {
|
||||
if (!$point->is_bezier_control) {
|
||||
$shape->edges[] = new LineRecord($point->pos, $pos);
|
||||
$pos = $point->pos;
|
||||
}else{
|
||||
} else {
|
||||
$end = next($this->points);
|
||||
if($end === false){
|
||||
if ($end === false) {
|
||||
throw new \Exception("Bezier without endpoint");
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace swf2ass;
|
|||
|
||||
class PendingPath {
|
||||
|
||||
/** @var PathSegment[] */
|
||||
/** @var PathSegment[] */
|
||||
public array $segments = [];
|
||||
|
||||
|
||||
|
@ -17,28 +17,28 @@ class PendingPath {
|
|||
* @param PathSegment $new_segment
|
||||
* @param bool $directed
|
||||
*/
|
||||
public function merge_path(PathSegment $new_segment, bool $directed){
|
||||
if(!$new_segment->is_empty()){
|
||||
public function merge_path(PathSegment $new_segment, bool $directed) {
|
||||
if (!$new_segment->is_empty()) {
|
||||
$merged = null;
|
||||
foreach ($this->segments as $i => $segment){
|
||||
if($segment->try_merge($new_segment, $directed)){
|
||||
foreach ($this->segments as $i => $segment) {
|
||||
if ($segment->try_merge($new_segment, $directed)) {
|
||||
unset($this->segments[$i]);
|
||||
$merged = $segment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($merged === null){
|
||||
if ($merged === null) {
|
||||
$this->segments[] = $new_segment;
|
||||
}else{
|
||||
} else {
|
||||
$this->merge_path($merged, $directed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getShape() : Shape {
|
||||
public function getShape(): Shape {
|
||||
$shape = new Shape();
|
||||
foreach ($this->segments as $segment){
|
||||
foreach ($this->segments as $segment) {
|
||||
$shape->edges = array_merge($shape->edges, $segment->getShape()->edges);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ class PendingPathMap {
|
|||
|
||||
}
|
||||
|
||||
public function merge_path(ActivePath $path, bool $directed){
|
||||
if(!isset($this->map[$path->style])){
|
||||
public function merge_path(ActivePath $path, bool $directed) {
|
||||
if (!isset($this->map[$path->style])) {
|
||||
$this->map[$path->style] = new PendingPath();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
namespace swf2ass;
|
||||
|
||||
class QuadraticCurveRecord implements Record {
|
||||
public Coordinate $start;
|
||||
public Coordinate $control;
|
||||
public Coordinate $anchor;
|
||||
public Vector2 $start;
|
||||
public Vector2 $control;
|
||||
public Vector2 $anchor;
|
||||
|
||||
public function __construct(Coordinate $control, Coordinate $anchor, Coordinate $start) {
|
||||
public function __construct(Vector2 $control, Vector2 $anchor, Vector2 $start) {
|
||||
$this->control = $control;
|
||||
$this->anchor = $anchor;
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function getStart(): Coordinate {
|
||||
public function getStart(): Vector2 {
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
|
@ -21,9 +21,13 @@ class QuadraticCurveRecord implements Record {
|
|||
return new QuadraticCurveRecord($this->control, $this->start, $this->anchor);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, Coordinate $cursor): QuadraticCurveRecord {
|
||||
$control = $cursor->add(new Coordinate((int)$element->getAttribute("x1"), (int)$element->getAttribute("y1")));
|
||||
$anchor = $control->add(new Coordinate((int)$element->getAttribute("x2"), (int)$element->getAttribute("y2")));
|
||||
public function applyMatrixTransform(MatrixTransform $transform): QuadraticCurveRecord {
|
||||
return new QuadraticCurveRecord($transform->applyToVector($this->control), $transform->applyToVector($this->anchor), $transform->applyToVector($this->start));
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, Vector2 $cursor): QuadraticCurveRecord {
|
||||
$control = $cursor->add(new Vector2((int)$element->getAttribute("x1"), (int)$element->getAttribute("y1")));
|
||||
$anchor = $control->add(new Vector2((int)$element->getAttribute("x2"), (int)$element->getAttribute("y2")));
|
||||
|
||||
return new QuadraticCurveRecord($control, $anchor, $cursor);
|
||||
}
|
||||
|
|
|
@ -12,10 +12,12 @@ class RadialGradient implements Gradient {
|
|||
$this->colors = $colors;
|
||||
$this->transform = $transform;
|
||||
}
|
||||
public function getItems() : array {
|
||||
|
||||
public function getItems(): array {
|
||||
return $this->colors;
|
||||
}
|
||||
public function getMatrixTransform() : ?MatrixTransform{
|
||||
|
||||
public function getMatrixTransform(): ?MatrixTransform {
|
||||
return $this->transform;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
namespace swf2ass;
|
||||
|
||||
interface Record {
|
||||
public function getStart(): Coordinate;
|
||||
public function getStart(): Vector2;
|
||||
|
||||
public function reverse(): Record;
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): Record;
|
||||
}
|
|
@ -6,15 +6,15 @@ namespace swf2ass;
|
|||
class Rectangle implements ComplexShape {
|
||||
|
||||
|
||||
public Coordinate $topLeft;
|
||||
public Coordinate $bottomRight;
|
||||
public Vector2 $topLeft;
|
||||
public Vector2 $bottomRight;
|
||||
|
||||
public function __construct(Coordinate $topLeft, Coordinate $bottomRight) {
|
||||
public function __construct(Vector2 $topLeft, Vector2 $bottomRight) {
|
||||
$this->topLeft = $topLeft;
|
||||
$this->bottomRight = $bottomRight;
|
||||
}
|
||||
|
||||
public function inBounds(Coordinate $pos): bool {
|
||||
public function inBounds(Vector2 $pos): bool {
|
||||
return $pos->x >= $this->topLeft->x and $pos->y >= $this->topLeft->y and $pos->x <= $this->bottomRight->x and $pos->y <= $this->bottomRight->y;
|
||||
}
|
||||
|
||||
|
@ -31,27 +31,28 @@ class Rectangle implements ComplexShape {
|
|||
}
|
||||
|
||||
public function draw(): array {
|
||||
return [new LineRecord(new Coordinate($this->topLeft->x, $this->bottomRight->y), $this->topLeft), new LineRecord($this->bottomRight, new Coordinate($this->topLeft->x, $this->bottomRight->y)), new LineRecord(new Coordinate($this->bottomRight->x, $this->topLeft->y), $this->bottomRight), new LineRecord($this->topLeft, new Coordinate($this->bottomRight->x, $this->topLeft->y)),];
|
||||
return [new LineRecord(new Vector2($this->topLeft->x, $this->bottomRight->y), $this->topLeft), new LineRecord($this->bottomRight, new Vector2($this->topLeft->x, $this->bottomRight->y)), new LineRecord(new Vector2($this->bottomRight->x, $this->topLeft->y), $this->bottomRight), new LineRecord($this->topLeft, new Vector2($this->bottomRight->x, $this->topLeft->y)),];
|
||||
}
|
||||
|
||||
public function drawOpen(): array {
|
||||
return [new LineRecord(new Coordinate($this->topLeft->x, $this->bottomRight->y), $this->topLeft), new LineRecord($this->bottomRight, new Coordinate($this->topLeft->x, $this->bottomRight->y)), new LineRecord(new Coordinate($this->bottomRight->x, $this->topLeft->y), $this->bottomRight),];
|
||||
return [new LineRecord(new Vector2($this->topLeft->x, $this->bottomRight->y), $this->topLeft), new LineRecord($this->bottomRight, new Vector2($this->topLeft->x, $this->bottomRight->y)), new LineRecord(new Vector2($this->bottomRight->x, $this->topLeft->y), $this->bottomRight),];
|
||||
}
|
||||
|
||||
|
||||
public function divide($size): Rectangle {
|
||||
return new Rectangle($this->topLeft->divide($size), $this->bottomRight->divide($size));
|
||||
}
|
||||
|
||||
public function multiply($size): Rectangle {
|
||||
return new Rectangle($this->topLeft->multiply($size), $this->bottomRight->multiply($size));
|
||||
}
|
||||
|
||||
public function toPixel($twipSize = TWIP_SIZE): Rectangle {
|
||||
public function toPixel($twipSize = Constants::TWIP_SIZE): Rectangle {
|
||||
return $this->divide($twipSize);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element): Rectangle {
|
||||
return new Rectangle(new Coordinate($element->getAttribute("left"), $element->getAttribute("top")), new Coordinate($element->getAttribute("right"), $element->getAttribute("bottom")));
|
||||
return new Rectangle(new Vector2((int)$element->getAttribute("left"), (int)$element->getAttribute("top")), new Vector2((int)$element->getAttribute("right"), (int)$element->getAttribute("bottom")));
|
||||
}
|
||||
|
||||
public static function fromData($bitdata, &$offset): Rectangle {
|
||||
|
@ -71,6 +72,6 @@ class Rectangle implements ComplexShape {
|
|||
$yMax = Utils::binary2dec(substr($bitdata, $offset, $nbits));
|
||||
$offset += $nbits;
|
||||
|
||||
return new Rectangle(new Coordinate($xMin, $yMin), new Coordinate($xMax, $yMax));
|
||||
return new Rectangle(new Vector2($xMin, $yMin), new Vector2($xMax, $yMax));
|
||||
}
|
||||
}
|
||||
|
|
24
src/RenderedFrame.php
Normal file
24
src/RenderedFrame.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class RenderedFrame {
|
||||
/** @var RenderedObject[] */
|
||||
private array $list = [];
|
||||
|
||||
public function __construct() {
|
||||
|
||||
}
|
||||
|
||||
public function add(RenderedObject $ob) {
|
||||
$this->list[] = $ob;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RenderedObject[]
|
||||
*/
|
||||
public function getObjects(): array {
|
||||
return $this->list;
|
||||
}
|
||||
}
|
23
src/RenderedObject.php
Normal file
23
src/RenderedObject.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class RenderedObject {
|
||||
/** @var int[] */
|
||||
public array $depth;
|
||||
public int $objectId;
|
||||
public DrawPathList $drawPathList;
|
||||
public ?Shape $clip;
|
||||
public ColorTransform $colorTransform;
|
||||
public MatrixTransform $matrixTransform;
|
||||
|
||||
public function __construct(array $depth, int $objectId, DrawPathList $drawPathList, ColorTransform $colorTransform, MatrixTransform $matrixTransform, ?Shape $clip = null) {
|
||||
$this->depth = $depth;
|
||||
$this->objectId = $objectId;
|
||||
$this->drawPathList = $drawPathList;
|
||||
$this->colorTransform = $colorTransform;
|
||||
$this->matrixTransform = $matrixTransform;
|
||||
$this->clip = $clip;
|
||||
}
|
||||
}
|
91
src/SWFProcessor.php
Normal file
91
src/SWFProcessor.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class SWFProcessor extends SWFTreeProcessor {
|
||||
const BACKGROUND_OBJECT_ID = 0;
|
||||
const BACKGROUND_OBJECT_DEPTH = 0;
|
||||
|
||||
|
||||
private FillStyleRecord $background;
|
||||
|
||||
private float $frameRate;
|
||||
|
||||
private $audio = null;
|
||||
|
||||
private Rectangle $viewPort;
|
||||
|
||||
public function __construct(\DOMElement $root) {
|
||||
parent::__construct(0, $root);
|
||||
|
||||
$this->background = new FillStyleRecord(new Color(255, 255, 255));
|
||||
|
||||
$rect = $root->getElementsByTagName("size")->item(0)->getElementsByTagName("Rectangle")->item(0);
|
||||
if ($rect instanceof \DOMElement and $rect->nodeName === "Rectangle") {
|
||||
$this->viewPort = Rectangle::fromXML($rect);
|
||||
} else {
|
||||
throw new \Exception("Could not find viewport");
|
||||
}
|
||||
|
||||
$this->frameRate = $root->getAttribute("framerate");
|
||||
}
|
||||
|
||||
public function getFrameRate(): float {
|
||||
return $this->frameRate;
|
||||
}
|
||||
|
||||
public function getViewPort(): Rectangle {
|
||||
return $this->viewPort;
|
||||
}
|
||||
|
||||
public function getAudio() {
|
||||
return $this->audio;
|
||||
}
|
||||
|
||||
protected function process(): ?string {
|
||||
$node = $this->current();
|
||||
if ($node === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ($node->nodeName) {
|
||||
case "SetBackgroundColor":
|
||||
//TODO: implement Gradient
|
||||
$this->background = new FillStyleRecord(Color::fromXML($node->getElementsByTagName("color")->item(0)->getElementsByTagName("Color")->item(0)));
|
||||
return $node->nodeName;
|
||||
case "SoundStreamHead":
|
||||
case "SoundStreamHead2":
|
||||
$this->audio = (object)["node" => $node, "start" => null, "content" => [],];
|
||||
return $node->nodeName;
|
||||
case "DefineSound":
|
||||
$this->audio = (object)["node" => $node, "start" => $this->getFrame(), "content" => []];
|
||||
return $node->nodeName;
|
||||
case "SoundStreamBlock":
|
||||
if ($this->audio !== null) {
|
||||
if ($this->audio->start === null) {
|
||||
$this->audio->start = $this->getFrame();
|
||||
}
|
||||
//$audio .= substr($data, 2);
|
||||
$this->audio->content[] = [$this->getFrame(), base64_decode(trim($node->textContent))];
|
||||
}
|
||||
return $node->nodeName;
|
||||
}
|
||||
|
||||
return parent::process();
|
||||
}
|
||||
|
||||
public function nextFrameOutput(): ?FrameInformation {
|
||||
$actions = $actions ?? new ActionList();
|
||||
$frame = $this->nextFrame($actions);
|
||||
|
||||
if ($frame === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//TODO: actions?
|
||||
|
||||
$frame->addChild(self::BACKGROUND_OBJECT_DEPTH, new ViewFrame(self::BACKGROUND_OBJECT_ID, new DrawPathList([DrawPath::fill($this->background, new Shape($this->getViewPort()->draw()))])));
|
||||
return new FrameInformation($this->frame - 1, $this->frameRate, $frame);
|
||||
}
|
||||
}
|
198
src/SWFTreeProcessor.php
Normal file
198
src/SWFTreeProcessor.php
Normal file
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class SWFTreeProcessor {
|
||||
protected \DOMElement $root;
|
||||
protected ViewLayout $layout;
|
||||
/** @var ObjectDefinition[] */
|
||||
protected array $objects = [];
|
||||
protected ?\DOMElement $currentElement = null;
|
||||
|
||||
protected int $frame;
|
||||
|
||||
public function __construct(int $objectId, \DOMElement $root) {
|
||||
$this->frame = 0;
|
||||
$this->root = $root;
|
||||
$this->layout = new ViewLayout($objectId, null);
|
||||
|
||||
$e = $this->root->getElementsByTagName("tags")->item(0);
|
||||
if ($e instanceof \DOMElement) {
|
||||
$e = $e->firstChild;
|
||||
do {
|
||||
$e = $e->nextSibling;
|
||||
} while ($e !== null and !($e instanceof \DOMElement));
|
||||
$this->currentElement = $e;
|
||||
} else {
|
||||
throw new \Exception("Could not find elements");
|
||||
}
|
||||
}
|
||||
|
||||
public function getFrame(): int {
|
||||
return $this->frame;
|
||||
}
|
||||
|
||||
protected function next() {
|
||||
$e = $this->currentElement;
|
||||
do {
|
||||
$e = $e->nextSibling;
|
||||
} while ($e !== null and !($e instanceof \DOMElement));
|
||||
$this->currentElement = $e;
|
||||
}
|
||||
|
||||
protected function current(): ?\DOMElement {
|
||||
return $this->currentElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectDefinition[]
|
||||
*/
|
||||
public function getObjects(): array {
|
||||
return $this->objects;
|
||||
}
|
||||
|
||||
protected function process(): ?string {
|
||||
$node = $this->current();
|
||||
if ($node === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ($node->nodeName) {
|
||||
case "DefineShape":
|
||||
case "DefineShape2":
|
||||
case "DefineShape3":
|
||||
case "DefineShape4":
|
||||
case "DefineShape5":
|
||||
$shape = ShapeDefinition::fromXML($node);
|
||||
$this->objects[$shape->getObjectId()] = $shape;
|
||||
break;
|
||||
case "DefineSprite":
|
||||
|
||||
$objectID = (int)$node->getAttribute("objectID");
|
||||
$framesCount = (int)$node->getAttribute("frames");
|
||||
$spriteTree = new SWFTreeProcessor($objectID, $node);
|
||||
$actions = new ActionList();
|
||||
/** @var ViewFrame[] $frames */
|
||||
$frames = [];
|
||||
while (($frame = $spriteTree->nextFrame($actions)) !== null) {
|
||||
$frames[] = $frame;
|
||||
}
|
||||
|
||||
$sprite = new SpriteDefinition($objectID, $frames);
|
||||
|
||||
$this->objects[$sprite->getObjectId()] = $sprite;
|
||||
break;
|
||||
case "DefineBitsLossless":
|
||||
case "DefineBitsLossless2":
|
||||
$bitmap = BitmapDefinition::fromXML($node);
|
||||
$this->objects[$bitmap->getObjectId()] = $bitmap;
|
||||
break;
|
||||
case "DefineBitsJPEG2":
|
||||
case "DefineBitsJPEG3":
|
||||
$bitmap = JPEGBitmapDefinition::fromXML($node);
|
||||
$this->objects[$bitmap->getObjectId()] = $bitmap;
|
||||
break;
|
||||
case "RemoveObject":
|
||||
case "RemoveObject2":
|
||||
$this->layout->remove((int)$node->getAttribute("depth"));
|
||||
break;
|
||||
case "PlaceObject2":
|
||||
case "PlaceObject3":
|
||||
$depth = (int)$node->getAttribute("depth");
|
||||
|
||||
$objectID = $node->hasAttribute("objectID") ? (int)$node->getAttribute("objectID") : null;
|
||||
$clipDepth = $node->hasAttribute("clipDepth") ? (int)$node->getAttribute("clipDepth") : null;
|
||||
|
||||
$replace = $node->getAttribute("replace") === "1";
|
||||
|
||||
$object = $objectID === null ? $this->layout->get($depth) : ($this->objects[$objectID] ?? null);
|
||||
if ($object === null) {
|
||||
var_dump("Object oid:$objectID depth:$depth not found");
|
||||
/*if($replace){
|
||||
$this->layout->remove($depth);
|
||||
}*/
|
||||
//TODO: insert bogus one
|
||||
$this->layout->remove($depth);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$transform = null;
|
||||
$transformNodes = $node->getElementsByTagName("transform");
|
||||
if ($transformNodes->count() > 0) {
|
||||
$transform = MatrixTransform::fromXML($transformNodes->item(0)->getElementsByTagName("Transform")->item(0));
|
||||
//TODO: multiple transforms!???
|
||||
}
|
||||
|
||||
|
||||
$colorTransform = null;
|
||||
$colorTransformNodes = $node->getElementsByTagName("colorTransform");
|
||||
if ($colorTransformNodes->count() > 0) {
|
||||
//TODO: add more modes?
|
||||
$colorTransform = ColorTransform::fromXML($colorTransformNodes->item(0)->getElementsByTagName("ColorTransform2")->item(0));
|
||||
}
|
||||
|
||||
|
||||
$currentObject = $this->layout->get($depth);
|
||||
|
||||
|
||||
if ($replace and $currentObject !== null) {
|
||||
if ($currentObject->getObjectId() === $object->getObjectId()) {
|
||||
if ($transform !== null) {
|
||||
$currentObject->setMatrixTransform($transform);
|
||||
}
|
||||
if ($colorTransform !== null) {
|
||||
$currentObject->setColorTransform($colorTransform);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$view = $clipDepth !== null ? new ClippingViewLayout($clipDepth, $objectID, $object, $this->layout) : new ViewLayout($objectID, $object, $this->layout);
|
||||
$view->setMatrixTransform($transform);
|
||||
$view->setColorTransform($colorTransform);
|
||||
|
||||
if ($replace) {
|
||||
$this->layout->replace($depth, $view);
|
||||
} else {
|
||||
$this->layout->place($depth, $view);
|
||||
}
|
||||
|
||||
break;
|
||||
case "ShowFrame":
|
||||
break;
|
||||
case "End":
|
||||
break;
|
||||
default:
|
||||
//TODO
|
||||
var_dump($node->nodeName);
|
||||
}
|
||||
|
||||
return $node->nodeName;
|
||||
}
|
||||
|
||||
public function getViewLayout(): ViewLayout {
|
||||
return $this->layout;
|
||||
}
|
||||
|
||||
public function nextFrame(ActionList $actions): ?ViewFrame {
|
||||
while (($nodeName = $this->process()) !== null) {
|
||||
$this->next();
|
||||
|
||||
if ($nodeName === "ShowFrame") {
|
||||
break;
|
||||
} else if ($nodeName === "End" and $this->frame === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($nodeName === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
++$this->frame;
|
||||
//TODO $this->layout->hasFrame();
|
||||
return $this->layout->nextFrame($actions);
|
||||
}
|
||||
}
|
|
@ -13,4 +13,8 @@ class Shape {
|
|||
public function __construct(array $edges = []) {
|
||||
$this->edges = $edges;
|
||||
}
|
||||
|
||||
public function merge(Shape $shape): Shape {
|
||||
return new Shape(array_merge($this->edges, $shape->edges));
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ class ShapeConverter {
|
|||
private ?ActivePath $fill_style1 = null;
|
||||
private ?ActivePath $line_style = null;
|
||||
|
||||
private Coordinate $position;
|
||||
private Vector2 $position;
|
||||
|
||||
private PendingPathMap $fills;
|
||||
private PendingPathMap $strokes;
|
||||
|
@ -19,10 +19,9 @@ class ShapeConverter {
|
|||
public DrawPathList $commands;
|
||||
|
||||
|
||||
|
||||
public function __construct(\DOMElement $element, StyleList $currentStyles) {
|
||||
$this->styles = $currentStyles;
|
||||
$this->position = new Coordinate(0, 0);
|
||||
$this->position = new Vector2(0, 0);
|
||||
$this->fills = new PendingPathMap();
|
||||
$this->strokes = new PendingPathMap();
|
||||
$this->commands = new DrawPathList([]);
|
||||
|
@ -45,7 +44,7 @@ class ShapeConverter {
|
|||
|
||||
|
||||
if ($node->hasAttribute("fillStyle1")) {
|
||||
if($this->fill_style1 !== null){
|
||||
if ($this->fill_style1 !== null) {
|
||||
$this->fills->merge_path($this->fill_style1, true);
|
||||
}
|
||||
|
||||
|
@ -56,8 +55,8 @@ class ShapeConverter {
|
|||
}
|
||||
|
||||
if ($node->hasAttribute("fillStyle0")) {
|
||||
if($this->fill_style0 !== null){
|
||||
if(!$this->fill_style0->segment->is_empty()){
|
||||
if ($this->fill_style0 !== null) {
|
||||
if (!$this->fill_style0->segment->is_empty()) {
|
||||
$this->fill_style0->flip();
|
||||
$this->fills->merge_path($this->fill_style0, true);
|
||||
}
|
||||
|
@ -68,7 +67,7 @@ class ShapeConverter {
|
|||
}
|
||||
|
||||
if ($node->hasAttribute("lineStyle")) {
|
||||
if($this->line_style !== null){
|
||||
if ($this->line_style !== null) {
|
||||
$this->strokes->merge_path($this->line_style, false);
|
||||
}
|
||||
|
||||
|
@ -77,13 +76,13 @@ class ShapeConverter {
|
|||
|
||||
$this->line_style = $id > 0 ? new ActivePath($id, $this->position) : null;
|
||||
}
|
||||
}else if ($node->nodeName === "LineTo") {
|
||||
$curve = LineRecord::fromXML($node, $this->position);
|
||||
} else if ($node->nodeName === "LineTo") {
|
||||
$line = LineRecord::fromXML($node, $this->position);
|
||||
|
||||
$this->visit_point($curve->coord, false);
|
||||
$this->visit_point($line->coord, false);
|
||||
|
||||
$this->position = $curve->coord;
|
||||
}else if ($node->nodeName === "CurveTo") {
|
||||
$this->position = $line->coord;
|
||||
} else if ($node->nodeName === "CurveTo") {
|
||||
$curve = QuadraticCurveRecord::fromXML($node, $this->position);
|
||||
|
||||
$this->visit_point($curve->control, true);
|
||||
|
@ -96,51 +95,51 @@ class ShapeConverter {
|
|||
$this->flush_layer();
|
||||
}
|
||||
|
||||
public function visit_point(Coordinate $coordinate, bool $isBezierControlPoint){
|
||||
public function visit_point(Vector2 $coordinate, bool $isBezierControlPoint) {
|
||||
$point = new VisitedPoint($coordinate, $isBezierControlPoint);
|
||||
if($this->fill_style0 !== null){
|
||||
if ($this->fill_style0 !== null) {
|
||||
$this->fill_style0->add_point($point);
|
||||
}
|
||||
if($this->fill_style1 !== null){
|
||||
if ($this->fill_style1 !== null) {
|
||||
$this->fill_style1->add_point($point);
|
||||
}
|
||||
if($this->line_style !== null){
|
||||
if ($this->line_style !== null) {
|
||||
$this->line_style->add_point($point);
|
||||
}
|
||||
}
|
||||
|
||||
public function flush_paths(){
|
||||
if($this->fill_style1 !== null){
|
||||
$this->fills->merge_path($this->fill_style1, true);
|
||||
$this->fill_style1 = new ActivePath($this->fill_style1->style, $this->position);
|
||||
public function flush_paths() {
|
||||
if ($this->fill_style1 !== null) {
|
||||
$this->fills->merge_path($this->fill_style1, true);
|
||||
$this->fill_style1 = new ActivePath($this->fill_style1->style, $this->position);
|
||||
}
|
||||
|
||||
if($this->fill_style0 !== null){
|
||||
if(!$this->fill_style0->segment->is_empty()){
|
||||
if ($this->fill_style0 !== null) {
|
||||
if (!$this->fill_style0->segment->is_empty()) {
|
||||
$this->fill_style0->flip();
|
||||
$this->fills->merge_path($this->fill_style0, true);
|
||||
}
|
||||
$this->fill_style0 = new ActivePath($this->fill_style0->style, $this->position);
|
||||
}
|
||||
if($this->line_style !== null){
|
||||
if ($this->line_style !== null) {
|
||||
$this->strokes->merge_path($this->line_style, false);
|
||||
$this->line_style = new ActivePath($this->line_style->style, $this->position);
|
||||
}
|
||||
}
|
||||
|
||||
public function flush_layer(){
|
||||
public function flush_layer() {
|
||||
$this->flush_paths();
|
||||
|
||||
$this->fill_style0 = null;
|
||||
$this->fill_style1 = null;
|
||||
$this->line_style = null;
|
||||
|
||||
foreach ($this->fills->map as $styleId => $path){
|
||||
foreach ($this->fills->map as $styleId => $path) {
|
||||
assert($styleId > 0 && $styleId < count($this->styles->fillStyles)); // ?????
|
||||
|
||||
$style = $this->styles->getFillStyle($styleId - 1);
|
||||
|
||||
if($style === null){
|
||||
if ($style === null) {
|
||||
var_dump($this->styles);
|
||||
var_dump($styleId);
|
||||
}
|
||||
|
@ -149,16 +148,16 @@ class ShapeConverter {
|
|||
}
|
||||
$this->fills->map = [];
|
||||
|
||||
foreach ($this->strokes->map as $styleId => $path){
|
||||
foreach ($this->strokes->map as $styleId => $path) {
|
||||
assert($styleId > 0 && $styleId < count($this->styles->lineStyles)); // ?????
|
||||
|
||||
$style = $this->styles->getLineStyle($styleId - 1);
|
||||
|
||||
|
||||
foreach ($path->segments as $segment){
|
||||
foreach ($path->segments as $segment) {
|
||||
$segmentStyle = $style;
|
||||
//Close non-closed segments by double drawing backwards
|
||||
if(!$segment->is_closed()){
|
||||
if (!$segment->is_closed()) {
|
||||
$other = clone $segment;
|
||||
$other->flip();
|
||||
$segment->merge($other);
|
||||
|
|
|
@ -3,25 +3,24 @@
|
|||
namespace swf2ass;
|
||||
|
||||
class ShapeDefinition implements ObjectDefinition {
|
||||
public $id;
|
||||
public int $id;
|
||||
public Rectangle $bounds;
|
||||
public DrawPathList $shapeList;
|
||||
|
||||
public function __construct($id, Rectangle $bounds, DrawPathList $shapes) {
|
||||
public function __construct(int $id, Rectangle $bounds, DrawPathList $shapes) {
|
||||
$this->id = $id;
|
||||
$this->bounds = $bounds;
|
||||
$this->shapeList = $shapes;
|
||||
}
|
||||
|
||||
public function getId(){
|
||||
public function getObjectId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
public function getShapeList() : DrawPathList{
|
||||
|
||||
public function getShapeList(): DrawPathList {
|
||||
return $this->shapeList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static function fromXML(\DOMElement $element): ShapeDefinition {
|
||||
$styles = StyleList::fromXML($element->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
|
||||
//Utils::dump_element($element->getElementsByTagName("styles")->item(0));
|
||||
|
@ -32,6 +31,6 @@ class ShapeDefinition implements ObjectDefinition {
|
|||
$drawPathList = $drawPathList->merge(DrawPathList::fromXML($node, $styles));
|
||||
}
|
||||
}
|
||||
return new ShapeDefinition($element->getAttribute("objectID"), Rectangle::fromXML($element->getElementsByTagName("bounds")->item(0)->getElementsByTagName("Rectangle")->item(0)), $drawPathList);
|
||||
return new ShapeDefinition((int)$element->getAttribute("objectID"), Rectangle::fromXML($element->getElementsByTagName("bounds")->item(0)->getElementsByTagName("Rectangle")->item(0)), $drawPathList);
|
||||
}
|
||||
}
|
|
@ -15,10 +15,12 @@ class ShiftedRadialGradient implements Gradient {
|
|||
$this->transform = $transform;
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
public function getItems() : array {
|
||||
|
||||
public function getItems(): array {
|
||||
return $this->colors;
|
||||
}
|
||||
public function getMatrixTransform() : ?MatrixTransform{
|
||||
|
||||
public function getMatrixTransform(): ?MatrixTransform {
|
||||
return $this->transform;
|
||||
}
|
||||
|
||||
|
@ -28,10 +30,6 @@ class ShiftedRadialGradient implements Gradient {
|
|||
$colors[] = GradientItem::fromXML($item);
|
||||
}
|
||||
$matrix = $element->getElementsByTagName("matrix");
|
||||
return new ShiftedRadialGradient($colors, $matrix->count() > 0 ? MatrixTransform::fromXML($matrix->item(0)) : null, [
|
||||
"spreadMode" => $element->hasAttribute("spreadMode") ? $element->getAttribute("spreadMode") : null,
|
||||
"interpolationMode" => $element->hasAttribute("interpolationMode") ? $element->getAttribute("interpolationMode") : null,
|
||||
"shift" => $element->hasAttribute("shift") ? $element->getAttribute("shift") : null,
|
||||
]);
|
||||
return new ShiftedRadialGradient($colors, $matrix->count() > 0 ? MatrixTransform::fromXML($matrix->item(0)) : null, ["spreadMode" => $element->hasAttribute("spreadMode") ? $element->getAttribute("spreadMode") : null, "interpolationMode" => $element->hasAttribute("interpolationMode") ? $element->getAttribute("interpolationMode") : null, "shift" => $element->hasAttribute("shift") ? $element->getAttribute("shift") : null,]);
|
||||
}
|
||||
}
|
56
src/SpriteDefinition.php
Normal file
56
src/SpriteDefinition.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
class SpriteDefinition implements MultiFrameObjectDefinition {
|
||||
public int $id;
|
||||
/** @var ViewFrame[] */
|
||||
public array $frames;
|
||||
|
||||
public int $frameCounter;
|
||||
public bool $hasFrames = true;
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param ViewFrame[] $frames
|
||||
*/
|
||||
public function __construct(int $id, array $frames, int $frameCounter = 0) {
|
||||
$this->id = $id;
|
||||
$this->frames = $frames;
|
||||
$this->frameCounter = $frameCounter % count($this->frames);
|
||||
}
|
||||
|
||||
public function getObjectId(): int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getShapeList(): DrawPathList {
|
||||
$list = new DrawPathList();
|
||||
foreach ($this->frames[$this->frameCounter]->render(0, [], ColorTransform::identity(), MatrixTransform::identity())->getObjects() as $object) {
|
||||
$list = $list->merge($object->drawPathList);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function hasFrame(): bool {
|
||||
return $this->hasFrames;
|
||||
}
|
||||
|
||||
public function getFrameCounter(): int {
|
||||
return $this->frameCounter;
|
||||
}
|
||||
|
||||
public function getFrame(int $frameNumber): ?ViewFrame {
|
||||
return $this->frames[$frameNumber] ?? null;
|
||||
}
|
||||
|
||||
public function nextFrame(): ViewFrame {
|
||||
$f = $this->frames[$this->frameCounter];
|
||||
++$this->frameCounter;
|
||||
if ($this->frameCounter >= count($this->frames)) {
|
||||
$this->frameCounter = count($this->frames) - 1;
|
||||
$this->hasFrames = false; //TODO LOOP
|
||||
}
|
||||
return $f;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace swf2ass;
|
|||
|
||||
|
||||
class Square extends Rectangle {
|
||||
public function __construct(Coordinate $topLeft, $size = 1) {
|
||||
parent::__construct($topLeft, $topLeft->add(new Coordinate($size, $size)));
|
||||
public function __construct(Vector2 $topLeft, $size = 1) {
|
||||
parent::__construct($topLeft, $topLeft->add(new Vector2($size, $size)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ class StyleContainer {
|
|||
public $fscx = null, $fscy = null;
|
||||
public $fax = null, $fay = null;
|
||||
|
||||
public ?Coordinate $pos = null;
|
||||
/** @var ?Coordinate[] */
|
||||
public ?Vector2 $pos = null;
|
||||
/** @var ?Vector2[] */
|
||||
public $move = null;
|
||||
|
||||
public ?Color $color1 = null;
|
||||
|
|
|
@ -43,7 +43,7 @@ class StyleList {
|
|||
//TODO
|
||||
Utils::dump_element($node);
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 255 / 2));
|
||||
}else{
|
||||
} else {
|
||||
Utils::dump_element($node);
|
||||
throw new \Exception("Unknown style " . $node->nodeName);
|
||||
}
|
||||
|
@ -61,18 +61,18 @@ class StyleList {
|
|||
|
||||
if ($colors->count() > 0) {
|
||||
$color = Color::fromXML($colors->item(0)->getElementsByTagName("Color")->item(0));
|
||||
}else if ($colors2->count() > 0) {
|
||||
} else if ($colors2->count() > 0) {
|
||||
$color = Color::fromXML($colors2->item(0));
|
||||
} else if ($grads->count() > 0) {
|
||||
//TODO: other gradients?
|
||||
//$fill = new FillStyleRecord(LinearGradient::fromXML($grads->item(0)->getElementsByTagName("LinearGradient")->item(0)));
|
||||
}
|
||||
|
||||
if($color === null){
|
||||
if ($color === null) {
|
||||
Utils::dump_element($node);
|
||||
}
|
||||
|
||||
$lineStyles[] = new LineStyleRecord(max(TWIP_SIZE, (int)$node->getAttribute("width")), $color, null);
|
||||
$lineStyles[] = new LineStyleRecord(max(Constants::TWIP_SIZE, (int)$node->getAttribute("width")), $color, null);
|
||||
}
|
||||
}
|
||||
return new StyleList($fillStyles, $lineStyles);
|
||||
|
|
214
src/Utils.php
214
src/Utils.php
|
@ -26,220 +26,6 @@ abstract class Utils {
|
|||
return str_pad($h, $l, "0", STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
static function processSWF(\DOMElement $element, &$audio = null) {
|
||||
$currentState = new CurrentState();
|
||||
|
||||
|
||||
$currentState->frameRate = $element->getAttribute("framerate");
|
||||
$currentState->frameCount = $element->getAttribute("frames");
|
||||
$rect = $element->getElementsByTagName("size")->item(0)->getElementsByTagName("Rectangle")->item(0);
|
||||
if ($rect instanceof \DOMElement and $rect->nodeName === "Rectangle") {
|
||||
$currentState->viewPort = Rectangle::fromXML($rect)->toPixel();
|
||||
}
|
||||
|
||||
$sound = null;
|
||||
|
||||
$width = $currentState->viewPort->getWidth();
|
||||
$height = $currentState->viewPort->getHeight();
|
||||
$ar = $width/$height;
|
||||
$durationSeconds = (int) ceil($currentState->frameRate * $currentState->frameCount);
|
||||
|
||||
$header = <<<ASSHEADER
|
||||
[Script Info]
|
||||
; Script generated by Aegisub 3.2.2
|
||||
; http://www.aegisub.org/
|
||||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
ScaledBorderAndShadow: yes
|
||||
YCbCr Matrix: TV.709
|
||||
PlayResX: {$width}
|
||||
PlayResY: {$height}
|
||||
|
||||
[Aegisub Project Garbage]
|
||||
Last Style Storage: Default
|
||||
Video File: ?dummy:{$currentState->frameRate}:{$durationSeconds}:{$width}:{$height}:160:160:160:c
|
||||
Video AR Value: {$ar}
|
||||
|
||||
[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
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
|
||||
ASSHEADER;
|
||||
|
||||
foreach (explode("\n", $header) as $line){
|
||||
yield $line . "\n";
|
||||
}
|
||||
|
||||
|
||||
$rootTag = $element->getElementsByTagName("tags")->item(0);
|
||||
$e = [];
|
||||
if ($rootTag instanceof \DOMElement) {
|
||||
foreach ($rootTag->childNodes as $node) {
|
||||
if ($node instanceof \DOMElement) {
|
||||
if ($node->nodeName === "SoundStreamHead") {
|
||||
$sound = $node;
|
||||
} else if ($node->nodeName === "DefineSound") {
|
||||
if($currentState->frameOffset === null){
|
||||
$currentState->frameOffset = $currentState->frameNumber;
|
||||
}
|
||||
} else if ($node->nodeName === "SoundStreamBlock" and $sound !== null) {
|
||||
if($currentState->frameOffset === null){
|
||||
$currentState->frameOffset = $currentState->frameNumber;
|
||||
}
|
||||
$data = base64_decode(trim($node->textContent));
|
||||
$audio .= substr($data, 2);
|
||||
} else if ($node->nodeName === "SetBackgroundColor") {
|
||||
$currentState->backgroundColor = Color::fromXML($node->getElementsByTagName("color")->item(0)->getElementsByTagName("Color")->item(0));
|
||||
$currentState->displayList[0] = new DisplayEntry();
|
||||
$currentState->displayList[0]->object = new ShapeDefinition(0, $currentState->viewPort->multiply(TWIP_SIZE), new DrawPathList([DrawPath::fill(new FillStyleRecord($currentState->backgroundColor), new Shape($currentState->viewPort->multiply(TWIP_SIZE)->draw()))]));
|
||||
$currentState->displayList[0]->startFrame = $currentState->frameNumber;
|
||||
$currentState->displayList[0]->depth = 0;
|
||||
} else if ($node->nodeName === "DefineMorphShape") {
|
||||
//$shapeDef = MorphShapeDefinition::fromXML($node);
|
||||
//$currentState->objects[$shapeDef->id] = $shapeDef;
|
||||
//TODO
|
||||
var_dump("TODO DefineMorphShape");
|
||||
} else if ($node->nodeName === "DefineShape" or $node->nodeName === "DefineShape2" or $node->nodeName === "DefineShape3" or $node->nodeName === "DefineShape4" or $node->nodeName === "DefineShape5") {
|
||||
$shapeDef = ShapeDefinition::fromXML($node);
|
||||
$currentState->objects[$shapeDef->id] = $shapeDef;
|
||||
} else if ($node->nodeName === "RemoveObject" or $node->nodeName === "RemoveObject2") {
|
||||
$depth = (int)$node->getAttribute("depth");
|
||||
if ($currentState->displayList[$depth] ?? null instanceof DisplayEntry) {
|
||||
//TODO: clips
|
||||
foreach ($currentState->displayList[$depth]->getLines($currentState) as $line) {
|
||||
yield $line . PHP_EOL;
|
||||
}
|
||||
}
|
||||
unset($currentState->displayList[$depth]);
|
||||
unset($currentState->clipList[$depth]);
|
||||
} else if ($node->nodeName === "PlaceObject2" or $node->nodeName === "PlaceObject3") {
|
||||
$depth = (int)$node->getAttribute("depth");
|
||||
|
||||
$transform = null;
|
||||
$transformNodes = $node->getElementsByTagName("transform");
|
||||
if ($transformNodes->count() > 0) {
|
||||
$transform = MatrixTransform::fromXML($transformNodes->item(0)->getElementsByTagName("Transform")->item(0));
|
||||
}
|
||||
|
||||
$colorTransform = null;
|
||||
$colorTransformNodes = $node->getElementsByTagName("colorTransform");
|
||||
if ($colorTransformNodes->count() > 0) {
|
||||
$colorTransform = ColorTransform::fromXML($colorTransformNodes->item(0)->getElementsByTagName("ColorTransform2")->item(0));
|
||||
}
|
||||
|
||||
$objectID = $node->getAttribute("objectID");
|
||||
|
||||
if ($objectID !== "") {
|
||||
$ob = new DisplayEntry();
|
||||
$objectID = $node->getAttribute("objectID");
|
||||
if (!isset($currentState->objects[$objectID])) {
|
||||
var_dump("Unknown object id $objectID");
|
||||
continue;
|
||||
throw new \Exception("Unknown object id $objectID");
|
||||
}
|
||||
$ob->startFrame = $currentState->frameNumber;
|
||||
$ob->depth = $depth;
|
||||
if ($node->hasAttribute("clipDepth")) {
|
||||
$ob->clipDepth = (int)$node->getAttribute("clipDepth");
|
||||
$currentState->clipList[$ob->clipDepth] = $ob;
|
||||
}
|
||||
$ob->object = $currentState->objects[$objectID];
|
||||
if ($currentState->displayList[$depth] ?? null instanceof DisplayEntry) {
|
||||
//TODO: clips
|
||||
foreach ($currentState->displayList[$depth]->getLines($currentState) as $line) {
|
||||
yield $line . PHP_EOL;
|
||||
}
|
||||
|
||||
if ($node->getAttribute("replace") === "1") {
|
||||
$ob->currentTransform = $currentState->displayList[$depth]->currentTransform;
|
||||
$ob->currentColorTransform = $currentState->displayList[$depth]->currentColorTransform;
|
||||
}
|
||||
}
|
||||
|
||||
$currentState->displayList[$depth] = $ob;
|
||||
}
|
||||
|
||||
$ob = $currentState->displayList[$depth] ?? null;
|
||||
if (!$ob instanceof DisplayEntry) {
|
||||
continue;
|
||||
throw new \Exception("Unknown depth id $depth");
|
||||
}
|
||||
|
||||
if ($transform !== null) {
|
||||
$ob->currentTransform = $transform;
|
||||
}
|
||||
|
||||
if ($colorTransform !== null) {
|
||||
$ob->currentColorTransform = $colorTransform;
|
||||
}
|
||||
} else if ($node->nodeName === "ShowFrame") {
|
||||
//fwrite(STDERR, "Frame {$currentState->frameNumber}". PHP_EOL);
|
||||
var_dump("Frame {$currentState->frameNumber} ~{$currentState->frameOffset} (" . count($currentState->displayList) .")");
|
||||
|
||||
ksort($currentState->displayList);
|
||||
|
||||
foreach ($currentState->displayList as $depth => $displayItem) {
|
||||
/** @var DisplayEntry $displayItem */
|
||||
|
||||
$info = new FrameInformation();
|
||||
$info->transform = $displayItem->currentTransform;
|
||||
$info->colorTransform = $displayItem->currentColorTransform;
|
||||
$info->frame = $currentState->frameNumber;
|
||||
$displayItem->frames[$currentState->frameNumber] = $info;
|
||||
}
|
||||
|
||||
++$currentState->frameNumber;
|
||||
} else if ($node->nodeName === "DefineSprite") {
|
||||
//TODO do actual animation
|
||||
var_dump("TODO DefineSprite");
|
||||
//Utils::dump_element($node);
|
||||
|
||||
foreach ($node->getElementsByTagName("tags")->item(0)->childNodes as $node2) {
|
||||
if ($node2 instanceof \DOMElement) {
|
||||
if($node2->nodeName === "PlaceObject2" and isset($currentState->objects[$node2->getAttribute("objectID")])){
|
||||
//TODO
|
||||
$currentState->objects[$node->getAttribute("objectID")] = $currentState->objects[$node2->getAttribute("objectID")];
|
||||
var_dump($node->getAttribute("objectID") . " === " . $node2->getAttribute("objectID") . " " . get_class($currentState->objects[$node->getAttribute("objectID")]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ($node->nodeName === "DefineBitsJPEG2" || $node->nodeName === "DefineBitsJPEG3") {
|
||||
$bitmapDef = JPEGBitmapDefinition::fromXML($node);
|
||||
$currentState->objects[$bitmapDef->id] = $bitmapDef;
|
||||
//file_put_contents("image_" . $bitmapDef->id .".png", $bitmapDef->toPNG());
|
||||
|
||||
} else if ($node->nodeName === "DefineBitsLossless" || $node->nodeName === "DefineBitsLossless2") {
|
||||
$bitmapDef = BitmapDefinition::fromXML($node);
|
||||
$currentState->objects[$bitmapDef->id] = $bitmapDef;
|
||||
//file_put_contents("image_" . $bitmapDef->id .".png", $bitmapDef->toPNG());
|
||||
|
||||
} else if ($node->nodeName === "DoAction") {
|
||||
|
||||
Utils::dump_element($node);
|
||||
} else if ($node->nodeName === "FileAttributes") {
|
||||
|
||||
Utils::dump_element($node);
|
||||
} else {
|
||||
|
||||
echo $node->nodeName . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($currentState->displayList as $depth => $entry){
|
||||
foreach ($entry->getLines($currentState) as $line) {
|
||||
yield $line . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
return $currentState;
|
||||
}
|
||||
|
||||
static function dump_element(\DOMElement $element) {
|
||||
var_dump($element->ownerDocument->saveXML($element));
|
||||
}
|
||||
|
|
64
src/Vector2.php
Normal file
64
src/Vector2.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
class Vector2 {
|
||||
/** @var numeric */
|
||||
public $x;
|
||||
/** @var numeric */
|
||||
public $y;
|
||||
|
||||
public function __construct($x, $y) {
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
}
|
||||
|
||||
|
||||
public function equals(Vector2 $b, $epsilon = Constants::EPSILON): bool {
|
||||
return ($this->x === $b->x or abs($b->x - $this->x) <= $epsilon) and ($this->y === $b->y or abs($b->y - $this->y) <= $epsilon);
|
||||
}
|
||||
|
||||
public function distance(Vector2 $b): float {
|
||||
return sqrt(pow($this->x - $b->x, 2) + pow($this->y - $b->y, 2));
|
||||
}
|
||||
|
||||
public function invert(): Vector2 {
|
||||
return new Vector2($this->y, $this->x);
|
||||
}
|
||||
|
||||
public function add(Vector2 $b): Vector2 {
|
||||
return new Vector2($this->x + $b->x, $this->y + $b->y);
|
||||
}
|
||||
|
||||
public function sub(Vector2 $b): Vector2 {
|
||||
return new Vector2($this->x - $b->x, $this->y - $b->y);
|
||||
}
|
||||
|
||||
public function abs(): Vector2 {
|
||||
return new Vector2(abs($this->x), abs($this->y));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Vector2 $b
|
||||
* @return numeric
|
||||
*/
|
||||
public function dot(Vector2 $b) {
|
||||
return $this->x * $b->x + $this->y * $b->y;
|
||||
}
|
||||
|
||||
public function vectorMultiply(Vector2 $b): Vector2 {
|
||||
return new Vector2($this->x * $b->x, $this->y * $b->y);
|
||||
}
|
||||
|
||||
public function multiply($size): Vector2 {
|
||||
return new Vector2($this->x * $size, $this->y * $size);
|
||||
}
|
||||
|
||||
public function divide($size): Vector2 {
|
||||
return new Vector2($this->x / $size, $this->y / $size);
|
||||
}
|
||||
|
||||
public function toPixel($twipSize = Constants::TWIP_SIZE): Vector2 {
|
||||
return $this->divide($twipSize);
|
||||
}
|
||||
}
|
113
src/ViewFrame.php
Normal file
113
src/ViewFrame.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class ViewFrame {
|
||||
private int $objectId;
|
||||
/** @var ViewFrame[] */
|
||||
private array $depthMap = [];
|
||||
/** @var ViewFrame[] */
|
||||
private ?array $clipDepthMap = null;
|
||||
private ?DrawPathList $drawPathList;
|
||||
|
||||
private ?ColorTransform $colorTransform = null;
|
||||
private ?MatrixTransform $matrixTransform = null;
|
||||
|
||||
public function __construct(int $objectId, ?DrawPathList $drawPathList) {
|
||||
$this->objectId = $objectId;
|
||||
$this->drawPathList = $drawPathList;
|
||||
}
|
||||
|
||||
public function getObjectId(): int {
|
||||
return $this->objectId;
|
||||
}
|
||||
|
||||
public function setClipDepthMap(array $clipDepthMap) {
|
||||
$this->clipDepthMap = $clipDepthMap;
|
||||
//TODO: process this
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ViewFrame[]|null
|
||||
*/
|
||||
public function getClipDepthMap(): ?array {
|
||||
return $this->clipDepthMap;
|
||||
}
|
||||
|
||||
public function addChild(int $depth, ViewFrame $frame) {
|
||||
if ($this->drawPathList !== null) {
|
||||
throw new \Exception();
|
||||
}
|
||||
$this->depthMap[$depth] = $frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ViewFrame[]
|
||||
*/
|
||||
public function getDepthMap(): array {
|
||||
return $this->depthMap;
|
||||
}
|
||||
|
||||
public function setColorTransform(?ColorTransform $transform) {
|
||||
$this->colorTransform = $transform;
|
||||
}
|
||||
|
||||
public function setMatrixTransform(?MatrixTransform $transform) {
|
||||
$this->matrixTransform = $transform;
|
||||
}
|
||||
|
||||
public function getColorTransform(): ?ColorTransform {
|
||||
return $this->colorTransform;
|
||||
}
|
||||
|
||||
public function getMatrixTransform(): ?MatrixTransform {
|
||||
return $this->matrixTransform;
|
||||
}
|
||||
|
||||
public function render(int $baseDepth, array $depthChain, ColorTransform $parentColor, MatrixTransform $parentMatrix): RenderedFrame {
|
||||
$depthChain[] = $baseDepth;
|
||||
|
||||
$matrixTransform = $this->matrixTransform !== null ? $parentMatrix->combine($this->matrixTransform) : $parentMatrix;
|
||||
$colorTransform = $this->colorTransform !== null ? $parentColor->combine($this->colorTransform) : $parentColor;
|
||||
|
||||
|
||||
$renderedFrame = new RenderedFrame();
|
||||
|
||||
$clipShape = null;
|
||||
if ($this->clipDepthMap !== null) {
|
||||
$colorIdentity = ColorTransform::identity();
|
||||
$matrixIdentity = MatrixTransform::identity();
|
||||
$clipShape = new Shape();
|
||||
foreach ($this->clipDepthMap as $clipDepth => $clipFrame) {
|
||||
//TODO: detect rectangle clips? for \iclip
|
||||
//TODO: add \clip for bounds
|
||||
foreach ($clipFrame->render($clipDepth, $depthChain, $colorIdentity, $matrixIdentity)->getObjects() as $clipObject) {
|
||||
foreach ($clipObject->drawPathList->commands as $clipPath) {
|
||||
//TODO: is transform here needed?
|
||||
$clipShape = $clipShape->merge($clipObject->matrixTransform->applyToShape($clipPath->commands));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->drawPathList !== null) {
|
||||
$renderedFrame->add(new RenderedObject($depthChain, $this->getObjectId(), $this->drawPathList, $colorTransform, $matrixTransform, $clipShape));
|
||||
} else {
|
||||
foreach ($this->depthMap as $depth => $frame) {
|
||||
$objects = $frame->render($depth, $depthChain, $colorTransform, $matrixTransform)->getObjects();
|
||||
foreach ($objects as $object) {
|
||||
if ($object->clip !== null and $clipShape !== null) {
|
||||
$object->clip = $object->clip->merge($clipShape);
|
||||
} else if ($clipShape !== null) {
|
||||
$object->clip = $clipShape;
|
||||
}
|
||||
|
||||
$renderedFrame->add($object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $renderedFrame;
|
||||
}
|
||||
}
|
150
src/ViewLayout.php
Normal file
150
src/ViewLayout.php
Normal file
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class ViewLayout {
|
||||
private ?ViewLayout $parent;
|
||||
|
||||
/** @var ViewLayout[] */
|
||||
private array $depthMap = [];
|
||||
|
||||
private ?ObjectDefinition $object;
|
||||
|
||||
private ?ColorTransform $colorTransform = null;
|
||||
private ?MatrixTransform $matrixTransform = null;
|
||||
|
||||
private int $objectId;
|
||||
|
||||
public function __construct(int $objectId, ?ObjectDefinition $object, ?ViewLayout $parent = null) {
|
||||
$this->objectId = $objectId;
|
||||
if ($object !== null and $objectId !== $object->getObjectId()) {
|
||||
throw new \LogicException();
|
||||
}
|
||||
$this->parent = $parent;
|
||||
$this->object = $object;
|
||||
}
|
||||
|
||||
public function getObjectId(): int {
|
||||
return $this->object !== null ? $this->object->getObjectId() : -1;
|
||||
}
|
||||
|
||||
public function getObject(): ?ObjectDefinition {
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ViewLayout[]
|
||||
*/
|
||||
public function getDepthMap(): array {
|
||||
return $this->depthMap;
|
||||
}
|
||||
|
||||
public function get(int $depth): ?ViewLayout {
|
||||
return $this->depthMap[$depth] ?? null;
|
||||
}
|
||||
|
||||
public function place(int $depth, ViewLayout $ob) {
|
||||
if ($this->object !== null) {
|
||||
throw new \LogicException("Cannot have ObjectDefinition and children at the same time");
|
||||
}
|
||||
if (isset($this->depthMap[$depth]) and $this->depthMap[$depth] !== $ob) {
|
||||
throw new \Exception("Depth $depth already exists: tried replacing object " . $this->depthMap[$depth]->getObjectId() . " with " . $ob->getObjectId());
|
||||
}
|
||||
$this->depthMap[$depth] = $ob;
|
||||
}
|
||||
|
||||
public function replace(int $depth, ViewLayout $ob) {
|
||||
if ($this->object !== null) {
|
||||
throw new \LogicException("Cannot have ObjectDefinition and children at the same time");
|
||||
}
|
||||
$oldObject = $this->get($depth);
|
||||
if ($oldObject !== null) {
|
||||
$ob->colorTransform = $ob->colorTransform ?? $oldObject->colorTransform;
|
||||
$ob->matrixTransform = $ob->matrixTransform ?? $oldObject->matrixTransform;
|
||||
}
|
||||
$this->depthMap[$depth] = $ob;
|
||||
}
|
||||
|
||||
public function remove(int $depth) {
|
||||
unset($this->depthMap[$depth]);
|
||||
}
|
||||
|
||||
public function hasFrame(): bool {
|
||||
if ($this->object !== null) {
|
||||
if ($this->object instanceof MultiFrameObjectDefinition) {
|
||||
if ($this->object->hasFrame()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->depthMap as $ob) {
|
||||
if ($ob->hasFrame()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMatrixTransform(): ?MatrixTransform {
|
||||
return $this->matrixTransform;
|
||||
}
|
||||
|
||||
public function getColorTransform(): ?ColorTransform {
|
||||
return $this->colorTransform;
|
||||
}
|
||||
|
||||
public function setMatrixTransform(?MatrixTransform $transform) {
|
||||
$this->matrixTransform = $transform;
|
||||
}
|
||||
|
||||
public function setColorTransform(?ColorTransform $transform) {
|
||||
$this->colorTransform = $transform;
|
||||
}
|
||||
|
||||
public function nextFrame(ActionList $actionList): ViewFrame {
|
||||
if ($this->object !== null) {
|
||||
if ($this->object instanceof MultiFrameObjectDefinition) {
|
||||
$frame = $this->object->nextFrame();
|
||||
} else {
|
||||
$frame = new ViewFrame($this->getObjectId(), $this->object->getShapeList());
|
||||
}
|
||||
} else {
|
||||
$frame = new ViewFrame($this->getObjectId(), null);
|
||||
/** @var ClippingViewLayout[] $clipMap */
|
||||
$clipMap = [];
|
||||
|
||||
ksort($this->depthMap);
|
||||
|
||||
foreach ($this->depthMap as $depth => $child) {
|
||||
if ($child instanceof ClippingViewLayout) {
|
||||
$clipMap[$depth] = $child;
|
||||
} else {
|
||||
/** @var ViewFrame[] $clips */
|
||||
$clips = []; //TODO: make something else?
|
||||
foreach ($clipMap as $clipDepth => $clip) {
|
||||
//$targetDepth = $clip->getClipDepth() + $clipDepth;
|
||||
if ($clip->getClipDepth() > $depth and $clipDepth < $depth) {
|
||||
$clips[$clipDepth] = $clip->nextFrame($actionList);
|
||||
}
|
||||
}
|
||||
|
||||
$f = $child->nextFrame($actionList);
|
||||
if (count($clips) > 0) {
|
||||
$f->setClipDepthMap($clips);
|
||||
}
|
||||
$frame->addChild($depth, $f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$frame->setColorTransform($this->colorTransform);
|
||||
$frame->setMatrixTransform($this->matrixTransform);
|
||||
|
||||
return $frame;
|
||||
}
|
||||
}
|
|
@ -4,10 +4,10 @@ namespace swf2ass;
|
|||
|
||||
|
||||
class VisitedPoint {
|
||||
public Coordinate $pos;
|
||||
public Vector2 $pos;
|
||||
public bool $is_bezier_control;
|
||||
|
||||
public function __construct(Coordinate $pos, bool $is_bezier_control = false) {
|
||||
public function __construct(Vector2 $pos, bool $is_bezier_control = false) {
|
||||
$this->pos = $pos;
|
||||
$this->is_bezier_control = $is_bezier_control;
|
||||
}
|
||||
|
|
10
src/ass/ASSColorTag.php
Normal file
10
src/ass/ASSColorTag.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\ColorTransform;
|
||||
use swf2ass\MatrixTransform;
|
||||
|
||||
interface ASSColorTag extends ASSTag {
|
||||
public function transitionColor(ColorTransform $transform): ?ASSColorTag;
|
||||
}
|
53
src/ass/ASSLine.php
Normal file
53
src/ass/ASSLine.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\FrameInformation;
|
||||
use swf2ass\RenderedFrame;
|
||||
use swf2ass\RenderedObject;
|
||||
|
||||
class ASSLine {
|
||||
const HOURS_MS = 1000 * 3600;
|
||||
const MINUTES_MS = 1000 * 60;
|
||||
|
||||
public int $layer;
|
||||
public int $start;
|
||||
public int $frames = 0;
|
||||
public int $end;
|
||||
public string $style;
|
||||
public string $name = "";
|
||||
public int $marginLeft = 0;
|
||||
public int $marginRight = 0;
|
||||
public int $marginVertical = 0;
|
||||
public string $effect = "";
|
||||
|
||||
public bool $isComment = false;
|
||||
|
||||
public string $text;
|
||||
|
||||
public function __construct(string $text) {
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
public static function encodeTime(int $ms, $msPrecision = 2): string {
|
||||
if ($ms < 0) {
|
||||
throw new \LogicException("ms less than 0: $ms");
|
||||
}
|
||||
|
||||
$hours = intdiv($ms, self::HOURS_MS);
|
||||
$ms -= $hours * self::HOURS_MS;
|
||||
$minutes = intdiv($ms, self::MINUTES_MS);
|
||||
$ms -= $minutes * self::MINUTES_MS;
|
||||
|
||||
$s = explode(".", strval(round($ms / 1000, $msPrecision)));
|
||||
if (!isset($s[1])) {
|
||||
$s[1] = 0;
|
||||
}
|
||||
|
||||
return ((string)$hours) . ":" . str_pad((string)$minutes, 2, "0", STR_PAD_LEFT) . ':' . str_pad($s[0], 2, "0", STR_PAD_LEFT) . "." . str_pad($s[1], $msPrecision, "0", STR_PAD_RIGHT);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return ($this->isComment ? "Comment" : "Dialogue") . ": " . $this->layer . "," . self::encodeTime($this->start) . "," . self::encodeTime($this->end) . "," . $this->style . "," . $this->name . "," . $this->marginLeft . "," . $this->marginRight . "," . $this->marginVertical . "," . $this->effect . "," . $this->text;
|
||||
}
|
||||
}
|
11
src/ass/ASSPositioningTag.php
Normal file
11
src/ass/ASSPositioningTag.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\MatrixTransform;
|
||||
|
||||
interface ASSPositioningTag extends ASSTag {
|
||||
public function transitionMatrixTransform(MatrixTransform $transform): ?ASSPositioningTag;
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?ASSPositioningTag;
|
||||
}
|
136
src/ass/ASSRenderer.php
Normal file
136
src/ass/ASSRenderer.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\FrameInformation;
|
||||
use swf2ass\Rectangle;
|
||||
use swf2ass\RenderedFrame;
|
||||
use swf2ass\RenderedObject;
|
||||
|
||||
class ASSRenderer {
|
||||
private ?string $header;
|
||||
|
||||
/** @var ASSLine[] */
|
||||
private array $dupeBuffer = [];
|
||||
|
||||
public function __construct(int $frameRate, Rectangle $viewPort) {
|
||||
$display = $viewPort->toPixel();
|
||||
$width = $display->getWidth();
|
||||
$height = $display->getHeight();
|
||||
$ar = $width / $height;
|
||||
|
||||
|
||||
$this->header = <<<ASSHEADER
|
||||
[Script Info]
|
||||
; Script generated by swf2ass ASSRenderer
|
||||
; https://git.gammaspectra.live/WeebDataHoarder/swf2ass
|
||||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
ScaledBorderAndShadow: yes
|
||||
YCbCr Matrix: TV.709
|
||||
PlayResX: {$width}
|
||||
PlayResY: {$height}
|
||||
|
||||
[Aegisub Project Garbage]
|
||||
Last Style Storage: Default
|
||||
Video File: ?dummy:{$frameRate}:10000:{$width}:{$height}:160:160:160:c
|
||||
Video AR Value: {$ar}
|
||||
|
||||
[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
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
ASSHEADER;
|
||||
}
|
||||
|
||||
|
||||
public function renderFrame(FrameInformation $information, RenderedFrame $frame): \Generator {
|
||||
if ($this->header !== null) {
|
||||
foreach (explode("\n", $this->header) as $line) {
|
||||
yield $line;
|
||||
}
|
||||
$this->header = null;
|
||||
}
|
||||
|
||||
$objects = $frame->getObjects();
|
||||
usort($objects, [self::class, "depthSort"]);
|
||||
|
||||
$dupeBuffer = [];
|
||||
|
||||
foreach ($objects as $object) {
|
||||
foreach ($this->renderObject($object) as $line) {
|
||||
$line->layer = $object->depth[0] === 0 ? $object->depth[1] : $object->depth[0];
|
||||
$line->start = $information->getStartTimeMilliSeconds();
|
||||
$line->end = $information->getEndTimeMilliSeconds();
|
||||
$line->frames = 1;
|
||||
$line->style = "f";
|
||||
|
||||
foreach ($this->dupeBuffer as $i => $dup) {
|
||||
if ($dup->layer === $line->layer and $dup->text === $line->text) {
|
||||
$line->start = $dup->start;
|
||||
$line->frames += $dup->frames;
|
||||
unset($this->dupeBuffer[$i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$dupeBuffer[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
//Flush non dupes
|
||||
foreach ($this->dupeBuffer as $line) {
|
||||
$line->name .= " dup:{$line->frames}";
|
||||
yield $line->encode();
|
||||
}
|
||||
|
||||
$this->dupeBuffer = $dupeBuffer;
|
||||
}
|
||||
|
||||
public function flush(): \Generator {
|
||||
foreach ($this->dupeBuffer as $line) {
|
||||
$line->name .= " dup:{$line->frames}";
|
||||
yield $line->encode();
|
||||
}
|
||||
$this->dupeBuffer = [];
|
||||
}
|
||||
|
||||
public static function depthSort(RenderedObject $a, RenderedObject $b) {
|
||||
if (count($b->depth) > count($a->depth)) {
|
||||
foreach ($b->depth as $i => $depth) {
|
||||
$otherDepth = $a->depth[$i] ?? 0;
|
||||
if ($depth !== $otherDepth) {
|
||||
return $otherDepth - $depth;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($a->depth as $i => $depth) {
|
||||
$otherDepth = $b->depth[$i] ?? 0;
|
||||
if ($depth !== $otherDepth) {
|
||||
return $depth - $otherDepth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RenderedObject $object
|
||||
* @return ASSLine[]
|
||||
*/
|
||||
public function renderObject(RenderedObject $object): array {
|
||||
$lines = [];
|
||||
foreach ($object->drawPathList->commands as $drawPath) {
|
||||
$container = containerTag::fromPathEntry($drawPath, $object->clip, $object->colorTransform, $object->matrixTransform);
|
||||
$line = new ASSLine("{" . $container->encode() . "}");
|
||||
$line->name = "o:{$object->objectId} d:" . implode(".", array_slice($object->depth, 1));
|
||||
$lines[] = $line;
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
}
|
11
src/ass/ASSStyleTag.php
Normal file
11
src/ass/ASSStyleTag.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\StyleRecord;
|
||||
|
||||
interface ASSStyleTag extends ASSTag {
|
||||
public function transitionStyleRecord(StyleRecord $record): ?ASSStyleTag;
|
||||
|
||||
public static function fromStyleRecord(StyleRecord $record): ?ASSStyleTag;
|
||||
}
|
13
src/ass/ASSTag.php
Normal file
13
src/ass/ASSTag.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\ColorTransform;
|
||||
use swf2ass\MatrixTransform;
|
||||
|
||||
interface ASSTag {
|
||||
|
||||
public function equals(ASSTag $tag): bool;
|
||||
|
||||
public function encode(): string;
|
||||
}
|
33
src/ass/blurEdgesGaussianTag.php
Normal file
33
src/ass/blurEdgesGaussianTag.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\StyleRecord;
|
||||
|
||||
class blurEdgesGaussianTag implements ASSStyleTag {
|
||||
|
||||
/** @var int|float */
|
||||
private $strength;
|
||||
|
||||
public function __construct($strength = 0) {
|
||||
$this->strength = $strength;
|
||||
}
|
||||
|
||||
public function transitionStyleRecord(StyleRecord $record): ?blurEdgesGaussianTag {
|
||||
return self::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\blur{$this->strength}";
|
||||
}
|
||||
|
||||
public static function fromStyleRecord(StyleRecord $record): ?blurEdgesGaussianTag {
|
||||
//TODO?
|
||||
return new blurEdgesGaussianTag(0);
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and abs($this->strength - $tag->strength) <= Constants::EPSILON;
|
||||
}
|
||||
}
|
32
src/ass/blurEdgesTag.php
Normal file
32
src/ass/blurEdgesTag.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\StyleRecord;
|
||||
|
||||
class blurEdgesTag implements ASSStyleTag {
|
||||
|
||||
private int $strength;
|
||||
|
||||
public function __construct(int $strength = 0) {
|
||||
$this->strength = $strength;
|
||||
}
|
||||
|
||||
public function transitionStyleRecord(StyleRecord $record): ?blurEdgesTag {
|
||||
return self::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\be{$this->strength}";
|
||||
}
|
||||
|
||||
public static function fromStyleRecord(StyleRecord $record): ?blurEdgesTag {
|
||||
//TODO?
|
||||
return new blurEdgesTag(0);
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and abs($this->strength - $tag->strength) <= Constants::EPSILON;
|
||||
}
|
||||
}
|
36
src/ass/borderTag.php
Normal file
36
src/ass/borderTag.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\LineStyleRecord;
|
||||
use swf2ass\StyleRecord;
|
||||
|
||||
class borderTag implements ASSStyleTag {
|
||||
|
||||
/** @var int|float */
|
||||
private $size;
|
||||
|
||||
public function __construct($size = 0) {
|
||||
$this->size = $size;
|
||||
}
|
||||
|
||||
public function transitionStyleRecord(StyleRecord $record): ?borderTag {
|
||||
return self::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\bord{$this->size}";
|
||||
}
|
||||
|
||||
public static function fromStyleRecord(StyleRecord $record): ?borderTag {
|
||||
if ($record instanceof LineStyleRecord) {
|
||||
return new borderTag($record->width / Constants::TWIP_SIZE);
|
||||
}
|
||||
return new borderTag();
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and abs($this->size - $tag->size) <= Constants::EPSILON;
|
||||
}
|
||||
}
|
10
src/ass/clipTag.php
Normal file
10
src/ass/clipTag.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
class clipTag extends drawingTag {
|
||||
|
||||
public function encode(): string {
|
||||
return "\\clip(" . implode(" ", $this->getCommands()) . ")";
|
||||
}
|
||||
}
|
33
src/ass/colorTag.php
Normal file
33
src/ass/colorTag.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Color;
|
||||
use swf2ass\ColorTransform;
|
||||
use swf2ass\FillStyleRecord;
|
||||
use swf2ass\Gradient;
|
||||
use swf2ass\StyleRecord;
|
||||
use swf2ass\Utils;
|
||||
|
||||
abstract class colorTag implements ASSStyleTag, ASSColorTag {
|
||||
|
||||
protected Color $color;
|
||||
|
||||
public function __construct(Color $color) {
|
||||
$this->color = $color;
|
||||
}
|
||||
|
||||
public static abstract function fromStyleRecord(StyleRecord $record): ?colorTag;
|
||||
|
||||
public function transitionStyleRecord(StyleRecord $record): ?ASSStyleTag {
|
||||
return $this::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->color->equals($tag->color);
|
||||
}
|
||||
|
||||
public function transitionColor(ColorTransform $transform): ?colorTag {
|
||||
return new $this($transform->applyToColor($this->color));
|
||||
}
|
||||
}
|
136
src/ass/containerTag.php
Normal file
136
src/ass/containerTag.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
|
||||
use swf2ass\ColorTransform;
|
||||
use swf2ass\DrawPath;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\Shape;
|
||||
use swf2ass\StyleRecord;
|
||||
|
||||
class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag {
|
||||
|
||||
/** @var ASSTag[] */
|
||||
private array $tags = [];
|
||||
|
||||
public function transitionColor(ColorTransform $transform): ?containerTag {
|
||||
$container = new containerTag();
|
||||
|
||||
foreach ($this->tags as $tag) {
|
||||
if ($tag instanceof ASSColorTag) {
|
||||
$newTag = $tag->transitionColor($transform);
|
||||
if ($newTag === null) {
|
||||
return null;
|
||||
}
|
||||
$container->tags[] = $newTag;
|
||||
} else {
|
||||
$container->tags[] = $tag;
|
||||
}
|
||||
}
|
||||
return $container;
|
||||
}
|
||||
|
||||
public function transitionMatrixTransform(MatrixTransform $transform): ?containerTag {
|
||||
$container = new containerTag();
|
||||
|
||||
foreach ($this->tags as $tag) {
|
||||
if ($tag instanceof ASSPositioningTag) {
|
||||
$newTag = $tag->transitionMatrixTransform($transform);
|
||||
if ($newTag === null) {
|
||||
return null;
|
||||
}
|
||||
$container->tags[] = $newTag;
|
||||
} else {
|
||||
$container->tags[] = $tag;
|
||||
}
|
||||
}
|
||||
return $container;
|
||||
}
|
||||
|
||||
public function transitionStyleRecord(StyleRecord $record): ?containerTag {
|
||||
$container = new containerTag();
|
||||
|
||||
foreach ($this->tags as $tag) {
|
||||
if ($tag instanceof ASSStyleTag) {
|
||||
$newTag = $tag->transitionStyleRecord($record);
|
||||
if ($newTag === null) {
|
||||
return null;
|
||||
}
|
||||
$container->tags[] = $newTag;
|
||||
} else {
|
||||
$container->tags[] = $tag;
|
||||
}
|
||||
}
|
||||
return $container;
|
||||
}
|
||||
|
||||
protected function try_append(?ASSTag $tag, $throw_on_null = false) {
|
||||
if ($tag !== null) {
|
||||
$this->tags[] = $tag;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($throw_on_null) {
|
||||
throw new \Exception();
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromPathEntry(DrawPath $path, ?Shape $clip, ?ColorTransform $colorTransform, ?MatrixTransform $matrixTransform): containerTag {
|
||||
$container = new containerTag();
|
||||
$container->try_append(borderTag::fromStyleRecord($path->style));
|
||||
$container->try_append(shadowTag::fromStyleRecord($path->style));
|
||||
$container->try_append(lineColorTag::fromStyleRecord($path->style));
|
||||
$container->try_append(fillColorTag::fromStyleRecord($path->style));
|
||||
if ($matrixTransform !== null) {
|
||||
$container->try_append(matrixTransformTag::fromMatrixTransform($matrixTransform));
|
||||
}
|
||||
if ($colorTransform !== null) {
|
||||
$container = $container->transitionColor($colorTransform);
|
||||
}
|
||||
if ($clip !== null) {
|
||||
//TODO: matrix transform?
|
||||
//$container->try_append(new clipTag($clip));
|
||||
}
|
||||
$container->try_append(new drawTag($path->commands));
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?ASSPositioningTag {
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
public static function fromStyleRecord(StyleRecord $record): ?ASSStyleTag {
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
if ($tag instanceof $this and count($this->tags) === count($tag->tags)) {
|
||||
$tags = $this->tags;
|
||||
$otherTags = $tag->tags;
|
||||
foreach ($tags as $i => $t) {
|
||||
foreach ($otherTags as $j => $t2) {
|
||||
if ($t->equals($t2)) {
|
||||
unset($tags[$i]);
|
||||
unset($otherTags[$j]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (isset($tags[$i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count($tags) === 0 and count($otherTags) === 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
$ret = "";
|
||||
foreach ($this->tags as $tag) {
|
||||
$ret .= $tag->encode();
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
}
|
10
src/ass/drawTag.php
Normal file
10
src/ass/drawTag.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
class drawTag extends drawingTag {
|
||||
|
||||
public function encode(): string {
|
||||
return "\\p1}" . implode(" ", $this->getCommands()) . "{\\p0";
|
||||
}
|
||||
}
|
83
src/ass/drawingTag.php
Normal file
83
src/ass/drawingTag.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\CubicCurveRecord;
|
||||
use swf2ass\CubicSplineCurveRecord;
|
||||
use swf2ass\LineRecord;
|
||||
use swf2ass\LineStyleRecord;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\MoveRecord;
|
||||
use swf2ass\QuadraticCurveRecord;
|
||||
use swf2ass\Record;
|
||||
use swf2ass\Shape;
|
||||
use swf2ass\StyleRecord;
|
||||
|
||||
abstract class drawingTag implements ASSTag {
|
||||
const PRECISION = 2;
|
||||
|
||||
protected Shape $shape;
|
||||
|
||||
public function __construct(Shape $shape) {
|
||||
$this->shape = $shape;
|
||||
}
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): drawingTag {
|
||||
return new $this($transform->applyToShape($this->shape));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getCommands(): array {
|
||||
$commands = [];
|
||||
/** @var ?Record $lastEdge */
|
||||
$lastEdge = null;
|
||||
foreach ($this->shape->edges as $edge) {
|
||||
if ($edge instanceof MoveRecord) {
|
||||
$coords = $edge->coord->toPixel();
|
||||
$commands[] = "m " . round($coords->x, self::PRECISION) . " " . round($coords->y, self::PRECISION) . " ";
|
||||
} else if ($edge instanceof LineRecord) {
|
||||
$coords = $edge->coord->toPixel();
|
||||
$commands[] = "l " . round($coords->x, self::PRECISION) . " " . round($coords->y, self::PRECISION) . " ";
|
||||
} else if ($edge instanceof QuadraticCurveRecord or $edge instanceof CubicCurveRecord or $edge instanceof CubicSplineCurveRecord) {
|
||||
if ($edge instanceof QuadraticCurveRecord) {
|
||||
$edge = CubicCurveRecord::fromQuadraticRecord($edge);
|
||||
}
|
||||
|
||||
if ($edge instanceof CubicCurveRecord) {
|
||||
$control1 = $edge->control1->toPixel();
|
||||
$control2 = $edge->control2->toPixel();
|
||||
$anchor = $edge->anchor->toPixel();
|
||||
$commands[] = "b " . round($control1->x, self::PRECISION) . " " . round($control1->y, self::PRECISION) . " " . round($control2->x, self::PRECISION) . " " . round($control2->y, self::PRECISION) . " " . round($anchor->x, self::PRECISION) . " " . round($anchor->y, self::PRECISION) . " ";
|
||||
}
|
||||
|
||||
//TODO
|
||||
if ($edge instanceof CubicSplineCurveRecord) {
|
||||
$anchor = $edge->anchor->toPixel();
|
||||
$controlPoints = [];
|
||||
foreach ($edge->control as $control) {
|
||||
$control = $control->toPixel();
|
||||
$controlPoints[] = round($control->x, self::PRECISION) . " " . round($control->y, self::PRECISION);
|
||||
}
|
||||
|
||||
$commands[] = "s " . implode(" ", $controlPoints) . " " . round($anchor->x, self::PRECISION) . " " . round($anchor->y, self::PRECISION) . " ";
|
||||
}
|
||||
|
||||
} else {
|
||||
var_dump($edge);
|
||||
throw new \Exception("Invalid edge " . get_class($edge));
|
||||
}
|
||||
|
||||
$lastEdge = $edge;
|
||||
}
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->encode() === $tag->encode();
|
||||
}
|
||||
|
||||
}
|
31
src/ass/fillColorTag.php
Normal file
31
src/ass/fillColorTag.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Color;
|
||||
use swf2ass\FillStyleRecord;
|
||||
use swf2ass\Gradient;
|
||||
use swf2ass\StyleRecord;
|
||||
use swf2ass\Utils;
|
||||
|
||||
class fillColorTag extends colorTag {
|
||||
public static function fromStyleRecord(StyleRecord $record): ?fillColorTag {
|
||||
if ($record instanceof FillStyleRecord) {
|
||||
/** @var ?Color $color */
|
||||
$color = null;
|
||||
if ($record->fill instanceof Color) {
|
||||
$color = $record->fill;
|
||||
} else if ($record->fill instanceof Gradient) { //TODO: split this elsewhere
|
||||
$color = $record->fill->getItems()[0]->color;
|
||||
} else {
|
||||
throw new \Exception("Invalid Fill record");
|
||||
}
|
||||
return new fillColorTag($color);
|
||||
}
|
||||
return new fillColorTag(new Color(0, 0, 0, 255));
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\1c&H" . strtoupper(Utils::padHex(dechex($this->color->b))) . strtoupper(Utils::padHex(dechex($this->color->g))) . strtoupper(Utils::padHex(dechex($this->color->r))) . "&\\1a&H" . strtoupper(Utils::padHex(dechex($this->color->alpha))) . "&";
|
||||
}
|
||||
}
|
10
src/ass/insideClipTag.php
Normal file
10
src/ass/insideClipTag.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
class insideClipTag extends drawingTag {
|
||||
|
||||
public function encode(): string {
|
||||
return "\\iclip(" . implode(" ", $this->getCommands()) . ")";
|
||||
}
|
||||
}
|
21
src/ass/lineColorTag.php
Normal file
21
src/ass/lineColorTag.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Color;
|
||||
use swf2ass\LineStyleRecord;
|
||||
use swf2ass\StyleRecord;
|
||||
use swf2ass\Utils;
|
||||
|
||||
class lineColorTag extends colorTag {
|
||||
public static function fromStyleRecord(StyleRecord $record): ?lineColorTag {
|
||||
if ($record instanceof LineStyleRecord) {
|
||||
return new lineColorTag($record->color);
|
||||
}
|
||||
return new lineColorTag(new Color(0, 0, 0, 255));
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\3c&H" . strtoupper(Utils::padHex(dechex($this->color->b))) . strtoupper(Utils::padHex(dechex($this->color->g))) . strtoupper(Utils::padHex(dechex($this->color->r))) . "&\\3a&H" . strtoupper(Utils::padHex(dechex($this->color->alpha))) . "&";
|
||||
}
|
||||
}
|
65
src/ass/matrixTransformTag.php
Normal file
65
src/ass/matrixTransformTag.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\MatrixTransform;
|
||||
|
||||
class matrixTransformTag implements ASSPositioningTag {
|
||||
|
||||
private ?scaleTag $scale = null;
|
||||
private ?rotationTag $rotation = null;
|
||||
private ?shearingTag $shear = null;
|
||||
private ?positionTag $position = null;
|
||||
|
||||
public function __construct() {
|
||||
|
||||
}
|
||||
|
||||
public function transitionMatrixTransform(MatrixTransform $transform): ?matrixTransformTag {
|
||||
if ($this->position !== null and $this->position->transitionMatrixTransform($transform) === null) {
|
||||
return null;
|
||||
}
|
||||
if ($this->scale !== null and $this->scale->transitionMatrixTransform($transform) === null) {
|
||||
return null;
|
||||
}
|
||||
if ($this->rotation !== null and $this->rotation->transitionMatrixTransform($transform) === null) {
|
||||
return null;
|
||||
}
|
||||
if ($this->shear !== null and $this->shear->transitionMatrixTransform($transform) === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::fromMatrixTransform($transform);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
$tag = "";
|
||||
if ($this->scale !== null) {
|
||||
$tag .= $this->scale->encode();
|
||||
}
|
||||
if ($this->rotation !== null) {
|
||||
$tag .= $this->rotation->encode();
|
||||
}
|
||||
if ($this->shear !== null) {
|
||||
$tag .= $this->shear->encode();
|
||||
}
|
||||
if ($this->position !== null) {
|
||||
$tag .= $this->position->encode();
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->position->equals($tag->position);
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?matrixTransformTag {
|
||||
$tag = new matrixTransformTag();
|
||||
$tag->scale = scaleTag::fromMatrixTransform($transform);
|
||||
$tag->rotation = rotationTag::fromMatrixTransform($transform);
|
||||
$tag->shear = shearingTag::fromMatrixTransform($transform);
|
||||
$tag->position = positionTag::fromMatrixTransform($transform);
|
||||
|
||||
return $tag;
|
||||
}
|
||||
}
|
35
src/ass/positionTag.php
Normal file
35
src/ass/positionTag.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\StyleRecord;
|
||||
use swf2ass\Vector2;
|
||||
|
||||
class positionTag implements ASSPositioningTag {
|
||||
|
||||
private Vector2 $position;
|
||||
|
||||
public function __construct(Vector2 $position) {
|
||||
$this->position = $position;
|
||||
}
|
||||
|
||||
public function transitionMatrixTransform(MatrixTransform $transform): ?positionTag {
|
||||
$translation = $transform->getTranslation()->divide(Constants::TWIP_SIZE);
|
||||
return $this->position->equals($translation) ? self::fromMatrixTransform($transform) : null;
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\pos({$this->position->x},{$this->position->y})";
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->position->equals($tag->position);
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?positionTag {
|
||||
$translation = $transform->getTranslation()->divide(Constants::TWIP_SIZE);
|
||||
return new positionTag($translation);
|
||||
}
|
||||
}
|
36
src/ass/rotationTag.php
Normal file
36
src/ass/rotationTag.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\StyleRecord;
|
||||
use swf2ass\Vector2;
|
||||
|
||||
class rotationTag implements ASSPositioningTag {
|
||||
|
||||
private Vector2 $rotate;
|
||||
|
||||
public function __construct(Vector2 $rotate) {
|
||||
$this->rotate = $rotate;
|
||||
}
|
||||
|
||||
public function transitionMatrixTransform(MatrixTransform $transform): ?rotationTag {
|
||||
return self::fromMatrixTransform($transform);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\frx{$this->rotate->x}\\fry{$this->rotate->y}";
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->rotate->equals($tag->rotate);
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?rotationTag {
|
||||
$scale = $transform->getScale();
|
||||
//$rotation = $transform->getRotateSkew(); ????
|
||||
|
||||
return new rotationTag(new Vector2($scale->y < 0 ? 180 : 0, $scale->x < 0 ? 180 : 0));
|
||||
}
|
||||
}
|
35
src/ass/scaleTag.php
Normal file
35
src/ass/scaleTag.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\StyleRecord;
|
||||
use swf2ass\Vector2;
|
||||
|
||||
class scaleTag implements ASSPositioningTag {
|
||||
|
||||
private Vector2 $scale;
|
||||
|
||||
public function __construct(Vector2 $scale) {
|
||||
$this->scale = $scale;
|
||||
}
|
||||
|
||||
public function transitionMatrixTransform(MatrixTransform $transform): ?scaleTag {
|
||||
return self::fromMatrixTransform($transform);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\fscx{$this->scale->x}\\fscy{$this->scale->y}";
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->scale->equals($tag->scale);
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?scaleTag {
|
||||
$scale = $transform->getScale();
|
||||
|
||||
return new scaleTag($scale->abs()->multiply(100));
|
||||
}
|
||||
}
|
33
src/ass/shadowTag.php
Normal file
33
src/ass/shadowTag.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\StyleRecord;
|
||||
|
||||
class shadowTag implements ASSStyleTag {
|
||||
|
||||
/** @var int|float */
|
||||
private $depth;
|
||||
|
||||
public function __construct($depth = 0) {
|
||||
$this->depth = $depth;
|
||||
}
|
||||
|
||||
public function transitionStyleRecord(StyleRecord $record): ?shadowTag {
|
||||
return self::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\shad{$this->depth}";
|
||||
}
|
||||
|
||||
public static function fromStyleRecord(StyleRecord $record): ?shadowTag {
|
||||
//TODO?
|
||||
return new shadowTag(0);
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and abs($this->depth - $tag->depth) <= Constants::EPSILON;
|
||||
}
|
||||
}
|
36
src/ass/shearingTag.php
Normal file
36
src/ass/shearingTag.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\StyleRecord;
|
||||
use swf2ass\Vector2;
|
||||
|
||||
class shearingTag implements ASSPositioningTag {
|
||||
|
||||
private Vector2 $shear;
|
||||
|
||||
public function __construct(Vector2 $shear) {
|
||||
$this->shear = $shear;
|
||||
}
|
||||
|
||||
public function transitionMatrixTransform(MatrixTransform $transform): ?shearingTag {
|
||||
return self::fromMatrixTransform($transform);
|
||||
}
|
||||
|
||||
public function encode(): string {
|
||||
return "\\fax{$this->shear->x}\\fay{$this->shear->y}";
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->shear->equals($tag->shear);
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?shearingTag {
|
||||
$skew = $transform->getRotateSkew();
|
||||
//TODO? maybe have to split this
|
||||
|
||||
return new shearingTag($skew);
|
||||
}
|
||||
}
|
54
swf2ass.php
54
swf2ass.php
|
@ -22,9 +22,57 @@ $fp = fopen($argv[2], "w+");
|
|||
|
||||
$headerTag = $swf->getElementsByTagName("Header")->item(0);
|
||||
if ($headerTag instanceof DOMElement) {
|
||||
foreach (\swf2ass\Utils::processSWF($headerTag, $soundStream) as $line) {
|
||||
$processor = new \swf2ass\SWFProcessor($headerTag);
|
||||
$assRenderer = new \swf2ass\ass\ASSRenderer($processor->getFrameRate(), $processor->getViewPort());
|
||||
|
||||
$keyFrameInterval = 10 * $processor->getFrameRate(); //kf every 10 seconds
|
||||
$frameOffset = 0;
|
||||
while(($frame = $processor->nextFrameOutput()) !== null){
|
||||
$audio = $processor->getAudio();
|
||||
if($audio !== null and $frameOffset === 0){
|
||||
if($audio->start === null){
|
||||
continue;
|
||||
}
|
||||
$frameOffset = $audio->start;
|
||||
}
|
||||
|
||||
$frame->setFrameOffset($frameOffset);
|
||||
|
||||
$rendered = $frame->getFrame()->render(0, [], \swf2ass\ColorTransform::identity(), \swf2ass\MatrixTransform::identity());
|
||||
$drawCalls = 0;
|
||||
$drawItems = 0;
|
||||
$clipCalls = 0;
|
||||
$clipItems = 0;
|
||||
foreach ($rendered->getObjects() as $object){
|
||||
if($object->clip !== null){
|
||||
++$clipCalls;
|
||||
$clipItems += count($object->clip->edges);
|
||||
}
|
||||
foreach ($object->drawPathList->commands as $path){
|
||||
++$drawCalls;
|
||||
$drawItems += count($path->commands->edges);
|
||||
}
|
||||
}
|
||||
echo "=== frame ".$frame->getFrameNumber()." ~ $frameOffset : Depth count: " . count($frame->getFrame()->getDepthMap()) . " :: Object count: " . count($rendered->getObjects()) ." :: Paths: $drawCalls draw calls, $drawItems items :: Clips: $clipCalls draw calls, $clipItems items" . PHP_EOL;
|
||||
|
||||
foreach ($assRenderer->renderFrame($frame, $rendered) as $line){
|
||||
fwrite($fp, $line . "\n");
|
||||
}
|
||||
|
||||
if($frame->getFrameNumber() > 0 and $frame->getFrameNumber() % $keyFrameInterval === 0){
|
||||
foreach ($assRenderer->flush() as $line){
|
||||
fwrite($fp, $line . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
foreach ($assRenderer->flush() as $line){
|
||||
fwrite($fp, $line . "\n");
|
||||
}
|
||||
|
||||
/*foreach (\swf2ass\Utils::processSWF($headerTag, $soundStream) as $line) {
|
||||
fwrite($fp, $line);
|
||||
};
|
||||
};*/
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -32,6 +80,6 @@ if($soundStream !== ""){
|
|||
file_put_contents($argv[2] . ".mp3", $soundStream);
|
||||
}*/
|
||||
|
||||
|
||||
fclose($fp);
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue