私はこの問題を解決するために数学を使用せず、分析のみを使用します。
次の画像を検討してください。
ここでは、すべてのケースを確実にカバーするために、一度に2つの例を示します。
私が問題を解決した方法は、次のものを使用します:
コーナーは、2〜8回書き込まれたポイントです。長方形ABCDを想像すると、コーナーBはABおよびBCと共有されるため(ピクセルは2回配置されます)、少なくとも2回です。長方形16、17、18、19の場合、1つのポイントが4つの長方形、つまり8つの辺で共有される場合に8回書き込むことができます。
側面とは、1回または2回(角を考慮せずに)書くことができる点のセットです。側面が単独で、別の側面に近くない場合は1回、側面が別の側面に近い場合は2回です。そして、別の側面に近くない側は外側に近くなります。それは最終的なポリゴンの一部になるはずです。
だからここにロジックがあります:
- 画像全体と同じサイズで、ゼロ(0)で埋められた仮想空間を作成します。
すべての長方形を書き込みますが、ピクセルを書き込む代わりに、仮想ピクセルの値をインクリメントします
21111111112
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
2111111111622222222261111111112
1 2 2 1
1 2 2 1
1 2 2 1
1 2 2 1
1 2 2 1
1 2 2 1
1 2 2 1
1 2 2 1
1 2 2 1
21111111116222222222611111111141111111112
1 2 1
1 2 1
1 2 1
1 2 1
1 2 1
1 2 1
1 2 1
1 2 1
1 2 1
(...)
(申し訳ありませんが、私のインデントにはSOのフォーマットツールに問題があるようです)
- 1に設定したコーナーを除き、値が2より大きいすべての仮想ポイントを削除します。
この時点で、ポリゴンとポイントのみがあります(他のいくつかの長方形の中央にコーナーがあります)。
11111111111
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
11111111111 11111111111
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
11111111111 111111111111111111111
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
11111111111 1 11111111111
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
11111111111111111111111111111111111111111
次に、1つまたは複数のポリゴンを探す必要があります(11 12 13 14 3 4 5の長方形の場合、複数のポリゴンがある可能性があります)。つまり、仮想画像内のポイントを検索します。
ポイントが単独の場合(上記を参照)、その上、左、下、または右側にポイントはありません。これは、他のいくつかの長方形の中央にあるコーナーです(前にコーナーを保存しました)。これは非常に注意が必要ですが、すべての長方形が4ピクセルより大きい場合に機能します。
ポイントを見つけたら、それを保存し、1つの方向(上/左/右/下)を繰り返して、ポイントがなくなるまでこの方向へのポイントを削除します。これはポリゴンの1つのコーナーです。どの方向にも移動できなくなるまでこの方法を続けます。これは、ポリゴンの終わりにいることを意味します。
これで、2次元配列が得られます。最初の次元はポリゴンのリスト(最初の例の場合)であり、2番目の次元はポリゴンを表すポイントのリストです。ポリゴンごとに、それらのポイントを繰り返し、現在のポイントを次のポイントに結合してポリゴンを取得する必要があります。
今の結果はどうですか?
実装 :
class PolygonMaker
{
private $image;
private $width;
private $height;
private $vImage;
public function __construct($width, $height)
{
// create a GD image to display results and debug
$this->width = $width;
$this->height = $height;
$this->image = imagecreatetruecolor($width, $height);
$white = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
imagefill($this->image, 0, 0, $white);
imagesetthickness($this->image, 3);
}
public function __destruct()
{
imagedestroy($this->image);
}
public function display()
{
// Display gd image as png
header("Content-type: image/png");
imagepng($this->image);
}
public function drawRectangles(array $rectangles, $r, $g, $b)
{
// Draw rectangles as they are inside the gd image
foreach ($rectangles as $rectangle)
{
list($tx, $ty) = $rectangle[0];
list($bx, $by) = $rectangle[1];
$color = imagecolorallocate($this->image, $r, $g, $b);
imagerectangle($this->image, $tx, $ty, $bx, $by, $color);
}
}
public function findPolygonsPoints(array $rectangles)
{
// Create a virtual image where rectangles will be "drawn"
$this->_createVirtualImage($rectangles);
$polygons = array ();
// Searches for all polygons inside the virtual image
while (!is_null($beginPoint = $this->_findPolygon()))
{
$polygon = array ();
// Push the first point
$polygon[] = $this->_cleanAndReturnPolygonPoint($beginPoint);
$point = $beginPoint;
// Try to go up, down, left, right until there is no more point
while ($point = $this->_getNextPolygonPoint($point))
{
// Push the found point
$polygon[] = $this->_cleanAndReturnPolygonPoint($point);
}
// Push the first point at the end to close polygon
$polygon[] = $beginPoint;
// Add the polygon to the list, in case of several polygons in the image
$polygons[] = $polygon;
}
$this->vImage = null;
return $polygons;
}
private function _createVirtualImage(array $rectangles)
{
// Create a 0-filled grid where will be stored rectangles
$this->vImage = array_fill(0, $this->height, array_fill(0, $this->width, 0));
// Draw each rectangle to that grid (each pixel increments the corresponding value of the grid of 1)
foreach ($rectangles as $rectangle)
{
list($x1, $y1, $x2, $y2) = array ($rectangle[0][0], $rectangle[0][1], $rectangle[1][0], $rectangle[1][1]);
$this->_drawVirtualLine($x1, $y1, $x1, $y2); // top-left, bottom-left
$this->_drawVirtualLine($x2, $y1, $x2, $y2); // top-right, bottom-right
$this->_drawVirtualLine($x1, $y1, $x2, $y1); // top-left, top-right
$this->_drawVirtualLine($x1, $y2, $x2, $y2); // bottom-left, bottom-right
}
// Remove all pixels that are scored > 1 (that's our logic!)
for ($y = 0; ($y < $this->height); $y++)
{
for ($x = 0; ($x < $this->width); $x++)
{
$value = &$this->vImage[$y][$x];
$value = $value > 1 ? 0 : $value;
}
}
}
private function _drawVirtualLine($x1, $y1, $x2, $y2)
{
// Draw a vertial line in the virtual image
if ($x1 == $x2)
{
if ($y1 > $y2)
{
list($x1, $y1, $x2, $y2) = array ($x2, $y2, $x1, $y1);
}
for ($y = $y1; ($y <= $y2); $y++)
{
$this->vImage[$y][$x1]++;
}
}
// Draw an horizontal line in the virtual image
if ($y1 == $y2)
{
if ($x1 > $x2)
{
list($x1, $y1, $x2, $y2) = array ($x2, $y2, $x1, $y1);
}
for ($x = $x1; ($x <= $x2); $x++)
{
$this->vImage[$y1][$x]++;
}
}
// Force corners to be 1 (because one corner is at least used 2 times but we don't want to remove them)
$this->vImage[$y1][$x1] = 1;
$this->vImage[$y1][$x2] = 1;
$this->vImage[$y2][$x1] = 1;
$this->vImage[$y2][$x2] = 1;
}
private function _findPolygon()
{
// We're looking for the first point in the virtual image
foreach ($this->vImage as $y => $row)
{
foreach ($row as $x => $value)
{
if ($value == 1)
{
// Removes alone points ( every corner have been set to 1, but some corners are alone (eg: middle of 4 rectangles)
if ((!$this->_hasPixelAtBottom($x, $y)) && (!$this->_hasPixelAtTop($x, $y))
&& (!$this->_hasPixelAtRight($x, $y)) && (!$this->_hasPixelAtLeft($x, $y)))
{
$this->vImage[$y][$x] = 0;
continue;
}
return array ($x, $y);
}
}
}
return null;
}
private function _hasPixelAtBottom($x, $y)
{
// The closest bottom point is a point positionned at (x, y + 1)
return $this->_hasPixelAt($x, $y + 1);
}
private function _hasPixelAtTop($x, $y)
{
// The closest top point is a point positionned at (x, y - 1)
return $this->_hasPixelAt($x, $y - 1);
}
private function _hasPixelAtLeft($x, $y)
{
// The closest left point is a point positionned at (x - 1, y)
return $this->_hasPixelAt($x - 1, $y);
}
private function _hasPixelAtRight($x, $y)
{
// The closest right point is a point positionned at (x + 1, y)
return $this->_hasPixelAt($x + 1, $y);
}
private function _hasPixelAt($x, $y)
{
// Check if the pixel (x, y) exists
return ((isset($this->vImage[$y])) && (isset($this->vImage[$y][$x])) && ($this->vImage[$y][$x] > 0));
}
private function _cleanAndReturnPolygonPoint(array $point)
{
// Remove a point from the virtual image
list($x, $y) = $point;
$this->vImage[$y][$x] = 0;
return $point;
}
private function _getNextPolygonPoint(array $point)
{
list($x, $y) = $point;
// Initialize modifiers, to move to the right, bottom, left or top.
$directions = array(
array(1, 0), // right
array(0, 1), // bottom
array(-1, 0), // left
array(0, -1), // top
);
// Try to get to one direction, if we can go ahead, there is a following corner
$return = null;
foreach ($directions as $direction)
{
list($xModifier, $yModifier) = $direction;
if (($return = $this->_iterateDirection($x, $y, $xModifier, $yModifier)) !== null)
{
return $return;
}
}
// the point is alone : we are at the end of the polygon
return $return;
}
private function _iterateDirection($x, $y, $xModifier, $yModifier)
{
// This method follows points in a direction until the last point
$return = null;
while ($this->_hasPixelAt($x + $xModifier, $y + $yModifier))
{
$x = $x + $xModifier;
$y = $y + $yModifier;
// Important : we remove the point so we'll not get back when moving
$return = $this->_cleanAndReturnPolygonPoint(array ($x, $y));
}
// The last point is a corner of the polygon because if it has no following point, we change direction
return $return;
}
/**
* This method draws a polygon with the given points. That's to check if
* our calculations are valid.
*
* @param array $points An array of points that define the polygon
*/
public function drawPolygon(array $points, $r, $g, $b)
{
$count = count($points);
for ($i = 0; ($i < $count); $i++)
{
// Draws a line between the current and the next point until the last point is reached
if (array_key_exists($i + 1, $points))
{
list($x1, $y1) = $points[$i];
list($x2, $y2) = $points[$i + 1];
$black = imagecolorallocate($this->image, $r, $g, $b);
imageline($this->image, $x1, $y1, $x2, $y2, $black);
}
}
}
}
使用例:
$rectanglesA = array (
array ( // 1
array (50, 50), // tx, ty
array (75, 75), // bx, by
),
array ( // 2
array (75, 50), // tx, ty
array (125, 75), // bx, by
),
array ( // 3
array (125, 50), // tx, ty
array (175, 75), // bx, by
),
array ( // 4
array (175, 50), // tx, ty
array (225, 75), // bx, by
),
array ( // 5
array (225, 50), // tx, ty
array (275, 75), // bx, by
),
array ( // 6
array (275, 50), // tx, ty
array (325, 75), // bx, by
),
array ( // 7
array (325, 50), // tx, ty
array (375, 75), // bx, by
),
array ( // 8
array (375, 50), // tx, ty
array (425, 75), // bx, by
),
array ( // 9
array (320, 42), // tx, ty
array (330, 50), // bx, by
),
array ( // 10
array (425, 60), // tx, ty
array (430, 65), // bx, by
),
array ( // 11
array (100, 75), // tx, ty
array (150, 250), // bx, by
),
array ( // 12
array (150, 125), // tx, ty
array (250, 150), // bx, by
),
array ( // 13
array (225, 75), // tx, ty
array (250, 125), // bx, by
),
array ( // 14
array (150, 92), // tx, ty
array (180, 107), // bx, by
),
);
$rectanglesB = array (
array ( // 15
array (200, 300), // tx, ty
array (250, 350), // bx, by
),
array ( // 16
array (250, 250), // tx, ty
array (300, 300), // bx, by
),
array ( // 17
array (250, 300), // tx, ty
array (300, 350), // bx, by
),
array ( // 18
array (300, 250), // tx, ty
array (350, 300), // bx, by
),
array ( // 19
array (300, 300), // tx, ty
array (350, 350), // bx, by
),
array ( // 20
array (300, 200), // tx, ty
array (350, 250), // bx, by
),
array ( // 21
array (350, 300), // tx, ty
array (400, 350), // bx, by
),
array ( // 22
array (350, 200), // tx, ty
array (400, 250), // bx, by
),
array ( // 23
array (350, 150), // tx, ty
array (400, 200), // bx, by
),
array ( // 24
array (400, 200), // tx, ty
array (450, 250), // bx, by
),
);
$polygonMaker = new PolygonMaker(500, 400);
// Just to get started and see what's happens
//$polygonMaker->drawRectangles($rectanglesA, 0xFF, 0x00, 0x00);
//$polygonMaker->drawRectangles($rectanglesB, 0xFF, 0x00, 0x00);
$polygonsA = $polygonMaker->findPolygonsPoints($rectanglesA);
foreach ($polygonsA as $polygon)
{
$polygonMaker->drawPolygon($polygon, 0x00, 0x00, 0x00);
}
$polygonsB = $polygonMaker->findPolygonsPoints($rectanglesB);
foreach ($polygonsB as $polygon)
{
$polygonMaker->drawPolygon($polygon, 0x00, 0x00, 0x00);
}
// Display image to see if everything is correct
$polygonMaker->display();