Added baking mode, fallback for matrix transforms in ASS

This commit is contained in:
DataHoarder 2021-12-31 05:01:39 +01:00
parent 087d992842
commit 9c38498737
36 changed files with 437 additions and 473 deletions

View file

@ -12,6 +12,7 @@
"ext-dom": "*",
"ext-imagick": "*",
"ext-zlib": "*",
"ext-gmp": "*"
"ext-gmp": "*",
"markrogoyski/math-php": "2.*"
}
}

80
composer.lock generated
View file

@ -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",

View file

@ -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

View file

@ -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++;
}
}

View file

@ -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})";
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -0,0 +1,13 @@
<?php
namespace swf2ass;
class DecomposedMatrixTransform {
public Vector2 $translation;
public $rotation;
public Vector2 $scale;
public Vector2 $skew;
}

View file

@ -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

View file

@ -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));
}
}

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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));
}
}

View file

@ -4,6 +4,4 @@ namespace swf2ass;
interface MultiFrameObjectDefinition extends ObjectDefinition {
public function nextFrame(): ViewFrame;
public function hasFrame(): bool;
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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 {

View file

@ -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);
}

View file

@ -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";
}
}

View file

@ -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));
}
/**

View file

@ -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))) . "&");
}
}

View file

@ -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))) . "&");
}
}

View file

@ -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
View 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);
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -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()));
}
}

View file

@ -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;