Added baking mode, fallback for matrix transforms in ASS
This commit is contained in:
parent
087d992842
commit
9c38498737
|
@ -12,6 +12,7 @@
|
|||
"ext-dom": "*",
|
||||
"ext-imagick": "*",
|
||||
"ext-zlib": "*",
|
||||
"ext-gmp": "*"
|
||||
"ext-gmp": "*",
|
||||
"markrogoyski/math-php": "2.*"
|
||||
}
|
||||
}
|
||||
|
|
80
composer.lock
generated
80
composer.lock
generated
|
@ -4,8 +4,84 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1a514369d72ef0aef2c5763c81494e78",
|
||||
"packages": [],
|
||||
"content-hash": "c0f6ba8f3e0787ea4c8bdccdb45dd2ce",
|
||||
"packages": [
|
||||
{
|
||||
"name": "markrogoyski/math-php",
|
||||
"version": "v2.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/markrogoyski/math-php.git",
|
||||
"reference": "ca71ca97dc136e7bb9e9e1fe05782f343a5692d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/markrogoyski/math-php/zipball/ca71ca97dc136e7bb9e9e1fe05782f343a5692d4",
|
||||
"reference": "ca71ca97dc136e7bb9e9e1fe05782f343a5692d4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=7.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.0",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"phploc/phploc": "*",
|
||||
"phpmd/phpmd": "^2.6",
|
||||
"phpstan/phpstan": "*",
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MathPHP\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Rogoyski",
|
||||
"email": "mark@rogoyski.com",
|
||||
"homepage": "https://github.com/markrogoyski",
|
||||
"role": "Lead developer"
|
||||
},
|
||||
{
|
||||
"name": "Kevin Nowaczyk",
|
||||
"homepage": "https://github.com/Beakerboy",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "MathPHP Community of Contributors",
|
||||
"homepage": "https://github.com/markrogoyski/math-php/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Math Library for PHP. Features descriptive statistics and regressions; Continuous and discrete probability distributions; Linear algebra with matrices and vectors, Numerical analysis; special mathematical functions; Algebra",
|
||||
"homepage": "https://github.com/markrogoyski/math-php/",
|
||||
"keywords": [
|
||||
"algebra",
|
||||
"combinatorics",
|
||||
"distributions",
|
||||
"linear algebra",
|
||||
"math",
|
||||
"mathematics",
|
||||
"matrix",
|
||||
"numerical analysis",
|
||||
"probability",
|
||||
"regressions",
|
||||
"statistics"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/markrogoyski/math-php/issues",
|
||||
"source": "https://github.com/markrogoyski/math-php/tree/v2.5.0"
|
||||
},
|
||||
"time": "2021-11-22T05:14:07+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
|
|
|
@ -147,7 +147,7 @@ foreach ($frames as $frame) {
|
|||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
ScaledBorderAndShadow: yes
|
||||
YCbCr Matrix: TV.709
|
||||
YCbCr Matrix: PC.709
|
||||
PlayResX: $resX
|
||||
PlayResY: $resY
|
||||
|
||||
|
|
|
@ -44,8 +44,8 @@ class BitmapDefinition implements ObjectDefinition {
|
|||
$i = 0;
|
||||
for ($y = 0; $y < $size->y; ++$y) {
|
||||
for ($x = 0; $x < $size->x; ++$x) {
|
||||
$pixel = unpack("Ca/Cr/Cg/Cb", "\x60\x00\x00\x00");
|
||||
$pixels[$y][$x] = new Color($pixel["r"], $pixel["g"], $pixel["b"], $pixel["a"] !== 255 ? $pixel["a"] : null);
|
||||
$pixel = unpack("Ca/Cr/Cg/Cb", "\x10\x00\x00\x00");
|
||||
$pixels[$y][$x] = new Color($pixel["r"], $pixel["g"], $pixel["b"], $pixel["a"]); //TODO: check transparency
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ class BitmapDefinition implements ObjectDefinition {
|
|||
for ($y = 0; $y < $size->y; ++$y) {
|
||||
for ($x = 0; $x < $size->x; ++$x) {
|
||||
$pixel = unpack("Ca/Cr/Cg/Cb", substr($content, $i * 4, 4));
|
||||
$pixels[$y][$x] = new Color($pixel["r"], $pixel["g"], $pixel["b"], $pixel["a"] !== 255 ? $pixel["a"] : null);
|
||||
$pixels[$y][$x] = new Color($pixel["r"], $pixel["g"], $pixel["b"], $pixel["a"]); //TODO: check transparency
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ class Color {
|
|||
public $b;
|
||||
public $alpha;
|
||||
|
||||
public function __construct($r, $g, $b, $alpha = 0) {
|
||||
public function __construct($r, $g, $b, $alpha = 255) {
|
||||
$this->r = $r;
|
||||
$this->g = $g;
|
||||
$this->b = $b;
|
||||
|
@ -23,18 +23,11 @@ class Color {
|
|||
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 === 0) and $nullAlpha) {
|
||||
|
||||
} else {
|
||||
$c .= "\\1a&H" . strtoupper(Utils::padHex(dechex($this->alpha ?? 0))) . "&";
|
||||
}
|
||||
|
||||
return $c;
|
||||
public static function fromXML(\DOMElement $element): Color {
|
||||
return new Color((int)$element->getAttribute("red"), (int)$element->getAttribute("green"), (int)$element->getAttribute("blue"), $element->hasAttribute("alpha") ? (int)$element->getAttribute("alpha") : 255);
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element): Color {
|
||||
return new Color((int)$element->getAttribute("red"), (int)$element->getAttribute("green"), (int)$element->getAttribute("blue"), $element->hasAttribute("alpha") ? (int)$element->getAttribute("alpha") : 0);
|
||||
public function toString($noAlpha = false){
|
||||
return $noAlpha ? "rgb({$this->r},{$this->g},{$this->b})" : "rgba({$this->r},{$this->g},{$this->b},{$this->alpha})";
|
||||
}
|
||||
}
|
|
@ -43,11 +43,11 @@ class ColorTransform {
|
|||
|
||||
public function combine(ColorTransform $transform) {
|
||||
//TODO: maybe these get altered all at once?
|
||||
return new ColorTransform($this->applyMultiplyToColor($transform->mult), $this->applyAdditionToColor($transform->add));
|
||||
return new ColorTransform($this->applyMultiplyToColor($transform->mult), $this->applyAdditionToColor($this->applyMultiplyToColor($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)));
|
||||
return ($this->mult === $other->mult or ($this->mult !== null and $this->mult->equals($other->mult))) and ($this->add === $other->add or ($this->add !== null and $this->add->equals($other->add)));
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element): ColorTransform {
|
||||
|
|
|
@ -23,8 +23,8 @@ 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 function applyMatrixTransform(MatrixTransform $transform, bool $applyTranslation = true): CubicCurveRecord {
|
||||
return new CubicCurveRecord($transform->applyToVector($this->control1, $applyTranslation), $transform->applyToVector($this->control2, $applyTranslation), $transform->applyToVector($this->anchor, $applyTranslation), $transform->applyToVector($this->start, $applyTranslation));
|
||||
}
|
||||
|
||||
public static function fromQuadraticRecord(QuadraticCurveRecord $q): CubicCurveRecord {
|
||||
|
|
|
@ -27,13 +27,13 @@ class CubicSplineCurveRecord implements Record {
|
|||
return new CubicSplineCurveRecord(array_reverse($this->control), $this->start, $this->anchor);
|
||||
}
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): CubicSplineCurveRecord {
|
||||
public function applyMatrixTransform(MatrixTransform $transform, bool $applyTranslation = true): CubicSplineCurveRecord {
|
||||
$control = [];
|
||||
foreach ($this->control as $c) {
|
||||
$control[] = $transform->applyToVector($c);
|
||||
$control[] = $transform->applyToVector($c, $applyTranslation);
|
||||
}
|
||||
|
||||
return new CubicSplineCurveRecord($control, $transform->applyToVector($this->anchor), $transform->applyToVector($this->start));
|
||||
return new CubicSplineCurveRecord($control, $transform->applyToVector($this->anchor, $applyTranslation), $transform->applyToVector($this->start, $applyTranslation));
|
||||
}
|
||||
|
||||
public function append(Record $record): ?CubicSplineCurveRecord {
|
||||
|
|
13
src/DecomposedMatrixTransform.php
Normal file
13
src/DecomposedMatrixTransform.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass;
|
||||
|
||||
|
||||
class DecomposedMatrixTransform {
|
||||
|
||||
public Vector2 $translation;
|
||||
public $rotation;
|
||||
public Vector2 $scale;
|
||||
public Vector2 $skew;
|
||||
|
||||
}
|
|
@ -38,8 +38,8 @@ class JPEGBitmapDefinition extends BitmapDefinition {
|
|||
$iterator = $im->getPixelIterator();
|
||||
foreach ($iterator as $row => $p) {
|
||||
foreach ($p as $col => $pixel) {
|
||||
$c = $pixel->getColor(true);
|
||||
$pixels[$row][$col] = new \swf2ass\Color((int)round($c["r"] * 255), (int)round($c["g"] * 255), (int)round($c["b"] * 255), isset($c["a"]) ? (int)round((1 - $c["a"]) * 255) : null);
|
||||
$c = $pixel->getColor(2);
|
||||
$pixels[$row][$col] = new \swf2ass\Color($c["r"], $c["g"], $c["b"], $c["a"] ?? 255);
|
||||
}
|
||||
}
|
||||
//TODO: add transparency
|
||||
|
|
|
@ -23,7 +23,7 @@ class LineRecord implements Record {
|
|||
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));
|
||||
public function applyMatrixTransform(MatrixTransform $transform, bool $applyTranslation = true): LineRecord {
|
||||
return new LineRecord($transform->applyToVector($this->coord, $applyTranslation), $transform->applyToVector($this->start, $applyTranslation));
|
||||
}
|
||||
}
|
224
src/Matrix2D.php
224
src/Matrix2D.php
|
@ -1,224 +0,0 @@
|
|||
<?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 __toString() : string{
|
||||
$str = "";
|
||||
for($y = 0; $y < $this->size->y; ++$y){
|
||||
$str .= "| ";
|
||||
for($x = 0; $x < $this->size->x; ++$x){
|
||||
$pad = 0;
|
||||
for($y2 = 0; $y2 < $this->size->y; ++$y2){
|
||||
$len = strlen((string) $this->internalGet($x, $y2));
|
||||
if($pad < $len){
|
||||
$pad = $len;
|
||||
}
|
||||
}
|
||||
$str .= str_pad((string) $this->internalGet($x, $y), $pad, " ", STR_PAD_LEFT) . " ";
|
||||
}
|
||||
$str .= "|\n";
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
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(2, 0) === 0 and $this->internalGet(2, 1) === 0 and $this->internalGet(2, 2) === 1;
|
||||
}
|
||||
|
||||
private function isVector2(): bool {
|
||||
return $this->size->x === 3 and $this->size->y === 1 and $this->internalGet(2, 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($matrix->size->x, $this->size->y));
|
||||
for ($i = 0; $i < $this->size->y; ++$i) {
|
||||
for ($j = 0; $j < $matrix->size->x; ++$j) {
|
||||
$sum = 0;
|
||||
for ($k = 0; $k < $this->size->x; ++$k) {
|
||||
$sum += $this->internalGet($k, $i) * $matrix->internalGet($j, $k);
|
||||
}
|
||||
$result->internalSet($j, $i, $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;
|
||||
}
|
||||
|
||||
}
|
|
@ -3,27 +3,20 @@
|
|||
namespace swf2ass;
|
||||
|
||||
|
||||
class MatrixTransform {
|
||||
private const IDENTITY = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
|
||||
use MathPHP\LinearAlgebra\MatrixFactory;
|
||||
use MathPHP\LinearAlgebra\NumericMatrix;
|
||||
|
||||
private Matrix2D $matrix;
|
||||
class MatrixTransform {
|
||||
|
||||
private NumericMatrix $matrix;
|
||||
private Vector2 $translation;
|
||||
|
||||
public function __construct(?Vector2 $scale, ?Vector2 $rotateSkew, ?Vector2 $translation) {
|
||||
$matrix = self::IDENTITY;
|
||||
|
||||
if ($scale !== null) {
|
||||
/* a */ $matrix[0][0] = $scale->x;
|
||||
/* d */ $matrix[1][1] = $scale->y;
|
||||
}
|
||||
if ($rotateSkew !== null) {
|
||||
/* c */ $matrix[1][0] = $rotateSkew->x;
|
||||
/* b */ $matrix[0][1] = $rotateSkew->y;
|
||||
}
|
||||
if ($translation !== null) {
|
||||
/* tx */ $matrix[2][0] = $translation->x;
|
||||
/* ty */ $matrix[2][1] = $translation->y;
|
||||
}
|
||||
$this->matrix = Matrix2D::from2DArray($matrix);
|
||||
$this->translation = $translation ?? new Vector2(0, 0);
|
||||
$this->matrix = MatrixFactory::createNumeric([
|
||||
[$scale !== null ? $scale->x : 1, $rotateSkew !== null ? $rotateSkew->y : 0],
|
||||
[$rotateSkew !== null ? $rotateSkew->x : 0, $scale !== null ? $scale->y : 1]
|
||||
]);
|
||||
}
|
||||
|
||||
public static function identity(): MatrixTransform {
|
||||
|
@ -32,98 +25,143 @@ class MatrixTransform {
|
|||
|
||||
public function combine(MatrixTransform $other): MatrixTransform {
|
||||
$result = clone $this;
|
||||
$result->matrix = $this->matrix->product($other->matrix);
|
||||
$result->matrix = $this->matrix->multiply($other->matrix);
|
||||
$result->translation = $this->translation->add($other->translation); //TODO check
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getScaleX() : float {
|
||||
$a = $this->matrix->get(new Vector2(0, 0));
|
||||
$b = $this->matrix->get(new Vector2(1, 0));
|
||||
return sqrt($a * $a + $b * $b) * 100;
|
||||
public function get_a(){
|
||||
return $this->matrix->get(0, 0);
|
||||
}
|
||||
|
||||
public function getScaleY() : float {
|
||||
$c = $this->matrix->get(new Vector2(0, 1));
|
||||
$d = $this->matrix->get(new Vector2(1, 1));
|
||||
return sqrt($c * $c + $d * $d) * 100;
|
||||
public function get_b(){
|
||||
return $this->matrix->get(0, 1);
|
||||
}
|
||||
|
||||
public function getScale(): Vector2 {
|
||||
return new Vector2($this->getScaleX(), $this->getScaleY());
|
||||
public function get_c(){
|
||||
return $this->matrix->get(1, 0);
|
||||
}
|
||||
|
||||
public function getSkewX(): float {
|
||||
$px = $this->applyToVector(new Vector2( 0, 1));
|
||||
return atan2($px->y, $px->x) - M_PI_2;
|
||||
public function get_d(){
|
||||
return $this->matrix->get(1, 1);
|
||||
}
|
||||
|
||||
public function getSkewY(): float {
|
||||
$py = $this->applyToVector(new Vector2( 1, 0));
|
||||
return atan2($py->y, $py->x);
|
||||
public function get_e(){
|
||||
return $this->translation->x;
|
||||
}
|
||||
|
||||
public function getSkew(): Vector2 {
|
||||
return new Vector2($this->getSkewX(), $this->getSkewY());
|
||||
public function get_f(){
|
||||
return $this->translation->y;
|
||||
}
|
||||
|
||||
public function getShearX(): float {
|
||||
return $this->matrix->get(new Vector2(0, 1));
|
||||
/**
|
||||
* Do special decomposition that maps all values into scale(X,Y), rotation(), skew(X,0), translation()
|
||||
* @return DecomposedMatrixTransform
|
||||
* @throws \MathPHP\Exception\IncorrectTypeException
|
||||
* @throws \MathPHP\Exception\MathException
|
||||
* @throws \MathPHP\Exception\MatrixException
|
||||
*/
|
||||
public function decompose() : DecomposedMatrixTransform{
|
||||
$result = new DecomposedMatrixTransform();
|
||||
|
||||
|
||||
/*$rotationX = atan2($this->get_b(), $this->get_a());
|
||||
$rotationY = atan2(-$this->get_c(), $this->get_d());
|
||||
|
||||
$result->rotation = $rotationX;
|
||||
$result->skew = new Vector2($rotationY - $rotationX, 0);
|
||||
|
||||
$result->scale = new Vector2(sqrt($this->get_a() * $this->get_a() + $this->get_b() * $this->get_b()), $this->get_c() * $this->get_c() + $this->get_d() * $this->get_d());
|
||||
$result->translation = $this->translation;
|
||||
return $result;*/
|
||||
|
||||
|
||||
$result->rotation = atan($this->get_c() / $this->get_d());
|
||||
|
||||
$scaleX = sqrt($this->get_a() * $this->get_a() + $this->get_b() * $this->get_b());
|
||||
$scaleY = sqrt($this->get_c() * $this->get_c() + $this->get_d() * $this->get_d());
|
||||
|
||||
$result->scale = new Vector2($this->get_a(), $this->get_d());
|
||||
$result->skew = new Vector2($this->get_c(), $this->get_b());
|
||||
$result->translation = $this->translation;
|
||||
return $result;
|
||||
|
||||
$scaleX = sqrt($this->get_a() * $this->get_a() + $this->get_b() * $this->get_b());
|
||||
$scaleY = sqrt($this->get_c() * $this->get_c() + $this->get_d() * $this->get_d());
|
||||
|
||||
|
||||
$px = $this->applyToVector(new Vector2(0, 1), false);
|
||||
$py = $this->applyToVector(new Vector2(1, 0), false);
|
||||
$rad = -atan2($px->y, $px->x) - M_PI_2; //TODO: maybe -
|
||||
|
||||
$skewX = atan2($px->y, $px->x) - M_PI_2;
|
||||
$skewY = atan2($py->y, $py->x);
|
||||
|
||||
var_dump($scaleX);
|
||||
var_dump($scaleY);
|
||||
|
||||
$q1 = MatrixFactory::createNumeric([
|
||||
[cos($rad), sin($rad)],
|
||||
[-sin($rad), cos($rad)]
|
||||
]);
|
||||
$a = MatrixFactory::createNumeric([
|
||||
[1, $this->get_b()],
|
||||
[$this->get_c(), 1]
|
||||
]);
|
||||
|
||||
$QR = $a->qrDecomposition();
|
||||
[$q2, $r] = [$QR->Q, $QR->R];
|
||||
|
||||
$I = MatrixFactory::diagonal(array_map(function ($i){ //Calculate sign
|
||||
return $i < 0 ? -1 : ($i === 0 ? 0 : 1);
|
||||
}, $r->diagonal()->getDiagonalElements()));
|
||||
|
||||
|
||||
$r = $I->multiply($r);
|
||||
|
||||
/*$r = $r->multiply(MatrixFactory::createNumeric([
|
||||
[$this->get_a(), 0],
|
||||
[0, $this->get_c()]
|
||||
]));*/
|
||||
|
||||
$q2 = $q2->multiply($I);
|
||||
$q = $q1->multiply($q2);
|
||||
|
||||
$result->rotation = -atan2($q->get(1, 0), $q->get(0, 0));
|
||||
$result->skew = new Vector2($r->get(0, 1), 0);
|
||||
$result->scale = new Vector2($r->get(0, 0), $r->get(1, 1));
|
||||
$result->translation = $this->translation;
|
||||
var_dump($r);
|
||||
var_dump($result);
|
||||
exit();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getShearY(): float {
|
||||
return $this->matrix->get(new Vector2(1, 0));
|
||||
}
|
||||
|
||||
public function getShear(): Vector2 {
|
||||
return new Vector2($this->getShearX(), $this->getShearY());
|
||||
}
|
||||
|
||||
public function getTranslationX() {
|
||||
return $this->matrix->get(new Vector2(0, 2));
|
||||
}
|
||||
|
||||
public function getTranslationY() {
|
||||
return $this->matrix->get(new Vector2(1, 2));
|
||||
public function getMatrix() : NumericMatrix{
|
||||
return $this->matrix;
|
||||
}
|
||||
|
||||
public function getTranslation(): Vector2 {
|
||||
return new Vector2($this->getTranslationX(), $this->getTranslationY());
|
||||
return $this->translation;
|
||||
}
|
||||
|
||||
public function getRotationX(): float {
|
||||
$a = $this->matrix->get(new Vector2(0, 0));
|
||||
$b = $this->matrix->get(new Vector2(1, 0));
|
||||
|
||||
return atan2($b, $a);
|
||||
public function applyToVector(Vector2 $vector, bool $applyTranslation = true): Vector2 {
|
||||
$result = MatrixFactory::createNumeric([[$vector->x, $vector->y]])->multiply($this->matrix);
|
||||
return $applyTranslation ? (new Vector2($result->get(0, 0), $result->get(0, 1)))->add($this->translation) : new Vector2($result->get(0, 0), $result->get(0, 1));
|
||||
}
|
||||
|
||||
public function getRotationY(): float {
|
||||
$d = $this->matrix->get(new Vector2(1, 1));
|
||||
$c = $this->matrix->get(new Vector2(0, 1));
|
||||
|
||||
return atan2(-$c, $d);
|
||||
}
|
||||
|
||||
public function getRotation(): Vector2 {
|
||||
return new Vector2($this->getRotationX(), $this->getRotationY());
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
public function applyToShape(Shape $shape): Shape {
|
||||
public function applyToShape(Shape $shape, bool $applyTranslation = true): Shape {
|
||||
$newShape = new Shape();
|
||||
foreach ($shape->edges as $edge) {
|
||||
$newShape->edges[] = $edge->applyMatrixTransform($this);
|
||||
$newShape->edges[] = $edge->applyMatrixTransform($this, $applyTranslation);
|
||||
}
|
||||
|
||||
return $newShape;
|
||||
}
|
||||
|
||||
public function equals(MatrixTransform $other, $epsilon = Constants::EPSILON): bool {
|
||||
return $this->matrix->equals($other->matrix, $epsilon);
|
||||
public function equals(MatrixTransform $other): bool {
|
||||
return $this->translation->equals($other->translation) and $this->matrix->isEqual($other->matrix);
|
||||
}
|
||||
|
||||
static function fromXML(\DOMElement $element): MatrixTransform {
|
||||
|
|
|
@ -23,7 +23,7 @@ class MoveRecord implements Record {
|
|||
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));
|
||||
public function applyMatrixTransform(MatrixTransform $transform, bool $applyTranslation = true): MoveRecord {
|
||||
return new MoveRecord($transform->applyToVector($this->coord, $applyTranslation), $transform->applyToVector($this->start, $applyTranslation));
|
||||
}
|
||||
}
|
|
@ -4,6 +4,4 @@ namespace swf2ass;
|
|||
|
||||
interface MultiFrameObjectDefinition extends ObjectDefinition {
|
||||
public function nextFrame(): ViewFrame;
|
||||
|
||||
public function hasFrame(): bool;
|
||||
}
|
|
@ -21,8 +21,8 @@ class QuadraticCurveRecord implements Record {
|
|||
return new QuadraticCurveRecord($this->control, $this->start, $this->anchor);
|
||||
}
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): QuadraticCurveRecord {
|
||||
return new QuadraticCurveRecord($transform->applyToVector($this->control), $transform->applyToVector($this->anchor), $transform->applyToVector($this->start));
|
||||
public function applyMatrixTransform(MatrixTransform $transform, bool $applyTranslation = true): QuadraticCurveRecord {
|
||||
return new QuadraticCurveRecord($transform->applyToVector($this->control, $applyTranslation), $transform->applyToVector($this->anchor, $applyTranslation), $transform->applyToVector($this->start, $applyTranslation));
|
||||
}
|
||||
|
||||
public static function fromXML(\DOMElement $element, Vector2 $cursor): QuadraticCurveRecord {
|
||||
|
|
|
@ -7,5 +7,5 @@ interface Record {
|
|||
|
||||
public function reverse(): Record;
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): Record;
|
||||
public function applyMatrixTransform(MatrixTransform $transform, bool $applyTranslation = true): Record;
|
||||
}
|
|
@ -8,11 +8,11 @@ class SpriteDefinition implements MultiFrameObjectDefinition {
|
|||
public array $frames;
|
||||
|
||||
public int $frameCounter;
|
||||
public bool $hasFrames = true;
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param ViewFrame[] $frames
|
||||
* @param int $frameCounter
|
||||
*/
|
||||
public function __construct(int $id, array $frames, int $frameCounter = 0) {
|
||||
$this->id = $id;
|
||||
|
@ -32,10 +32,6 @@ class SpriteDefinition implements MultiFrameObjectDefinition {
|
|||
return $list;
|
||||
}
|
||||
|
||||
public function hasFrame(): bool {
|
||||
return $this->hasFrames;
|
||||
}
|
||||
|
||||
public function getFrameCounter(): int {
|
||||
return $this->frameCounter;
|
||||
}
|
||||
|
@ -48,8 +44,7 @@ class SpriteDefinition implements MultiFrameObjectDefinition {
|
|||
$f = $this->frames[$this->frameCounter];
|
||||
++$this->frameCounter;
|
||||
if ($this->frameCounter >= count($this->frames)) {
|
||||
$this->frameCounter = count($this->frames) - 1;
|
||||
$this->hasFrames = false; //TODO LOOP
|
||||
$this->frameCounter = 0;
|
||||
}
|
||||
return $f;
|
||||
}
|
||||
|
|
|
@ -30,19 +30,19 @@ class StyleList {
|
|||
} else if ($node->nodeName === "ClippedBitmap") {
|
||||
Utils::dump_element($node);
|
||||
//TODO
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 200));
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 20));
|
||||
} else if ($node->nodeName === "ClippedBitmap2") {
|
||||
Utils::dump_element($node);
|
||||
//TODO
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 200));
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 20));
|
||||
} else if ($node->nodeName === "TiledBitmap") {
|
||||
//TODO
|
||||
Utils::dump_element($node);
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 200));
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 20));
|
||||
} else if ($node->nodeName === "TiledBitmap2") {
|
||||
//TODO
|
||||
Utils::dump_element($node);
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 200));
|
||||
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 20));
|
||||
} else {
|
||||
Utils::dump_element($node);
|
||||
throw new \Exception("Unknown style " . $node->nodeName);
|
||||
|
@ -72,7 +72,7 @@ class StyleList {
|
|||
Utils::dump_element($node);
|
||||
}
|
||||
|
||||
$lineStyles[] = new LineStyleRecord(max(Constants::TWIP_SIZE, (int)$node->getAttribute("width")), $color, null);
|
||||
$lineStyles[] = new LineStyleRecord(max(Constants::TWIP_SIZE, (int)$node->getAttribute("width")), $color);
|
||||
}
|
||||
}
|
||||
return new StyleList($fillStyles, $lineStyles);
|
||||
|
|
|
@ -15,7 +15,7 @@ class Vector2 {
|
|||
|
||||
|
||||
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);
|
||||
return abs($b->x - $this->x) <= $epsilon and abs($b->y - $this->y) <= $epsilon;
|
||||
}
|
||||
|
||||
public function distance(Vector2 $b): float {
|
||||
|
@ -46,6 +46,18 @@ class Vector2 {
|
|||
return $this->x * $b->x + $this->y * $b->y;
|
||||
}
|
||||
|
||||
public function squaredLength() : float {
|
||||
return $this->x * $this->x + $this->y * $this->y;
|
||||
}
|
||||
|
||||
public function normalize() : Vector2{
|
||||
$len = $this->squaredLength();
|
||||
if($len > 0){
|
||||
return $this->divide(sqrt($len));
|
||||
}
|
||||
return new Vector2(0, 0);
|
||||
}
|
||||
|
||||
public function vectorMultiply(Vector2 $b): Vector2 {
|
||||
return new Vector2($this->x * $b->x, $this->y * $b->y);
|
||||
}
|
||||
|
|
|
@ -71,17 +71,9 @@ class ViewLayout {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
foreach ($this->depthMap as $ob) {
|
||||
if ($ob->hasFrame()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ class ASSLine {
|
|||
* @param RenderedObject $object
|
||||
* @return ASSLine[]
|
||||
*/
|
||||
public static function fromRenderObject(FrameInformation $information, RenderedObject $object): array {
|
||||
public static function fromRenderObject(FrameInformation $information, RenderedObject $object, bool $bakeTransforms = false): array {
|
||||
$lines = [];
|
||||
foreach ($object->drawPathList->commands as $drawPath) {
|
||||
$line = new ASSLine();
|
||||
|
@ -74,7 +74,7 @@ class ASSLine {
|
|||
$line->objectId = $object->objectId;
|
||||
$line->start = $information->getFrameNumber();
|
||||
$line->end = $information->getFrameNumber();
|
||||
$line->tags[] = containerTag::fromPathEntry($drawPath, $object->clip, $object->colorTransform, $object->matrixTransform);
|
||||
$line->tags[] = containerTag::fromPathEntry($drawPath, $object->clip, $object->colorTransform, $object->matrixTransform, $bakeTransforms);
|
||||
$line->name = "o:{$object->objectId} d:" . implode(".", array_slice($object->depth, 1));
|
||||
$lines[] = $line;
|
||||
}
|
||||
|
|
|
@ -12,12 +12,19 @@ class ASSRenderer {
|
|||
|
||||
/** @var ASSLine[] */
|
||||
private array $runningBuffer = [];
|
||||
private array $settings = [];
|
||||
|
||||
public function __construct(float $frameRate, Rectangle $viewPort) {
|
||||
|
||||
public function getSetting($name, $default = null){
|
||||
return $this->settings[$name] ?? $default;
|
||||
}
|
||||
|
||||
public function __construct(float $frameRate, Rectangle $viewPort, array $settings = []) {
|
||||
$display = $viewPort->toPixel();
|
||||
$width = $display->getWidth();
|
||||
$height = $display->getHeight();
|
||||
$ar = $width / $height;
|
||||
$this->settings = $settings;
|
||||
|
||||
if(($frameRate * 2) <= 60){
|
||||
$frameRate *= 2;
|
||||
|
@ -30,7 +37,7 @@ class ASSRenderer {
|
|||
ScriptType: v4.00+
|
||||
WrapStyle: 0
|
||||
ScaledBorderAndShadow: yes
|
||||
YCbCr Matrix: TV.709
|
||||
YCbCr Matrix: PC.709
|
||||
PlayResX: {$width}
|
||||
PlayResY: {$height}
|
||||
|
||||
|
@ -92,7 +99,7 @@ ASSHEADER;
|
|||
}else{
|
||||
$this->runningBuffer = array_merge($this->runningBuffer, $tagsToTransition);
|
||||
|
||||
foreach (ASSLine::fromRenderObject($information, $object) as $line) {
|
||||
foreach (ASSLine::fromRenderObject($information, $object, $this->getSetting("bakeTransforms", false)) as $line) {
|
||||
$line->style = "f";
|
||||
$line->dropCache();
|
||||
$runningBuffer[] = $line;
|
||||
|
|
|
@ -8,10 +8,10 @@ use swf2ass\StyleRecord;
|
|||
|
||||
abstract class colorTag implements ASSStyleTag, ASSColorTag {
|
||||
|
||||
protected Color $color;
|
||||
protected Color $originalColor;
|
||||
protected ?Color $color;
|
||||
protected ?Color $originalColor;
|
||||
|
||||
public function __construct(Color $color, Color $originalColor) {
|
||||
public function __construct(?Color $color, ?Color $originalColor) {
|
||||
$this->color = $color;
|
||||
$this->originalColor = $originalColor;
|
||||
}
|
||||
|
@ -23,11 +23,11 @@ abstract class colorTag implements ASSStyleTag, ASSColorTag {
|
|||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->color->equals($tag->color);
|
||||
return $tag instanceof $this and ($this->color === $tag->color or ($this->color !== null and $this->color->equals($tag->color))) and ($this->originalColor === $tag->originalColor or ($this->originalColor !== null and $this->originalColor->equals($tag->originalColor)));
|
||||
}
|
||||
|
||||
public function applyColorTransform(?ColorTransform $transform): ?colorTag {
|
||||
return $transform !== null ? new $this($transform->applyToColor($this->originalColor), $this->originalColor) : $this;
|
||||
return $transform !== null ? (new $this($this->originalColor !== null ? $transform->applyToColor($this->originalColor) : $this->color, $this->originalColor)) : $this;
|
||||
}
|
||||
|
||||
public function transitionColor(ASSLine $line, ColorTransform $transform): ?colorTag {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace swf2ass\ass;
|
||||
|
||||
|
||||
use MathPHP\LinearAlgebra\MatrixFactory;
|
||||
use swf2ass\ColorTransform;
|
||||
use swf2ass\DrawPath;
|
||||
use swf2ass\MatrixTransform;
|
||||
|
@ -17,6 +18,8 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag {
|
|||
/** @var ASSTag[][] */
|
||||
private array $transitions = [];
|
||||
|
||||
private ?MatrixTransform $bakeTransforms = null;
|
||||
|
||||
public function transitionColor(ASSLine $line, ColorTransform $transform): ?containerTag {
|
||||
$container = clone $this;
|
||||
|
||||
|
@ -40,6 +43,12 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag {
|
|||
}
|
||||
|
||||
public function transitionMatrixTransform(ASSLine $line, MatrixTransform $transform): ?containerTag {
|
||||
if($this->bakeTransforms !== null){
|
||||
if(!$transform->getMatrix()->isEqual($this->bakeTransforms->getMatrix())){ //Do not allow matrix changes but moves
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$container = clone $this;
|
||||
|
||||
$index = $line->end - $line->start - 1;
|
||||
|
@ -99,31 +108,37 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag {
|
|||
}
|
||||
}
|
||||
|
||||
public static function fromPathEntry(DrawPath $path, ?Shape $clip, ?ColorTransform $colorTransform, ?MatrixTransform $matrixTransform): containerTag {
|
||||
public static function fromPathEntry(DrawPath $path, ?Shape $clip, ?ColorTransform $colorTransform, ?MatrixTransform $matrixTransform, bool $bakeTransforms = false): 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)->applyColorTransform($colorTransform));
|
||||
$container->try_append(fillColorTag::fromStyleRecord($path->style)->applyColorTransform($colorTransform));
|
||||
|
||||
$matrixTransform = $matrixTransform ?? MatrixTransform::identity();
|
||||
$container->try_append(positionTag::fromMatrixTransform($matrixTransform));
|
||||
$container->try_append(rotationTag::fromMatrixTransform($matrixTransform));
|
||||
$container->try_append(scaleTag::fromMatrixTransform($matrixTransform));
|
||||
|
||||
if ($clip !== null) {
|
||||
//TODO: matrix transform?
|
||||
//$container->try_append(new clipTag($clip));
|
||||
}
|
||||
|
||||
//$container->try_append(matrixTransformTag::fromMatrixTransform(MatrixTransform::identity()));
|
||||
$drawTag = new drawTag($path->commands);
|
||||
if ($matrixTransform !== null) {
|
||||
//$drawTag = $drawTag->applyMatrixTransform($matrixTransform);
|
||||
|
||||
if($bakeTransforms){
|
||||
$container->bakeTransforms = $matrixTransform;
|
||||
|
||||
$drawTag = new drawTag($path->commands);
|
||||
if(!$matrixTransform->getMatrix()->isEqual(MatrixFactory::identity(2))){
|
||||
$drawTag = $drawTag->applyMatrixTransform($matrixTransform, false);
|
||||
}
|
||||
$pos = $matrixTransform->getTranslation()->toPixel();
|
||||
$container->try_append(new positionTag($pos, $pos, 1, 1));
|
||||
$container->try_append($drawTag);
|
||||
}else{
|
||||
$container->try_append(positionTag::fromMatrixTransform($matrixTransform));
|
||||
$container->try_append(matrixTransformTag::fromMatrixTransform($matrixTransform));
|
||||
|
||||
$container->try_append(new drawTag($path->commands));
|
||||
}
|
||||
$container->try_append($drawTag);
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
@ -169,7 +184,10 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag {
|
|||
continue;
|
||||
}
|
||||
//TODO: clone $line?
|
||||
$ret .= "\\t(" . round($frameDurationMs * $index) . "," . round($frameDurationMs * ($index + 1)) . ",";
|
||||
//TODO: animations with smoothing really don't play well. maybe allow them when only one animation "direction" exists, or smooth them manually?
|
||||
//Or just don't animate MatrixTransform / do it in a single tick
|
||||
$ret .= "\\t(" . floor($frameDurationMs * $index) . "," . (floor($frameDurationMs * ($index + 1))) . ",";
|
||||
//$ret .= "\\t(" . floor($frameDurationMs * ($index + 1)) . "," . floor($frameDurationMs * ($index + 1)) . ",";
|
||||
foreach ($transitions as $tag){
|
||||
$ret .= $tag->encode($line, $frameDurationMs);
|
||||
}
|
||||
|
|
|
@ -4,12 +4,18 @@ namespace swf2ass\ass;
|
|||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\MoveRecord;
|
||||
use swf2ass\Shape;
|
||||
|
||||
class drawTag extends drawingTag {
|
||||
|
||||
protected int $scale;
|
||||
public function __construct(Shape $shape, $scale = 6) {
|
||||
parent::__construct($shape);
|
||||
$this->scale = $scale;
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
$scale = 6;
|
||||
$scaleMultiplier = 2 ** ($scale - 1);
|
||||
return "\\p$scale}" . implode(" ", $this->getCommands($scaleMultiplier, 0)) . "{\\p0";
|
||||
$scaleMultiplier = 2 ** ($this->scale - 1);
|
||||
return "\\p".$this->scale."}" . implode(" ", $this->getCommands($scaleMultiplier, 0)) . "{\\p0";
|
||||
}
|
||||
}
|
|
@ -23,8 +23,8 @@ abstract class drawingTag implements ASSTag {
|
|||
$this->shape = $shape;
|
||||
}
|
||||
|
||||
public function applyMatrixTransform(MatrixTransform $transform): drawingTag {
|
||||
return new $this($transform->applyToShape($this->shape));
|
||||
public function applyMatrixTransform(MatrixTransform $transform, bool $applyTranslation = true): drawingTag {
|
||||
return new $this($transform->applyToShape($this->shape, $applyTranslation));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,10 +22,10 @@ class fillColorTag extends colorTag {
|
|||
}
|
||||
return new fillColorTag($color, $color);
|
||||
}
|
||||
return new fillColorTag(new Color(0, 0, 0, 255), new Color(0, 0, 0, 255));
|
||||
return new fillColorTag(null, null);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): 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))) . "&";
|
||||
return $this->color === null ? "\\1a&HFF&" : ("\\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(255 - $this->color->alpha))) . "&");
|
||||
}
|
||||
}
|
|
@ -12,10 +12,10 @@ class lineColorTag extends colorTag {
|
|||
if ($record instanceof LineStyleRecord) {
|
||||
return new lineColorTag($record->color, $record->color);
|
||||
}
|
||||
return new lineColorTag(new Color(0, 0, 0, 255), new Color(0, 0, 0, 255));
|
||||
return new lineColorTag(null, null);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): 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))) . "&";
|
||||
return $this->color === null ? "\\3a&HFF&" : ("\\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(255 - $this->color->alpha))) . "&");
|
||||
}
|
||||
}
|
|
@ -2,64 +2,50 @@
|
|||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use MathPHP\LinearAlgebra\MatrixFactory;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\Vector2;
|
||||
|
||||
class matrixTransformTag implements ASSPositioningTag {
|
||||
|
||||
private ?scaleTag $scale = null;
|
||||
private ?rotationTag $rotation = null;
|
||||
private ?shearingTag $shear = null;
|
||||
private ?positionTag $position = null;
|
||||
|
||||
public function __construct() {
|
||||
private scaleTag $scale;
|
||||
private rotationTag $rotation;
|
||||
private shearingTag $shear;
|
||||
|
||||
public function __construct(Vector2 $scale, $rotationX, $rotationY, $rotationZ, $shearX, $shearY) {
|
||||
$this->scale = new scaleTag($scale);
|
||||
$this->rotation = new rotationTag($rotationX, $rotationY, $rotationZ);
|
||||
$this->shear = new shearingTag(new Vector2($shearX, $shearY));
|
||||
}
|
||||
|
||||
public function transitionMatrixTransform(ASSLine $line, 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);
|
||||
$new = self::fromMatrixTransform($transform);
|
||||
return $this->equals($new) ? $this : null; //TODO: check this
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
$tag = "";
|
||||
if ($this->scale !== null) {
|
||||
$tag .= $this->scale->encode($line, $frameDurationMs);
|
||||
}
|
||||
if ($this->rotation !== null) {
|
||||
$tag .= $this->rotation->encode($line, $frameDurationMs);
|
||||
}
|
||||
if ($this->shear !== null) {
|
||||
$tag .= $this->shear->encode($line, $frameDurationMs);
|
||||
}
|
||||
if ($this->position !== null) {
|
||||
$tag .= $this->position->encode($line, $frameDurationMs);
|
||||
}
|
||||
return $tag;
|
||||
return $this->scale->encode($line, $frameDurationMs) . $this->rotation->encode($line, $frameDurationMs) . $this->shear->encode($line, $frameDurationMs);
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->position->equals($tag->position);
|
||||
return $tag instanceof $this and $this->scale->equals($tag->scale) and $this->rotation->equals($tag->rotation) and $this->shear->equals($tag->shear);
|
||||
}
|
||||
|
||||
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);
|
||||
// Several bugs in libass and xy-VSFilter / VSFilter do not allow having working skewX and skewY at the same time
|
||||
// See https://mechaweaponsvidya.wordpress.com/2014/08/
|
||||
// However values can be mapped back into just one of \fax or \fay, and scale/rotation
|
||||
|
||||
return $tag;
|
||||
$qr = $transform->decompose();
|
||||
|
||||
$frz = (180 / M_PI) * -$qr->rotation;
|
||||
$fax = $qr->skew->x;
|
||||
$fay = $qr->skew->y;
|
||||
$fsc = $qr->scale->multiply(100);
|
||||
|
||||
$frx = $fsc->y < 0 ? 180 : 0;
|
||||
$fry = $fsc->x < 0 ? 180 : 0;
|
||||
//$frz = 0;
|
||||
return new matrixTransformTag($fsc->abs(), $frx, $fry, $frz, $fax, $fay);
|
||||
}
|
||||
}
|
25
src/ass/originTag.php
Normal file
25
src/ass/originTag.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
use swf2ass\Constants;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\StyleRecord;
|
||||
use swf2ass\Vector2;
|
||||
|
||||
class originTag implements ASSTag {
|
||||
|
||||
private Vector2 $origin;
|
||||
|
||||
public function __construct(Vector2 $origin) {
|
||||
$this->origin = $origin;
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
return "\\org(" . round($this->origin->x, 5) ."," . round($this->origin->y, 5) .")";
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->origin->equals($tag->origin);
|
||||
}
|
||||
}
|
|
@ -11,50 +11,77 @@ class positionTag implements ASSPositioningTag {
|
|||
|
||||
private Vector2 $from;
|
||||
private ?Vector2 $to;
|
||||
private int $transitions;
|
||||
private int $start;
|
||||
private int $end;
|
||||
|
||||
public function __construct(Vector2 $from, ?Vector2 $to, int $transitions) {
|
||||
public function __construct(Vector2 $from, ?Vector2 $to, int $start, int $end) {
|
||||
$this->from = $from;
|
||||
$this->to = $to;
|
||||
$this->transitions = $transitions;
|
||||
$this->to = $to ?? $from;
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function transitionMatrixTransform(ASSLine $line, MatrixTransform $transform): ?positionTag {
|
||||
$translation = $transform->getTranslation()->divide(Constants::TWIP_SIZE);
|
||||
if($this->from->equals($translation) and $this->to === null){
|
||||
return clone $this;
|
||||
$translation = $transform->getTranslation()->toPixel();
|
||||
|
||||
$frame = $line->end - $line->start;
|
||||
|
||||
$isInitialState = ($this->start === $frame and $this->end === $frame);
|
||||
$isMovingState = ($this->start < $frame and $this->end === $frame);
|
||||
$isMovedState = ($this->start < $frame and $this->end < $frame);
|
||||
|
||||
if($this->to->equals($translation)){
|
||||
if($isInitialState){
|
||||
return new positionTag($this->from, $this->to,$this->start + 1, $this->end + 1);
|
||||
}elseif ($isMovingState or $isMovedState){
|
||||
return new positionTag($this->from, $this->to, $this->start, $this->end);
|
||||
}else{
|
||||
var_dump($this);
|
||||
throw new \LogicException();
|
||||
}
|
||||
}
|
||||
|
||||
$transitions = $line->end - $line->start;
|
||||
if($transitions !== ($this->transitions + 1)){ //Not single step, TODO maybe move after $this->to === null
|
||||
if($isInitialState){ //Always allow initial move
|
||||
return new positionTag($this->from, $translation, $this->start, $this->end + 1);
|
||||
}else if($isMovingState){
|
||||
$duration = $this->end - $this->start + 1;
|
||||
|
||||
$direction = $this->to->sub($this->from)->normalize();
|
||||
if(abs($direction->dot($translation->normalize()) - 1) < Constants::EPSILON){ //Same direction
|
||||
$length = $this->to->sub($this->from)->divide($duration)->squaredLength();
|
||||
$length2 = $translation->sub($this->to)->squaredLength();
|
||||
|
||||
//TODO: add extra leeway?
|
||||
if(abs($length - $length2) < Constants::EPSILON){ //Same length
|
||||
return new positionTag($this->from, $translation, $this->start, $this->end + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}else if($isMovedState){
|
||||
return null;
|
||||
}else{
|
||||
var_dump($this);
|
||||
throw new \LogicException();
|
||||
}
|
||||
|
||||
if($this->to === null){
|
||||
return new positionTag($this->from, $translation, $transitions);
|
||||
}
|
||||
|
||||
$shift = $this->to->sub($this->from)->divide($this->transitions);
|
||||
$newShift = $translation->sub($this->to);
|
||||
|
||||
if($newShift->equals($shift)){ //Same distance and speed
|
||||
return new positionTag($this->from, $translation, $transitions);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
//TODO: Maybe time 0,round($line->end * $frameDurationMs)
|
||||
return ($this->to !== null and ($line->end - $line->start) > 0) ? "\\move(" . round($this->from->x, 2) ."," . round($this->from->y, 2) ."," . round($this->to->x, 2) ."," . round($this->to->y, 2) .")" : "\\pos({$this->from->x},{$this->from->y})";
|
||||
$frame = $line->end - $line->start;
|
||||
$hasMoved = $this->start < $frame;
|
||||
|
||||
if($hasMoved){
|
||||
return "\\move(" . $this->from->x ."," . $this->from->y ."," . $this->to->x ."," . $this->to->y .",".ceil(($this->start - 1) * $frameDurationMs).",".floor($this->end * $frameDurationMs).")";
|
||||
}
|
||||
return "\\pos(".$this->from->x ."," . $this->from->y.")";
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->transitions === $tag->transitions and $this->from->equals($tag->from) and ($this->to === $tag->to or $this->to->equals($tag->to));
|
||||
return $tag instanceof $this and $this->start === $tag->start and $this->end === $tag->end and $this->from->equals($tag->from) and $this->to->equals($tag->to);
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?positionTag {
|
||||
$translation = $transform->getTranslation()->divide(Constants::TWIP_SIZE);
|
||||
return new positionTag($translation, null, 0);
|
||||
$translation = $transform->getTranslation()->toPixel();
|
||||
return new positionTag($translation, $translation, 1, 1);
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ class rotationTag implements ASSPositioningTag {
|
|||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
return "\\frx" . round($this->x, 2) ."\\fry" . round($this->y, 2) ."\\frz" . round($this->z, 2);
|
||||
return "\\frx" . round($this->x, 5) ."\\fry" . round($this->y, 5) ."\\frz" . round($this->z, 5);
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
|
@ -30,8 +30,8 @@ class rotationTag implements ASSPositioningTag {
|
|||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?rotationTag {
|
||||
$rotation = $transform->getRotation()->multiply(180 / M_PI);
|
||||
$diff = $rotation->y - $rotation->x;
|
||||
return new rotationTag( $diff, 0, -$rotation->x); //maybe x -> y instead of y -> x, maybe do not use z
|
||||
$qr = $transform->decomposeQR();
|
||||
|
||||
return new rotationTag( $qr->scale->y < 0 ? 180 : 0, $qr->scale->x < 0 ? 180 : 0, $qr->rotation * (180 / M_PI));
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ class scaleTag implements ASSPositioningTag {
|
|||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
return "\\fscx" . round($this->scale->x, 4) ."\\fscy" . round($this->scale->y, 4);
|
||||
return "\\fscx" . round($this->scale->x, 5) ."\\fscy" . round($this->scale->y, 5);
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
|
@ -28,6 +28,6 @@ class scaleTag implements ASSPositioningTag {
|
|||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?scaleTag {
|
||||
return new scaleTag($transform->getScale());
|
||||
return new scaleTag($transform->decomposeQR()->scale->abs()->multiply(100));
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ class shearingTag implements ASSPositioningTag {
|
|||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
return "\\fax" . round($this->shear->x, 5) ."\\fay" . round($this->shear->x, 5) ."";
|
||||
return "\\fax" . round($this->shear->x, 5) ."\\fay" . round($this->shear->y, 5) ."";
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
|
@ -28,6 +28,6 @@ class shearingTag implements ASSPositioningTag {
|
|||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?shearingTag {
|
||||
return new shearingTag(new Vector2($transform->getRotationY() - $transform->getRotationX(), 0));
|
||||
return new shearingTag(new Vector2($transform->get_b(), $transform->get_c()));
|
||||
}
|
||||
}
|
|
@ -17,11 +17,12 @@ $swf->loadXML($contents);
|
|||
$fp = fopen($argv[2], "w+");
|
||||
|
||||
|
||||
|
||||
$headerTag = $swf->getElementsByTagName("Header")->item(0);
|
||||
if ($headerTag instanceof DOMElement) {
|
||||
$processor = new \swf2ass\SWFProcessor($headerTag);
|
||||
$assRenderer = new \swf2ass\ass\ASSRenderer($processor->getFrameRate(), $processor->getViewPort());
|
||||
$assRenderer = new \swf2ass\ass\ASSRenderer($processor->getFrameRate(), $processor->getViewPort(), [
|
||||
"bakeTransforms" => true //TODO: fix ASS matrix transform rendering and remove this
|
||||
]);
|
||||
|
||||
$keyFrameInterval = 10 * $processor->getFrameRate(); //kf every 10 seconds
|
||||
$frameOffset = 0;
|
||||
|
|
Loading…
Reference in a new issue