299 lines
10 KiB
PHP
299 lines
10 KiB
PHP
<?php
|
|
|
|
namespace swf2ass;
|
|
|
|
|
|
class BitmapConverter {
|
|
/** @var Color[][] */
|
|
private array $pixelMatrix;
|
|
private Vector2 $resolution;
|
|
|
|
public function __construct(array $pixelMatrix) {
|
|
$this->pixelMatrix = $pixelMatrix;
|
|
$this->resolution = new Vector2(count($pixelMatrix[0]), count($pixelMatrix));
|
|
}
|
|
|
|
private static function calculateAreaOfPath($path) {
|
|
$totalArea = 0;
|
|
/** @var Vector2[][] $subPolygons */
|
|
$subPolygons = [];
|
|
/** @var Vector2[] $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->to;
|
|
}
|
|
}
|
|
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 Vector2(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->to;
|
|
}
|
|
|
|
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->to->x . "_" . $p->to->y])) {
|
|
$cornerMap[$p->to->x . "_" . $p->to->y] = [];
|
|
}
|
|
$cornerMap[$p->to->x . "_" . $p->to->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->to->equals($p->to)) or ($currentPath->start->equals($p->to) and $currentPath->to->equals($p->start)))) {
|
|
unset($path[$i]);
|
|
unset($cornerMap[$k1 = $p->start->x . "_" . $p->start->y][$i]);
|
|
unset($cornerMap[$k2 = $p->to->x . "_" . $p->to->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->to->x . "_" . $p->to->y])) {
|
|
$cornerMap[$p->to->x . "_" . $p->to->y] = [];
|
|
}
|
|
$cornerMap[$p->to->x . "_" . $p->to->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->to->sub($currentPath->start);
|
|
$sortedPath[] = $startPath;
|
|
unset($newPath[$index - 1]);
|
|
unset($cornerMap[$startPath->start->x . "_" . $startPath->start->y][$index - 1]);
|
|
unset($cornerMap[$startPath->to->x . "_" . $startPath->to->y][$index - 1]);
|
|
|
|
while (($nextPath = self::findNextCorner($currentPath->to, $newPath, $cornerMap)) !== null) {
|
|
$nextVector = $nextPath->to->sub($nextPath->start);
|
|
if ($nextVector->equals($currentVector)) { //Enlongate
|
|
$currentPath->to = $nextPath->to;
|
|
continue;
|
|
}
|
|
$sortedPath[] = $nextPath;
|
|
|
|
if ($nextPath->to->equals($nextPath->start)) { //Reached the end!
|
|
$startPath = null;
|
|
break;
|
|
}
|
|
$currentPath = $nextPath;
|
|
$currentVector = $nextVector;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return $sortedPath;
|
|
}
|
|
|
|
/**
|
|
* @param Vector2 $corner
|
|
* @param LineRecord[] $path
|
|
*
|
|
* @return ?LineRecord
|
|
*/
|
|
private static function findNextCorner(Vector2 $corner, array &$path, array &$cornerMap): ?LineRecord {
|
|
foreach ($cornerMap[$corner->x . "_" . $corner->y] as $i => $p) {
|
|
unset($path[$i]);
|
|
unset($cornerMap[$p->start->x . "_" . $p->start->y][$i]);
|
|
unset($cornerMap[$p->to->x . "_" . $p->to->y][$i]);
|
|
if ($p->start->equals($corner)) {
|
|
return $p;
|
|
} else if ($p->to->equals($corner)) {
|
|
return $p->reverse();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
private static function getBoundingBox($path) {
|
|
$bb = new Rectangle(new \swf2ass\Vector2(PHP_INT_MAX, PHP_INT_MAX), new Vector2(PHP_INT_MIN, PHP_INT_MIN));
|
|
/*if(count($path) > 0){
|
|
$first = reset($path);
|
|
if($first instanceof MoveRecord or $first instanceof LineRecord){
|
|
$bb->topLeft = $first->to;
|
|
$bb->bottomRight = $first->to;
|
|
}
|
|
}*/
|
|
foreach ($path as $i => $e) {
|
|
/** @var LineRecord|MoveRecord $e */
|
|
if ($i === 0 and $e instanceof MoveRecord) {
|
|
continue;
|
|
}
|
|
|
|
if (!$bb->inBounds($e->to)) {
|
|
$bb->topLeft->x = min($e->to->x, $bb->topLeft->x);
|
|
$bb->topLeft->y = min($e->to->y, $bb->topLeft->y);
|
|
$bb->bottomRight->x = max($e->to->x, $bb->bottomRight->x);
|
|
$bb->bottomRight->y = max($e->to->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 Vector2($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 Vector2(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(Constants::TWIP_SIZE);
|
|
|
|
$edges[] = new MoveRecord($pos, new Vector2(0, 0));
|
|
|
|
foreach ($path as $edge) {
|
|
if ($edge instanceof MoveRecord) {
|
|
$edges[] = new MoveRecord($edge->to->multiply(Constants::TWIP_SIZE), $edge->start->multiply(Constants::TWIP_SIZE));
|
|
} else if ($edge instanceof LineRecord) {
|
|
$edges[] = new LineRecord($edge->to->multiply(Constants::TWIP_SIZE), $edge->start->multiply(Constants::TWIP_SIZE));
|
|
} else {
|
|
var_dump($edge);
|
|
throw new \Exception();
|
|
}
|
|
}
|
|
|
|
$commands[] = DrawPath::fill(new FillStyleRecord($colors[$k]), new Shape($edges));
|
|
}
|
|
|
|
return new DrawPathList($commands);
|
|
}
|
|
} |