Proper path drawing

This commit is contained in:
DataHoarder 2021-12-26 15:59:57 +01:00
parent f968dcf144
commit 61d79768aa
34 changed files with 1011 additions and 470 deletions

28
src/ActivePath.php Normal file
View file

@ -0,0 +1,28 @@
<?php
namespace swf2ass;
class ActivePath {
/** @var PathSegment */
public PathSegment $segment;
public int $style;
/**
* @param $styleId
* @param Coordinate $start
*/
public function __construct($styleId, Coordinate $start) {
$this->style = $styleId;
$this->segment = new PathSegment($start);
}
public function add_point(VisitedPoint $point){
$this->segment->add_point($point);
}
public function flip() {
$this->segment->flip();
}
}

299
src/BitmapConverter.php Normal file
View file

@ -0,0 +1,299 @@
<?php
namespace swf2ass;
class BitmapConverter {
/** @var Color[][] */
private array $pixelMatrix;
private Coordinate $resolution;
public function __construct(array $pixelMatrix) {
$this->pixelMatrix = $pixelMatrix;
$this->resolution = new Coordinate(count($pixelMatrix[0]), count($pixelMatrix));
}
private static function calculateAreaOfPath($path) {
$totalArea = 0;
/** @var Coordinate[][] $subPolygons */
$subPolygons = [];
/** @var Coordinate[] $currentPolygon */
$currentPolygon = [];
foreach ($path as $n) {
if ($n instanceof MoveRecord) {
if (count($currentPolygon) > 0) {
$subPolygons[] = $currentPolygon;
$currentPolygon = [];
}
} else if ($n instanceof LineRecord) {
if (count($currentPolygon) === 0) {
$currentPolygon[] = $n->start;
}
$currentPolygon[] = $n->coord;
}
}
if (count($currentPolygon) > 0) {
$subPolygons[] = $currentPolygon;
$currentPolygon = [];
}
foreach ($subPolygons as $polygon) {
$NumPoints = count($polygon);
if ($polygon[$NumPoints - 1]->equals($polygon[0])) {
$NumPoints--;
} else {
//Add the first point at the end of the array.
$polygon[$NumPoints] = $polygon[0];
}
if ($NumPoints < 3) {
break;
} else {
$area = 0;
$lastPoint = $polygon[$NumPoints - 1];
foreach ($polygon as $point) {
$area += ($lastPoint->x * $point->y - $lastPoint->y * $point->x);
$lastPoint = $point;
}
$totalArea += ($area / 2.0);
}
}
return $totalArea;
}
private static function normalizePath($path) {
$newPath = [];
$lastCursor = new Coordinate(0, 0);
foreach ($path as $e) {
/** @var LineRecord|MoveRecord $e */
if (!$e->start->equals($lastCursor)) {
$newPath[] = new MoveRecord($e->start, $lastCursor);
}
$newPath[] = $e;
$lastCursor = $e->coord;
}
return $newPath;
}
/**
* @param LineRecord[] $path
*/
private static function cleanupPath(array $path) {
$path = array_values($path);
/** @var LineRecord[] $newPath */
$newPath = [];
$currentPath = null;
$cornerMap = [];
foreach ($path as $i => $p) {
if (!isset($cornerMap[$p->start->x . "_" . $p->start->y])) {
$cornerMap[$p->start->x . "_" . $p->start->y] = [];
}
$cornerMap[$p->start->x . "_" . $p->start->y][$i] = $p;
if (!isset($cornerMap[$p->coord->x . "_" . $p->coord->y])) {
$cornerMap[$p->coord->x . "_" . $p->coord->y] = [];
}
$cornerMap[$p->coord->x . "_" . $p->coord->y][$i] = $p;
}
$index = 0;
while (count($path) > 0) {
$currentPath = $path[$index++] ?? null;
if ($currentPath !== null) {
$dupes = 0;
foreach ($cornerMap[$currentPath->start->x . "_" . $currentPath->start->y] as $i => $p) {
if ((($currentPath->start->equals($p->start) and $currentPath->coord->equals($p->coord)) or ($currentPath->start->equals($p->coord) and $currentPath->coord->equals($p->start)))) {
unset($path[$i]);
unset($cornerMap[$k1 = $p->start->x . "_" . $p->start->y][$i]);
unset($cornerMap[$k2 = $p->coord->x . "_" . $p->coord->y][$i]);
if ($currentPath !== $p) {
++$dupes;
}
if (count($cornerMap[$k1]) === 0) {
unset($cornerMap[$k1]);
}
if (count($cornerMap[$k2]) === 0) {
unset($cornerMap[$k2]);
}
}
}
if ($dupes === 0) {
$newPath[] = $currentPath;
}
}
}
$cornerMap = [];
foreach ($newPath as $i => $p) {
if (!isset($cornerMap[$p->start->x . "_" . $p->start->y])) {
$cornerMap[$p->start->x . "_" . $p->start->y] = [];
}
$cornerMap[$p->start->x . "_" . $p->start->y][$i] = $p;
if (!isset($cornerMap[$p->coord->x . "_" . $p->coord->y])) {
$cornerMap[$p->coord->x . "_" . $p->coord->y] = [];
}
$cornerMap[$p->coord->x . "_" . $p->coord->y][$i] = $p;
}
$sortedPath = [];
$startPath = null;
$currentVector = null;
$index = 0;
while (count($newPath) > 0) {
$currentPath = $newPath[$index++] ?? null;
if ($currentPath !== null) {
$startPath = $currentPath;
$currentVector = $currentPath->coord->sub($currentPath->start);
$sortedPath[] = $startPath;
unset($newPath[$index - 1]);
unset($cornerMap[$startPath->start->x . "_" . $startPath->start->y][$index - 1]);
unset($cornerMap[$startPath->coord->x . "_" . $startPath->coord->y][$index - 1]);
while (($nextPath = self::findNextCorner($currentPath->coord, $newPath, $cornerMap)) !== null) {
$nextVector = $nextPath->coord->sub($nextPath->start);
if ($nextVector->equals($currentVector)) { //Enlongate
$currentPath->coord = $nextPath->coord;
continue;
}
$sortedPath[] = $nextPath;
if ($nextPath->coord->equals($nextPath->start)) { //Reached the end!
$startPath = null;
break;
}
$currentPath = $nextPath;
$currentVector = $nextVector;
}
}
}
return $sortedPath;
}
/**
* @param Coordinate $corner
* @param LineRecord[] $path
*
* @return ?LineRecord
*/
private static function findNextCorner(Coordinate $corner, array &$path, array &$cornerMap): ?LineRecord {
foreach ($cornerMap[$corner->x . "_" . $corner->y] as $i => $p) {
unset($path[$i]);
unset($cornerMap[$p->start->x . "_" . $p->start->y][$i]);
unset($cornerMap[$p->coord->x . "_" . $p->coord->y][$i]);
if ($p->start->equals($corner)) {
return $p;
} else if ($p->coord->equals($corner)) {
return $p->reverse();
}
}
return null;
}
private static function getBoundingBox($path) {
$bb = new Rectangle(new \swf2ass\Coordinate(PHP_INT_MAX, PHP_INT_MAX), new Coordinate(PHP_INT_MIN, PHP_INT_MIN));
/*if(count($path) > 0){
$first = reset($path);
if($first instanceof MoveRecord or $first instanceof LineRecord){
$bb->topLeft = $first->coord;
$bb->bottomRight = $first->coord;
}
}*/
foreach ($path as $i => $e) {
/** @var LineRecord|MoveRecord $e */
if ($i === 0 and $e instanceof MoveRecord) {
continue;
}
if (!$bb->inBounds($e->coord)) {
$bb->topLeft->x = min($e->coord->x, $bb->topLeft->x);
$bb->topLeft->y = min($e->coord->y, $bb->topLeft->y);
$bb->bottomRight->x = max($e->coord->x, $bb->bottomRight->x);
$bb->bottomRight->y = max($e->coord->y, $bb->bottomRight->y);
}
if (!$bb->inBounds($e->start)) {
$bb->topLeft->x = min($e->start->x, $bb->topLeft->x);
$bb->topLeft->y = min($e->start->y, $bb->topLeft->y);
$bb->bottomRight->x = max($e->start->x, $bb->bottomRight->x);
$bb->bottomRight->y = max($e->start->y, $bb->bottomRight->y);
}
}
return $bb;
}
public function render(bool $doFullFrameOptimizations): DrawPathList {
$colors = [];
$paths = [];
foreach ($this->pixelMatrix as $y => $row) {
foreach ($row as $x => $color) {
$k = $color->toString(true);
if (!isset($paths[$k])) {
$colors[$k] = $color;
$paths[$k] = [];
}
foreach ((new Square(new Coordinate($x, $y), 1))->draw() as $p) {
$paths[$k][] = $p;
}
}
}
$optimizedPaths = [];
foreach ($paths as $k => $path) {
$path = self::cleanupPath($path);
$path = self::normalizePath($path);
$optimizedPaths[$k] = $path;
}
if ($doFullFrameOptimizations) {
uasort($optimizedPaths, function ($a, $b) {
return self::calculateAreaOfPath($a) - self::calculateAreaOfPath($b);
});
//Largest path
$k0 = array_key_first($optimizedPaths);
$path = $optimizedPaths[$k0];
$optimizedPaths[$k0] = [];
foreach ((new Rectangle(new Coordinate(0, 0), $this->resolution))->draw() as $p) {
$optimizedPaths[$k0][] = $p;
}
}
/** @var DrawPath[] $commands */
$commands = [];
foreach ($optimizedPaths as $k => $path){
$edges = [];
$bb = self::getBoundingBox($path);
$pos = $bb->topLeft->multiply(TWIP_SIZE);
$edges[] = new MoveRecord($pos, new Coordinate(0, 0));
foreach ($path as $edge){
if($edge instanceof MoveRecord){
$edges[] = new MoveRecord($edge->coord->multiply(TWIP_SIZE), $edge->start->multiply(TWIP_SIZE));
}else if($edge instanceof LineRecord){
$edges[] = new LineRecord($edge->coord->multiply(TWIP_SIZE), $edge->start->multiply(TWIP_SIZE));
}else{
var_dump($edge);
throw new \Exception();
}
}
$commands[] = DrawPath::fill(new FillStyleRecord($colors[$k]), new Shape($edges));
}
return new DrawPathList($commands);
}
}

View file

@ -8,11 +8,22 @@ class BitmapDefinition implements ObjectDefinition {
public Coordinate $size;
/** @var Color[][] */
public $pixels;
private DrawPathList $drawPathList;
public function __construct($id, Coordinate $size, array $pixels) {
$this->id = $id;
$this->size = $size;
$this->pixels = $pixels;
$converter = new BitmapConverter($this->pixels);
$this->drawPathList = $converter->render(true);
}
public function getId(){
return $this->id;
}
public function getShapeList() : DrawPathList{
return $this->drawPathList;
}
static function fromXML(\DOMElement $element): BitmapDefinition {
@ -63,4 +74,5 @@ class BitmapDefinition implements ObjectDefinition {
return (string)$im;
}
}

View file

@ -15,6 +15,10 @@ class Color {
$this->alpha = $alpha;
}
public function equals(?Color $other): bool {
return $other !== null and $other->r === $this->r and $other->g === $this->g and $other->b === $this->b and $other->alpha == $this->alpha;
}
public function distance(Color $color): float {
return sqrt(pow($color->r - $this->r, 2) + pow($color->g - $this->g, 2) + pow($color->b - $this->b, 2));
}
@ -22,6 +26,7 @@ class Color {
public function toString($nullAlpha = false) {
$c = "\\1c&H" . strtoupper(Utils::padHex(dechex($this->b ?? 0))) . strtoupper(Utils::padHex(dechex($this->g ?? 0))) . strtoupper(Utils::padHex(dechex($this->r ?? 0))) . "&";
if (($this->alpha === null or $this->alpha === 0) and $nullAlpha) {
} else {
$c .= "\\1a&H" . strtoupper(Utils::padHex(dechex($this->alpha ?? 0))) . "&";
}

View file

@ -17,7 +17,7 @@ class ColorTransform {
$styles->color1 = $styles->original_color1->applyTransform($this);
}
if ($styles->original_color3 instanceof Color) {
//$styles->color3 = $styles->original_color3->applyTransform($this);
$styles->color3 = $styles->original_color3->applyTransform($this);
}
return $styles;

View file

@ -32,6 +32,10 @@ class Coordinate {
return new Coordinate($this->x - $b->x, $this->y - $b->y);
}
public function multiply($size): Coordinate {
return new Coordinate($this->x * $size, $this->y * $size);
}
public function divide($size): Coordinate {
return new Coordinate($this->x / $size, $this->y / $size);
}

View file

@ -24,6 +24,11 @@ class CubicCurveRecord implements Record {
}
public static function fromQuadraticRecord(QuadraticCurveRecord $q): CubicCurveRecord {
return new CubicCurveRecord(new Coordinate(($q->start->x + 2 * $q->control->x) / 3, ($q->start->y + 2 * $q->control->y) / 3), new Coordinate(($q->anchor->x + 2 * $q->control->x) / 3, ($q->anchor->y + 2 * $q->control->y) / 3), $q->anchor, $q->start);
return new CubicCurveRecord(
$q->start->add($q->control->multiply(2))->divide(3),
$q->anchor->add($q->control->multiply(2))->divide(3),
$q->anchor,
$q->start
);
}
}

View file

@ -13,7 +13,6 @@ class DisplayEntry {
public $depth;
public $clipDepth = 0;
public function getLines(CurrentState $currentState) {
if ($this->clipDepth > 0) {
return [];
@ -26,114 +25,24 @@ class DisplayEntry {
$endTime = ($this->startFrame + 1) * (1 / $currentState->frameRate);
if ($this->object instanceof ShapeDefinition) {
if ($this->object instanceof ObjectDefinition) {
$shapeDef = $this->object;
//TODO: animation
$currentShapeList = $shapeDef->styles;
foreach ($shapeDef->shapeList->shapes as $shape) {
foreach ($shapeDef->getShapeList()->commands as $path){
$currentFrameStyles = [new StyleContainer()];
$edges = $shape->edges;
if ($shape->styleRecord->styleList instanceof StyleList) {
$currentShapeList = $shape->styleRecord->styleList;
}
$fillStyleInside = null;
if ($shape->styleRecord->fillStyleInside > 0) {
$fillStyleInside = $currentShapeList->getFillStyle($shape->styleRecord->fillStyleInside - 1);
}
$lineStyle = null;
if ($shape->styleRecord->lineStyle > 0) {
$lineStyle = $currentShapeList->getLineStyle($shape->styleRecord->lineStyle - 1);
$hasUnopenedPath = false;
$newEdges = [];
$currentSegment = [];
foreach ($edges as $edge) {
if (($edge instanceof MoveRecord) and count($currentSegment) > 0) {
$reversedPath = array_reverse($currentSegment);
$end = $reversedPath[0]->reverse()->start;
$start = $currentSegment[0]->start;
$diff = abs($end->x - $start->x) + abs($end->y - $start->y);
//Close paths that are left open by drawing backwards
if ($diff >= 1) {
foreach ($reversedPath as $node) {
$n = $node->reverse();
$newEdges[] = $n;
}
$hasUnopenedPath = true;
}
$currentSegment = [];
}
$newEdges[] = $edge;
$currentSegment[] = $edge;
}
if (count($currentSegment) > 0) {
$reversedPath = array_reverse($currentSegment);
$end = $reversedPath[0]->reverse()->start;
$start = $currentSegment[0]->start;
$diff = abs($end->x - $start->x) + abs($end->y - $start->y);
//Close paths that are left open by drawing backwards
if ($diff >= 1) {
foreach ($reversedPath as $node) {
$n = $node->reverse();
$newEdges[] = $n;
}
$hasUnopenedPath = true;
}
$currentSegment = [];
}
if ($hasUnopenedPath) {
$edges = $newEdges;
if (end($edges) instanceof MoveRecord) {
array_pop($edges);
}
//Need to half the border for double path
if ($lineStyle instanceof LineStyle) {
$lineStyle = clone $lineStyle;
$lineStyle->width /= 2;
}
}
}
if ($fillStyleInside instanceof LinearGradient) {
$fillStyleInside = $fillStyleInside->colors[1]->color;
}
if ($lineStyle instanceof LineStyle and $lineStyle->item instanceof LinearGradient) {
$oStyle = $lineStyle;
$lineStyle = new LineStyle();
$lineStyle->item = $oStyle->item->colors[1]->color;
$lineStyle->width = $oStyle->width;
}
if ($fillStyleInside !== null) {
$currentFrameStyles[0]->original_color1 = $currentFrameStyles[0]->color1 = $fillStyleInside;
}
if ($lineStyle !== null) {
$currentFrameStyles[0]->original_color3 = $currentFrameStyles[0]->color3 = $lineStyle->item;
$currentFrameStyles[0]->bord = round($lineStyle->width / TWIP_SIZE, 1);
if($path->style instanceof LineStyleRecord){ //Stroke
$currentFrameStyles[0]->original_color3 = $currentFrameStyles[0]->color3 = $path->style->color;
$currentFrameStyles[0]->bord = round($path->style->width / TWIP_SIZE, 1);
}else if($path->style instanceof FillStyleRecord){ //Fill
$currentFrameStyles[0]->original_color1 = $currentFrameStyles[0]->color1 = $path->style->fill instanceof Gradient ? $path->style->fill->getItems()[0]->color : $path->style->fill; //TODO: gradient?
}
$currentFrameStyles[0]->shad = 0;
$assLine = "{\\an7\\shad0\\p1}";
foreach ($edges as $edge) {
foreach ($path->commands->edges as $edge) {
if ($edge instanceof MoveRecord) {
$coords = $edge->coord->toPixel();
$assLine .= "m " . round($coords->x, 2) . " " . round($coords->y, 2) . " ";
@ -161,6 +70,10 @@ class DisplayEntry {
$lastLineStart = $startTime;
$lastFrameStart = $this->startFrame;
$debugInfo = "oid:".$shapeDef->getId();
$savedFrameStyles = $currentFrameStyles;
$af = 0;
$frames = 0;
foreach ($this->frames as $frame => $frameInformation) {
@ -180,6 +93,16 @@ class DisplayEntry {
$frameInformation->colorTransform->applyToStyleContainer($frameStyle);
}
$lastSavedStyle = end($savedFrameStyles);
$savedStyle = clone $lastSavedStyle;
$savedStyle->resetTransformedStyles();
if ($frameInformation->transform instanceof MatrixTransform) {
$frameInformation->transform->applyToStyleContainer($savedStyle);
}
if ($frameInformation->colorTransform instanceof ColorTransform) {
$frameInformation->colorTransform->applyToStyleContainer($savedStyle);
}
if ($frames === 0) {
$currentFrameStyles[array_key_last($currentFrameStyles)] = $frameStyle;
} else if (!$lastStyle->isEqualish($frameStyle)) {
@ -189,7 +112,7 @@ class DisplayEntry {
$transition = $lastStyle->doTransitionTo($frameStyle, $frame, $frame, $frame, $frame);
}
if ($transition === false) {
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($currentTime) . ",f,objectid:{$shapeDef->id},0,0,0,af:" . ($af++) . ",";
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($currentTime) . ",f,$debugInfo,0,0,0,af:" . ($af++) . ",";
foreach ($currentFrameStyles as $i => $style) {
$assHeader .= "{" . $style->toString($lastFrameStart, (1 / $currentState->frameRate), $currentFrameStyles[$i - 1] ?? null, $i === 0) . "}";
}
@ -197,15 +120,16 @@ class DisplayEntry {
$lastLineStart = $currentTime;
$lastFrameStart = $frame;
$currentFrameStyles = [$frameStyle];
$currentFrameStyles = [$savedStyle];
} else if ($transition instanceof StyleContainer) {
$currentFrameStyles[] = $transition;
$savedFrameStyles[] = $savedStyle;
}
}
++$frames;
}
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($endTime) . ",f,objectid:{$shapeDef->id},0,0,0,af:" . ($af++) . ",";
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($endTime) . ",f,$debugInfo,0,0,0,af:" . ($af++) . ",";
foreach ($currentFrameStyles as $i => $style) {
$assHeader .= "{" . $style->toString($lastFrameStart, (1 / $currentState->frameRate), $currentFrameStyles[$i - 1] ?? null, $i === 0) . "}";
}

27
src/DrawPath.php Normal file
View file

@ -0,0 +1,27 @@
<?php
namespace swf2ass;
class DrawPath {
public StyleRecord $style;
public bool $is_closed;
public Shape $commands;
public static function fill(FillStyleRecord $style, Shape $shape): DrawPath {
$p = new DrawPath();
$p->style = $style;
$p->commands = $shape;
$p->is_closed = true;
return $p;
}
public static function stroke(LineStyleRecord $style, Shape $shape, bool $is_closed): DrawPath {
$p = new DrawPath();
$p->style = $style;
$p->commands = $shape;
$p->is_closed = $is_closed;
return $p;
}
}

24
src/DrawPathList.php Normal file
View file

@ -0,0 +1,24 @@
<?php
namespace swf2ass;
class DrawPathList {
/** @var DrawPath[] */
public array $commands;
public function __construct($commands = []) {
$this->commands = $commands;
}
public function merge(DrawPathList $b): DrawPathList {
return new DrawPathList(array_merge($this->commands, $b->commands));
}
public static function fromXML(\DOMElement $element, StyleList $currentStyles): DrawPathList {
$converter = new ShapeConverter($element, $currentStyles);
return $converter->commands;
}
}

13
src/FillStyleRecord.php Normal file
View file

@ -0,0 +1,13 @@
<?php
namespace swf2ass;
class FillStyleRecord implements StyleRecord {
/** @var Gradient|Color */
public $fill;
public function __construct($fill){
$this->fill = $fill;
}
}

12
src/Gradient.php Normal file
View file

@ -0,0 +1,12 @@
<?php
namespace swf2ass;
interface Gradient {
/**
* @return GradientItem[]
*/
public function getItems() : array;
public function getMatrixTransform() : ?MatrixTransform;
}

View file

@ -4,7 +4,7 @@ namespace swf2ass;
class GradientItem {
public $position;
public $color;
public Color $color;
public function __construct($position, Color $color) {
$this->position = $position;

View file

@ -19,10 +19,7 @@ class LineRecord implements Record {
return new LineRecord($this->start, $this->coord);
}
public static function fromXML(\DOMElement $element, $cursorX = 0, $cursorY = 0): LineRecord {
$x = (int)$element->getAttribute("x");
$y = (int)$element->getAttribute("y");
return new LineRecord(new Coordinate($cursorX + $x, $cursorY + $y), new Coordinate($cursorX, $cursorY));
public static function fromXML(\DOMElement $element, Coordinate $cursor): LineRecord {
return new LineRecord($cursor->add(new Coordinate((int)$element->getAttribute("x"), (int)$element->getAttribute("y"))), $cursor);
}
}

View file

@ -1,9 +0,0 @@
<?php
namespace swf2ass;
class LineStyle {
/** @var Color|LinearGradient */
public $item;
public $width;
}

20
src/LineStyleRecord.php Normal file
View file

@ -0,0 +1,20 @@
<?php
namespace swf2ass;
class LineStyleRecord implements StyleRecord {
public int $width;
public Color $color;
public ?FillStyleRecord $fill_style = null;
//TODO: join/cap/etc style
public bool $allow_close = true;
public function __construct(int $width, Color $color){
$this->width = $width;
$this->color = $color;
}
}

View file

@ -2,7 +2,7 @@
namespace swf2ass;
class LinearGradient {
class LinearGradient implements Gradient {
/** @var GradientItem[] */
public $colors;
/** @var MatrixTransform|null */
@ -12,6 +12,12 @@ class LinearGradient {
$this->colors = $colors;
$this->transform = $transform;
}
public function getItems() : array {
return $this->colors;
}
public function getMatrixTransform() : ?MatrixTransform{
return $this->transform;
}
public static function fromXML(\DOMElement $element): LinearGradient {
$colors = [];

View file

@ -19,10 +19,7 @@ class MoveRecord implements Record {
return new MoveRecord($this->start, $this->coord);
}
public static function fromXML(\DOMElement $element, $cursorX = 0, $cursorY = 0): MoveRecord {
$x = (int)$element->getAttribute("x");
$y = (int)$element->getAttribute("y");
return new MoveRecord(new Coordinate($x, $y), new Coordinate($cursorX, $cursorY));
public static function fromXML(\DOMElement $element, Coordinate $cursor): MoveRecord {
return new MoveRecord(new Coordinate((int)$element->getAttribute("x"), (int)$element->getAttribute("y")), $cursor);
}
}

View file

@ -4,4 +4,6 @@ namespace swf2ass;
interface ObjectDefinition {
public function getId();
public function getShapeList() : DrawPathList;
}

100
src/PathSegment.php Normal file
View file

@ -0,0 +1,100 @@
<?php
namespace swf2ass;
use mysql_xdevapi\Exception;
class PathSegment {
/** @var VisitedPoint[] */
public array $points;
public function __construct(Coordinate $start) {
$this->points = [new VisitedPoint($start, false)];
}
public function flip() {
$this->points = array_reverse($this->points, false);
}
public function add_point(VisitedPoint $point){
$this->points[] = $point;
}
public function start(): Coordinate {
return reset($this->points)->pos;
}
public function end(): Coordinate {
return end($this->points)->pos;
}
public function is_empty(): bool {
return count($this->points) <= 1;
}
public function is_closed(): bool {
return $this->start()->equals($this->end());
}
public function swap(PathSegment $other){
[$this->points, $other->points] = [$other->points, $this->points];
}
public function merge(PathSegment $other){
$this->points = array_merge($this->points, array_slice($other->points, 1));
}
public function try_merge(PathSegment $other, bool $directed) : bool{
if($other->end()->equals($this->start())){
$this->swap($other);
$this->merge($other);
return true;
}else if($this->end()->equals($other->start())){
$this->merge($other);
return true;
}else if(!$directed and $this->end()->equals($other->end())){
$other->flip();
$this->merge($other);
return true;
}else if(!$directed and $this->start()->equals($other->start())){
$other->flip();
$this->swap($other);
$this->merge($other);
return true;
}
return false;
}
public function getShape() : Shape {
if($this->is_empty()){
throw new Exception();
}
$shape = new Shape();
$first = reset($this->points);
$pos = new Coordinate(0, 0);
$shape->edges[] = new MoveRecord($first->pos, $pos);
$pos = $first->pos;
while (($point = next($this->points)) !== false){
if(!$point->is_bezier_control){
$shape->edges[] = new LineRecord($point->pos, $pos);
$pos = $point->pos;
}else{
$end = next($this->points);
if($end === false){
throw new \Exception("Bezier without endpoint");
}
$shape->edges[] = new QuadraticCurveRecord($point->pos, $end->pos, $pos);
$pos = $end->pos;
}
}
return $shape;
}
}

47
src/PendingPath.php Normal file
View file

@ -0,0 +1,47 @@
<?php
namespace swf2ass;
class PendingPath {
/** @var PathSegment[] */
public array $segments = [];
public function __construct() {
}
/**
* @param PathSegment $new_segment
* @param bool $directed
*/
public function merge_path(PathSegment $new_segment, bool $directed){
if(!$new_segment->is_empty()){
$merged = null;
foreach ($this->segments as $i => $segment){
if($segment->try_merge($new_segment, $directed)){
unset($this->segments[$i]);
$merged = $segment;
break;
}
}
if($merged === null){
$this->segments[] = $new_segment;
}else{
$this->merge_path($merged, $directed);
}
}
}
public function getShape() : Shape {
$shape = new Shape();
foreach ($this->segments as $segment){
$shape->edges = array_merge($shape->edges, $segment->getShape()->edges);
}
return $shape;
}
}

22
src/PendingPathMap.php Normal file
View file

@ -0,0 +1,22 @@
<?php
namespace swf2ass;
class PendingPathMap {
/** @var PendingPath[] */
public array $map = [];
public function __construct() {
}
public function merge_path(ActivePath $path, bool $directed){
if(!isset($this->map[$path->style])){
$this->map[$path->style] = new PendingPath();
}
$this->map[$path->style]->merge_path($path->segment, $directed);
}
}

View file

@ -21,12 +21,10 @@ class QuadraticCurveRecord implements Record {
return new QuadraticCurveRecord($this->control, $this->start, $this->anchor);
}
public static function fromXML(\DOMElement $element, $cursorX = 0, $cursorY = 0): QuadraticCurveRecord {
$controlX = (int)$element->getAttribute("x1");
$controlY = (int)$element->getAttribute("y1");
$anchorX = (int)$element->getAttribute("x2");
$anchorY = (int)$element->getAttribute("y2");
public static function fromXML(\DOMElement $element, Coordinate $cursor): QuadraticCurveRecord {
$control = $cursor->add(new Coordinate((int)$element->getAttribute("x1"), (int)$element->getAttribute("y1")));
$anchor = $control->add(new Coordinate((int)$element->getAttribute("x2"), (int)$element->getAttribute("y2")));
return new QuadraticCurveRecord(new Coordinate($cursorX + $controlX, $cursorY + $controlY), new Coordinate($cursorX + $controlX + $anchorX, $cursorY + $controlY + $anchorY), new Coordinate($cursorX, $cursorY));
return new QuadraticCurveRecord($control, $anchor, $cursor);
}
}

30
src/RadialGradient.php Normal file
View file

@ -0,0 +1,30 @@
<?php
namespace swf2ass;
class RadialGradient implements Gradient {
/** @var GradientItem[] */
public $colors;
/** @var MatrixTransform|null */
public ?MatrixTransform $transform;
public function __construct($colors = [], MatrixTransform $transform = null) {
$this->colors = $colors;
$this->transform = $transform;
}
public function getItems() : array {
return $this->colors;
}
public function getMatrixTransform() : ?MatrixTransform{
return $this->transform;
}
public static function fromXML(\DOMElement $element): RadialGradient {
$colors = [];
foreach ($element->getElementsByTagName("GradientItem") as $item) {
$colors[] = GradientItem::fromXML($item);
}
$matrix = $element->getElementsByTagName("matrix");
return new RadialGradient($colors, $matrix->count() > 0 ? MatrixTransform::fromXML($matrix->item(0)) : null);
}
}

View file

@ -4,12 +4,13 @@ namespace swf2ass;
class Shape {
/** @var QuadraticCurveRecord[]|CubicCurveRecord[]|LineRecord[]|MoveRecord[] */
public $edges;
public $styleRecord;
/** @var Record[] */
public array $edges;
public function __construct($edges = [], StyleRecord $record = null) {
/**
* @param Record[] $edges
*/
public function __construct(array $edges = []) {
$this->edges = $edges;
$this->styleRecord = $record;
}
}

176
src/ShapeConverter.php Normal file
View file

@ -0,0 +1,176 @@
<?php
namespace swf2ass;
class ShapeConverter {
private StyleList $styles;
private ?ActivePath $fill_style0 = null;
private ?ActivePath $fill_style1 = null;
private ?ActivePath $line_style = null;
private Coordinate $position;
private PendingPathMap $fills;
private PendingPathMap $strokes;
public DrawPathList $commands;
public function __construct(\DOMElement $element, StyleList $currentStyles) {
$this->styles = $currentStyles;
$this->position = new Coordinate(0, 0);
$this->fills = new PendingPathMap();
$this->strokes = new PendingPathMap();
$this->commands = new DrawPathList([]);
foreach ($element->getElementsByTagName("edges")->item(0)->childNodes as $node) {
if ($node->nodeName === "ShapeSetup") {
//Utils::dump_element($node);
if ($node->hasAttribute("x")) {
$move = MoveRecord::fromXML($node, $this->position);
$this->position = $move->coord;
$this->flush_paths();
}
if ($node->hasChildNodes()) {
$this->flush_layer();
$this->styles = StyleList::fromXML($node->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
}
if ($node->hasAttribute("fillStyle1")) {
if($this->fill_style1 !== null){
$this->fills->merge_path($this->fill_style1, true);
}
$id = (int)$node->getAttribute("fillStyle1");
$this->fill_style1 = $id > 0 ? new ActivePath($id, $this->position) : null;
}
if ($node->hasAttribute("fillStyle0")) {
if($this->fill_style0 !== null){
if(!$this->fill_style0->segment->is_empty()){
$this->fill_style0->flip();
$this->fills->merge_path($this->fill_style0, true);
}
}
$id = (int)$node->getAttribute("fillStyle0");
$this->fill_style0 = $id > 0 ? new ActivePath($id, $this->position) : null;
}
if ($node->hasAttribute("lineStyle")) {
if($this->line_style !== null){
$this->strokes->merge_path($this->line_style, false);
}
$id = (int)$node->getAttribute("lineStyle");
$this->line_style = $id > 0 ? new ActivePath($id, $this->position) : null;
}
}else if ($node->nodeName === "LineTo") {
$curve = LineRecord::fromXML($node, $this->position);
$this->visit_point($curve->coord, false);
$this->position = $curve->coord;
}else if ($node->nodeName === "CurveTo") {
$curve = QuadraticCurveRecord::fromXML($node, $this->position);
$this->visit_point($curve->control, true);
$this->visit_point($curve->anchor, false);
$this->position = $curve->anchor;
}
}
$this->flush_layer();
}
public function visit_point(Coordinate $coordinate, bool $isBezierControlPoint){
$point = new VisitedPoint($coordinate, $isBezierControlPoint);
if($this->fill_style0 !== null){
$this->fill_style0->add_point($point);
}
if($this->fill_style1 !== null){
$this->fill_style1->add_point($point);
}
if($this->line_style !== null){
$this->line_style->add_point($point);
}
}
public function flush_paths(){
if($this->fill_style1 !== null){
$this->fills->merge_path($this->fill_style1, true);
$this->fill_style1 = new ActivePath($this->fill_style1->style, $this->position);
}
if($this->fill_style0 !== null){
if(!$this->fill_style0->segment->is_empty()){
$this->fill_style0->flip();
$this->fills->merge_path($this->fill_style0, true);
}
$this->fill_style0 = new ActivePath($this->fill_style0->style, $this->position);
}
if($this->line_style !== null){
$this->strokes->merge_path($this->line_style, false);
$this->line_style = new ActivePath($this->line_style->style, $this->position);
}
}
public function flush_layer(){
$this->flush_paths();
$this->fill_style0 = null;
$this->fill_style1 = null;
$this->line_style = null;
foreach ($this->fills->map as $styleId => $path){
assert($styleId > 0 && $styleId < count($this->styles->fillStyles)); // ?????
$style = $this->styles->getFillStyle($styleId - 1);
if($style === null){
var_dump($this->styles);
var_dump($styleId);
}
$this->commands->commands[] = DrawPath::fill($style, $path->getShape());
}
$this->fills->map = [];
foreach ($this->strokes->map as $styleId => $path){
assert($styleId > 0 && $styleId < count($this->styles->lineStyles)); // ?????
$style = $this->styles->getLineStyle($styleId - 1);
foreach ($path->segments as $segment){
$segmentStyle = $style;
//Close non-closed segments by double drawing backwards
if(!$segment->is_closed()){
$other = clone $segment;
$other->flip();
$segment->merge($other);
//Reduce width of line style to account for double border
$segmentStyle = clone $style;
$segmentStyle->width /= 2;
}
$this->commands->commands[] = DrawPath::stroke($segmentStyle, $segment->getShape(), $segment->is_closed());
}
}
$this->strokes->map = [];
}
}

View file

@ -5,25 +5,33 @@ namespace swf2ass;
class ShapeDefinition implements ObjectDefinition {
public $id;
public Rectangle $bounds;
public StyleList $styles;
public ShapeList $shapeList;
public DrawPathList $shapeList;
public function __construct($id, Rectangle $bounds, StyleList $styles, ShapeList $shapes) {
public function __construct($id, Rectangle $bounds, DrawPathList $shapes) {
$this->id = $id;
$this->bounds = $bounds;
$this->styles = $styles;
$this->shapeList = $shapes;
}
static function fromXML(\DOMElement $element): ShapeDefinition {
$shapes = new ShapeList([]);
$styles = StyleList::fromXML($element->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
public function getId(){
return $this->id;
}
public function getShapeList() : DrawPathList{
return $this->shapeList;
}
static function fromXML(\DOMElement $element): ShapeDefinition {
$styles = StyleList::fromXML($element->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
//Utils::dump_element($element->getElementsByTagName("styles")->item(0));
$drawPathList = new DrawPathList([]);
foreach ($element->getElementsByTagName("shapes")->item(0)->childNodes as $node) {
if ($node instanceof \DOMElement and $node->nodeName === "Shape") {
$shapes = $shapes->merge(ShapeList::fromXML($node, $styles));
$drawPathList = $drawPathList->merge(DrawPathList::fromXML($node, $styles));
}
}
return new ShapeDefinition($element->getAttribute("objectID"), Rectangle::fromXML($element->getElementsByTagName("bounds")->item(0)->getElementsByTagName("Rectangle")->item(0)), $styles, $shapes);
return new ShapeDefinition($element->getAttribute("objectID"), Rectangle::fromXML($element->getElementsByTagName("bounds")->item(0)->getElementsByTagName("Rectangle")->item(0)), $drawPathList);
}
}

View file

@ -1,281 +0,0 @@
<?php
namespace swf2ass;
class ShapeList {
/** @var Shape[] */
public $shapes;
public function __construct($shapes = []) {
$this->shapes = $shapes;
}
public function merge(ShapeList $b): ShapeList {
return new ShapeList(array_merge($this->shapes, $b->shapes));
}
/**
* @param Record[] $subPath
* @param $lineStyleIdx
* @param $fillStyleIdx
* @param $fillStyleInsideIdx
* @param Record[][] $currentFillEdgeMap
* @param Record[][] $currentLineEdgeMap
*/
private static function processSubPath(array $subPath, $lineStyleIdx, $fillStyleIdx, $fillStyleInsideIdx, array &$currentFillEdgeMap, array &$currentLineEdgeMap) {
if ($fillStyleIdx > 0) {
if (!isset($currentFillEdgeMap[$fillStyleIdx])) {
$currentFillEdgeMap[$fillStyleIdx] = [];
}
foreach ($subPath as $n) {
$currentFillEdgeMap[$fillStyleIdx][] = $n->reverse();
}
}
if ($fillStyleInsideIdx > 0) {
if (!isset($currentFillEdgeMap[$fillStyleInsideIdx])) {
$currentFillEdgeMap[$fillStyleInsideIdx] = [];
}
foreach ($subPath as $n) {
$currentFillEdgeMap[$fillStyleInsideIdx][] = $n;
}
}
if ($lineStyleIdx > 0) {
if (!isset($currentLineEdgeMap[$lineStyleIdx])) {
$currentLineEdgeMap[$lineStyleIdx] = [];
}
foreach ($subPath as $n) {
$currentLineEdgeMap[$lineStyleIdx][] = $n;
}
}
}
/**
* @param Record[][] $edgeMap
*/
private static function cleanEdgeMap(array &$edgeMap) {
foreach ($edgeMap as $styleIdx => $subPath) {
if (count($subPath) > 0) {
$idx = 0;
/** @var Record $prevEdge */
$prevEdge = null;
/** @var Record $prevEdgeReverse */
$prevEdgeReverse = null;
$tmpPath = [];
while (count($subPath) > 0) {
$idx = 0;
while ($idx < count($subPath)) {
if ($prevEdge === null or $prevEdgeReverse->getStart()->equals($subPath[$idx]->getStart())) {
$edge = array_splice($subPath, $idx, 1)[0];
$tmpPath[] = $edge;
$prevEdge = $edge;
$prevEdgeReverse = $edge->reverse();
} else {
$found = false;
for ($i = 0; $i < count($subPath); ++$i) {
if ($prevEdgeReverse->getStart()->equals($subPath[$i]->getStart())) {
$idx = $i;
$found = true;
break;
}
}
if (!$found) {
$idx = 0;
$prevEdge = null;
$prevEdgeReverse = null;
}
}
}
}
$edgeMap[$styleIdx] = [];
$pos = new Coordinate(0, 0);
foreach ($tmpPath as $node) {
if (!$pos->equals($node->getStart())) {
$edgeMap[$styleIdx][] = new MoveRecord($node->getStart(), $pos);
}
$edgeMap[$styleIdx][] = $node;
$pos = $node->reverse()->getStart();
}
}
}
}
public static function fromXML(\DOMElement $element, StyleList $currentStyles): ShapeList {
/** @var Shape[] $shapes */
$shapes = [];
/** @var Record[] $edgeRecords */
$edgeRecords = [];
$styleRecord = new StyleRecord();
$styleRecord->styleList = clone $currentStyles;
$fillStyleIdxOffset = 0;
$lineStyleIdxOffset = 0;
$cursorX = 0;
$cursorY = 0;
foreach ($element->getElementsByTagName("edges")->item(0)->childNodes as $node) {
if ($node instanceof \DOMElement) {
if ($node->nodeName === "CurveTo") {
$curve = QuadraticCurveRecord::fromXML($node, $cursorX, $cursorY);
$cursorX = $curve->anchor->x;
$cursorY = $curve->anchor->y;
$edgeRecords[] = $curve;
} else if ($node->nodeName === "LineTo") {
$line = LineRecord::fromXML($node, $cursorX, $cursorY);
$cursorX = $line->coord->x;
$cursorY = $line->coord->y;
$edgeRecords[] = $line;
} else if ($node->nodeName === "ShapeSetup") {
$record = StyleRecord::nullStyle();
$styleChange = false;
if ($node->hasChildNodes()) {
$newStyles = StyleList::fromXML($node->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
foreach ($newStyles->fillStyles as $s) {
$styleRecord->styleList->fillStyles[] = $s;
++$fillStyleIdxOffset;
}
foreach ($newStyles->lineStyles as $s) {
$styleRecord->styleList->lineStyles[] = $s;
++$lineStyleIdxOffset;
}
$styleChange = true;
$record->styleList = $styleRecord->styleList;
}
if ($node->hasAttribute("fillStyle0")) {
$fillStyle = (int)$node->getAttribute("fillStyle0");
$styleRecord->fillStyle = $fillStyle + ($fillStyle > 0 ? $fillStyleIdxOffset : 0);
$styleChange = true;
$record->fillStyle = $styleRecord->fillStyle;
}
if ($node->hasAttribute("fillStyle1")) {
$fillStyleInside = (int)$node->getAttribute("fillStyle1");
$styleRecord->fillStyleInside = $fillStyleInside + ($fillStyleInside > 0 ? $fillStyleIdxOffset : 0);
$styleChange = true;
$record->fillStyleInside = $styleRecord->fillStyleInside;
}
if ($node->hasAttribute("lineStyle")) {
$lineStyle = (int)$node->getAttribute("lineStyle");
$styleRecord->lineStyle = $lineStyle + ($lineStyle > 0 ? $lineStyleIdxOffset : 0);
$styleChange = true;
$record->lineStyle = $styleRecord->lineStyle;
}
if ($styleChange) {
$edgeRecords[] = $record;
}
if ($node->hasAttribute("x")) {
$move = MoveRecord::fromXML($node, $cursorX, $cursorY);
$cursorX = $move->coord->x;
$cursorY = $move->coord->y;
$edgeRecords[] = $move;
}
}
}
}
/** @var Record[] $subPath */
$subPath = [];
$currentFillStyleIdx = 0;
$currentFillStyleInsideIdx = 0;
$currentLineStyleIdx = 0;
/** @var Record[][] $currentFillEdgeMap */
$currentFillEdgeMap = [];
/** @var Record[] $currentLineEdgeMap */
$currentLineEdgeMap = [];
$fillEdgeMaps = [];
$lineEdgeMaps = [];
$numGroups = 0;
$lastNode = null;
foreach ($edgeRecords as $edge) {
if ($edge instanceof LineRecord or $edge instanceof CubicCurveRecord or $edge instanceof QuadraticCurveRecord) {
$subPath[] = $edge;
} elseif ($edge instanceof MoveRecord) {
//$subPath[] = $edge;
} else if ($edge instanceof StyleRecord) {
$changed = false;
if ($edge->lineStyle !== null or $edge->fillStyle !== null or $edge->fillStyleInside !== null) {
self::processSubPath($subPath, $currentLineStyleIdx, $currentFillStyleIdx, $currentFillStyleInsideIdx, $currentFillEdgeMap, $currentLineEdgeMap);
/** @var Record[] $subPath */
$subPath = [];
$changed = true;
}
if ($changed and $edge->lineStyle === 0 and $edge->fillStyle === 0 and $edge->fillStyleInside === 0) {
self::cleanEdgeMap($currentFillEdgeMap);
self::cleanEdgeMap($currentLineEdgeMap);
$fillEdgeMaps[] = $currentFillEdgeMap;
$lineEdgeMaps[] = $currentLineEdgeMap;
$currentFillEdgeMap = [];
$currentLineEdgeMap = [];
$currentFillStyleIdx = 0;
$currentFillStyleInsideIdx = 0;
$currentLineStyleIdx = 0;
++$numGroups;
} else {
if ($edge->lineStyle !== null and $edge->lineStyle !== $currentLineStyleIdx) {
$currentLineStyleIdx = $edge->lineStyle;
}
if ($edge->fillStyle !== null and $edge->fillStyle !== $currentFillStyleIdx) {
$currentFillStyleIdx = $edge->fillStyle;
}
if ($edge->fillStyleInside !== null and $edge->fillStyleInside !== $currentFillStyleInsideIdx) {
$currentFillStyleInsideIdx = $edge->fillStyleInside;
}
}
}
}
self::processSubPath($subPath, $currentLineStyleIdx, $currentFillStyleIdx, $currentFillStyleInsideIdx, $currentFillEdgeMap, $currentLineEdgeMap);
self::cleanEdgeMap($currentFillEdgeMap);
self::cleanEdgeMap($currentLineEdgeMap);
$fillEdgeMaps[] = $currentFillEdgeMap;
$lineEdgeMaps[] = $currentLineEdgeMap;
$currentFillEdgeMap = [];
$currentLineEdgeMap = [];
++$numGroups;
for ($i = 0; $i < $numGroups; ++$i) {
//ksort($fillEdgeMaps[$i]);
ksort($lineEdgeMaps[$i]);
foreach ($fillEdgeMaps[$i] as $fillStyle => $records) {
if (count($records) <= 1) {
continue;
}
$record = new StyleRecord();
$record->fillStyleInside = $fillStyle;
$record->styleList = $styleRecord->styleList;
$shapes[] = new Shape($records, $record);
}
foreach ($lineEdgeMaps[$i] as $lineStyle => $records) {
if (count($records) <= 1) {
continue;
}
$record = new StyleRecord();
$record->lineStyle = $lineStyle;
$record->styleList = $styleRecord->styleList;
$shapes[] = $s = new Shape($records, $record);
}
}
return new ShapeList($shapes);
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace swf2ass;
class ShiftedRadialGradient implements Gradient {
/** @var GradientItem[] */
public $colors;
/** @var MatrixTransform|null */
public ?MatrixTransform $transform;
public array $attributes;
public function __construct($colors = [], MatrixTransform $transform = null, array $attributes = []) {
$this->colors = $colors;
$this->transform = $transform;
$this->attributes = $attributes;
}
public function getItems() : array {
return $this->colors;
}
public function getMatrixTransform() : ?MatrixTransform{
return $this->transform;
}
public static function fromXML(\DOMElement $element): ShiftedRadialGradient {
$colors = [];
foreach ($element->getElementsByTagName("GradientItem") as $item) {
$colors[] = GradientItem::fromXML($item);
}
$matrix = $element->getElementsByTagName("matrix");
return new ShiftedRadialGradient($colors, $matrix->count() > 0 ? MatrixTransform::fromXML($matrix->item(0)) : null, [
"spreadMode" => $element->hasAttribute("spreadMode") ? $element->getAttribute("spreadMode") : null,
"interpolationMode" => $element->hasAttribute("interpolationMode") ? $element->getAttribute("interpolationMode") : null,
"shift" => $element->hasAttribute("shift") ? $element->getAttribute("shift") : null,
]);
}
}

View file

@ -7,15 +7,13 @@ class StyleContainer {
public $frx = null, $fry = null;
public $fscx = null, $fscy = null;
public $fax = null, $fay = null;
/** @var ?Coordinate */
public $pos = null;
public ?Coordinate $pos = null;
/** @var ?Coordinate[] */
public $move = null;
/** @var ?Color */
public $color1 = null;
/** @var ?Color */
public $color3 = null;
public ?Color $color1 = null;
public ?Color $color3 = null;
/** @var ?Color */
public $original_color1 = null;
@ -60,7 +58,7 @@ class StyleContainer {
if ($k === "move" or $k === "pos" or $k === "transitionStart" or $k === "transitionEnd" or $k === "moveTransitionStart" or $k === "moveTransitionEnd") {
} elseif ($k === "color1" or $k === "color3" or $k === "original_color1" or $k === "original_color3") {
if (!(($container->{$k} !== null and $value !== null and $container->{$k} == $value) or $container->{$k} === $value)) {
if (!(($container->{$k} !== null and $value !== null and $container->{$k}->equals($value)) or $container->{$k} === $value)) {
return false;
}
@ -98,7 +96,7 @@ class StyleContainer {
if ($k === "move" or $k === "pos" or $k === "transitionStart" or $k === "transitionEnd" or $k === "moveTransitionStart" or $k === "moveTransitionEnd" or $k === "original_color1" or $k === "original_color3") {
} elseif ($k === "color1") {
if ($prevValues !== null and $prevValues->{$k} !== null and $prevValues->{$k} == $value) {
if ($prevValues !== null and $prevValues->{$k} !== null and $prevValues->{$k}->equals($value)) {
continue;
}
if ($value !== null) {
@ -107,11 +105,11 @@ class StyleContainer {
$a .= "\\1a&HFF&";
}
} elseif ($k === "color3") {
if ($prevValues !== null and $prevValues->{$k} !== null and $prevValues->{$k} == $value) {
if ($prevValues !== null and $prevValues->{$k} !== null and $prevValues->{$k}->equals($value)) {
continue;
}
if ($value !== null) {
$a .= "\\3c&H" . strtoupper(Utils::padHex(dechex($value->b ?? 0))) . strtoupper(Utils::padHex(dechex($value->g ?? 0))) . strtoupper(Utils::padHex(dechex($value->r ?? 0))) . "&\\1a&H" . strtoupper(Utils::padHex(dechex($value->alpha ?? 0))) . "&";
$a .= "\\3c&H" . strtoupper(Utils::padHex(dechex($value->b ?? 0))) . strtoupper(Utils::padHex(dechex($value->g ?? 0))) . strtoupper(Utils::padHex(dechex($value->r ?? 0))) . "&\\3a&H" . strtoupper(Utils::padHex(dechex($value->alpha ?? 0))) . "&";
} else if ($outputEmpty) {
$a .= "\\3a&HFF&";
}
@ -203,7 +201,7 @@ class StyleContainer {
if ($k === "move" or $k === "pos" or $k === "transitionStart" or $k === "transitionEnd" or $k === "moveTransitionStart" or $k === "moveTransitionEnd" or $k === "original_color1" or $k === "original_color3") {
} elseif ($k === "color1" or $k === "color3") {
if ($value !== null and $secondStyle->{$k} !== null and $value == $secondStyle->{$k}) {
if ($value !== null and $secondStyle->{$k} !== null and $value->equals($secondStyle->{$k})) {
$secondStyle->{$k} = null;
}
} elseif ($value === $secondStyle->{$k}) {

View file

@ -4,10 +4,10 @@ namespace swf2ass;
class StyleList {
/** @var LinearGradient[]|Color[] */
public $fillStyles;
/** @var LineStyle */
public $lineStyles;
/** @var FillStyleRecord[] */
public array $fillStyles;
/** @var LineStyleRecord[] */
public array $lineStyles;
public function __construct($fillStyles = [], $lineStyles = []) {
$this->fillStyles = $fillStyles;
@ -20,24 +20,52 @@ class StyleList {
foreach ($element->getElementsByTagName("fillStyles")->item(0)->childNodes as $node) {
if ($node instanceof \DOMElement) {
if ($node->nodeName === "Solid") {
$fillStyles[] = Color::fromXML($node->getElementsByTagName("color")->item(0)->getElementsByTagName("Color")->item(0));
$fillStyles[] = new FillStyleRecord(Color::fromXML($node->getElementsByTagName("color")->item(0)->getElementsByTagName("Color")->item(0)));
} else if ($node->nodeName === "LinearGradient") {
$fillStyles[] = LinearGradient::fromXML($node);
$fillStyles[] = new FillStyleRecord(LinearGradient::fromXML($node));
} else if ($node->nodeName === "RadialGradient") {
$fillStyles[] = new FillStyleRecord(RadialGradient::fromXML($node));
} else if ($node->nodeName === "ShiftedRadialGradient") {
$fillStyles[] = new FillStyleRecord(RadialGradient::fromXML($node));
} else if ($node->nodeName === "ClippedBitmap") {
Utils::dump_element($node);
//TODO
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 255 / 2));
} else if ($node->nodeName === "ClippedBitmap2") {
Utils::dump_element($node);
//TODO
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 255 / 2));
} else if ($node->nodeName === "TiledBitmap") {
//TODO
Utils::dump_element($node);
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 255 / 2));
}
}
}
foreach ($element->getElementsByTagName("lineStyles")->item(0)->childNodes as $node) {
if ($node instanceof \DOMElement) {
$ob = new LineStyle();
$ob->width = max(TWIP_SIZE, (int)$node->getAttribute("width"));
//$fill = null;
$colors = $node->getElementsByTagName("color");
$colors2 = $node->getElementsByTagName("Color");
$grads = $node->getElementsByTagName("coLinearGradientlor");
$color = null;
if ($colors->count() > 0) {
$ob->item = Color::fromXML($colors->item(0)->getElementsByTagName("Color")->item(0));
$color = Color::fromXML($colors->item(0)->getElementsByTagName("Color")->item(0));
}else if ($colors2->count() > 0) {
$color = Color::fromXML($colors2->item(0));
} else if ($grads->count() > 0) {
$ob->item = LinearGradient::fromXML($grads->item(0)->getElementsByTagName("LinearGradient")->item(0));
//TODO: other gradients?
//$fill = new FillStyleRecord(LinearGradient::fromXML($grads->item(0)->getElementsByTagName("LinearGradient")->item(0)));
}
$lineStyles[] = $ob;
if($color === null){
Utils::dump_element($node);
}
$lineStyles[] = new LineStyleRecord(max(TWIP_SIZE, (int)$node->getAttribute("width")), $color, null);
}
}
return new StyleList($fillStyles, $lineStyles);
@ -45,17 +73,17 @@ class StyleList {
/**
* @param $i
* @return Color|LinearGradient|mixed|null
* @return FillStyleRecord|null
*/
public function getFillStyle($i) {
public function getFillStyle($i): ?FillStyleRecord {
return $this->fillStyles[$i] ?? null;
}
/**
* @param $i
* @return Color|LinearGradient|mixed|null
* @return LineStyleRecord|null
*/
public function getLineStyle($i) {
public function getLineStyle($i): ?LineStyleRecord {
return $this->lineStyles[$i] ?? null;
}
}

View file

@ -2,19 +2,6 @@
namespace swf2ass;
class StyleRecord {
public ?StyleList $styleList = null;
public ?int $fillStyle = 0;
public ?int $fillStyleInside = 0;
public ?int $lineStyle = 0;
interface StyleRecord {
public static function nullStyle(): StyleRecord {
$record = new StyleRecord();
$record->styleList = null;
$record->lineStyle = null;
$record->fillStyle = null;
$record->fillStyleInside = null;
return $record;
}
}

View file

@ -91,6 +91,7 @@ abstract class Utils {
$ob = new DisplayEntry();
$objectID = $node->getAttribute("objectID");
if (!isset($currentState->objects[$objectID])) {
var_dump("Unknown object id $objectID");
continue;
throw new \Exception("Unknown object id $objectID");
}
@ -143,12 +144,21 @@ abstract class Utils {
}
++$currentState->frameNumber;
} else if ($node->nodeName === "SoundStreamBlock") {
} else if ($node->nodeName === "DefineSprite") {
//TODO do actual animation
var_dump("TODO DefineSprite");
//Utils::dump_element($node);
//var_dump($node);
foreach ($node->getElementsByTagName("tags")->item(0)->childNodes as $node2) {
if ($node2 instanceof \DOMElement) {
if($node2->nodeName === "PlaceObject2" and isset($currentState->objects[$node2->getAttribute("objectID")])){
//TODO
$currentState->objects[$node->getAttribute("objectID")] = $currentState->objects[$node2->getAttribute("objectID")];
var_dump($node->getAttribute("objectID") . " === " . $node2->getAttribute("objectID") . " " . get_class($currentState->objects[$node->getAttribute("objectID")]));
break;
}
}
}
} else if ($node->nodeName === "DefineBitsJPEG2" || $node->nodeName === "DefineBitsJPEG3") {
$bitmapDef = JPEGBitmapDefinition::fromXML($node);
$currentState->objects[$bitmapDef->id] = $bitmapDef;

14
src/VisitedPoint.php Normal file
View file

@ -0,0 +1,14 @@
<?php
namespace swf2ass;
class VisitedPoint {
public Coordinate $pos;
public bool $is_bezier_control;
public function __construct(Coordinate $pos, bool $is_bezier_control = false) {
$this->pos = $pos;
$this->is_bezier_control = $is_bezier_control;
}
}