annotateImage - image magick

Shooter2k

Erfahrenes Mitglied
Hallo liebes Forum,
ich habe ein kleines Problem mit image magick.

Ich erstelle ein Bild 500px * 500px. Dieses befülle ich mit zufälligen Texten an zufälligen Positionen.
Nun möchte ich lediglich sicherstellen, dass sich die Texte nicht überschneiden. Wie mache ich das?

Ich setze hier zufällige Positonen:
$x= rand(1,$height);
$y= rand(1, $width);

Hier mein bisheriger Code:
PHP:
function create()
{
		#$userID = rand(1,1000000);

		$width 	= 15000;
		$height = 10000;

		/* Create an Imagick object with transparent canvas */
		$img = new Imagick();
		$draw = new ImagickDraw();


		$img->newImage($width, $height, new ImagickPixel('white'));

		$draw->setFillColor('black');
		//$draw->setFont('Arial');
		


		$sql = "SELECT spruch, userID FROM sprueche";
		$todo = mysql_query($sql);

		while($daten = mysql_fetch_array($todo))
		{
			$draw->setFontSize(rand(20,50));

			$spruch = $daten['spruch'];
			$userID = $daten['userID'];

			$x = rand(10,$width);
			$y = rand(10,$height);
			$drehung = rand(-50,50);

			$img->annotateImage($draw, $x, $y, $drehung, utf8_encode($spruch));

			updateCoords($userID, $x, $y);
		}

		$img->setImageFormat('jpg');

		$img->writeImage("tmp/img/map.jpg");

		
}

create();


Vielen Dank für die Hilfe.

Gruß
 
Hallo,
ich habe noch einiges gefunden was vielleicht hilfreich sein könnte.

Mit
PHP:
$metrics = $image->queryFontMetrics($draw, $text);

Damit bekommen ich die Größe des Textes zurück. Weis aber leider immer noch nicht weiter :-(

Vielen Dank,
Gruß

Shooter
 
Naja, das ist doch schon ganz gut dokumentirt auf php.net.
Die von dir gefundene Funktion wird dir auch sichherlich weiterhelfen: http://www.php.net/manual/en/imagick.queryfontmetrics.php

Der Trick ist es, diese Funktion jeweils vor dem eigentlichen Malen aufzurufen und die entsprechenden Daten 1.) mit vorher gespeicherten daten zu vergleiche, ob es keine Kollisionen gibt und 2.) die Daten zB in einem Array zu speichern. Beim ersten Schleifendurchlauf entfällt 1.) natürlich.
Die Daten die du dafür wahrscheinlic brauchst sind:
Code:
[boundingBox] => Array
        (
            [x1] => 0
            [y1] => -2
            [x2] => 6.890625
            [y2] => 7
        )

    [originX] => 70
    [originY] => 0

Achja, eifnach ist das nicht, aber auch nicht unlösbar ;)
 
Hallo,
vielen Dank für die Antwort. Ich komme einfach nicht auf die Lösung :-( . Wie würdest du das denn umsetzen?
 
Ich kann das auch nicht aus dem FF ;)

probier mal die ausgabe von fontquerymetrics bei jedem Schleifendurchlauf zu speichern, also vor der schleife einmal $metrics = array(); schreiben und dann in jedem schleifendurchlauf: $metrics[] = $imagick->queryFontMetrics( ... hier dann noch die entsprechenden Paramter rein ...), zusätzlich muss bei der natürlich noch die drehung gespeichert werden (das macht den Algorithmus noch deutlich komplexer, eigentloch fällt mir dazu garkeine lösung ein, da musste wohl mal nen mathematiker dann drannlassen)....
 
Hallo,
das speichern der Daten ist kein Problem(array). Nur eben die Berechnung ist der Knackpunkt. Kennst du vielleicht ein Forum wo mir da weitergeholfen werden könnte?

Vielen Dank
Gruß
Shooter
 
Nehmen wir mal an, dass wir die korrekten Punkte des Rechtecks (ich sage in Zukunft Polygon, weil die Lösung allgemeingültig sein dürfte) ermitteln können. (Zur Not per Rotationsmatrix die Drehung selbst nachberechnen. Lässt sich sicher fertig für PHP finden und ist ansonsten nicht so schwierig, wie es bei Wikipedia aussieht.)

Dann müssen wir noch das hier machen:

- http://stackoverflow.com/questions/10635040/how-to-detect-overlapping-polygons (bitte anklicken, die Links aus der Antwort habe ich nicht mitzitiert)

That said, if by polygons overlapping you mean at least one point of one is inside the other, you can test each polygon's point against the others by either looking at the point in point polygon problem or checking each polygons lines to see if it cuts across another polygon.

…und zwar – meines Erachtens – beides, denn ein Polygon könnte vollständig innerhalb eines anderen liegen (dann gibt es keine Linienschnittpunkte) oder ein Polygon könnte ein anderes schneiden, ohne dass ein Punkt des Polygons innerhalb des anderen liegen muss. (Beispiel: Pluszeichen, das aus zwei Rechtecken gebildet wird.)

Code, um zu prüfen, ob sich zwei Strecken/Kanten schneiden:

- http://stackoverflow.com/questions/...ine-intersects-a-polygon-in-c/1120126#1120126 (habe ihn nicht weiter angeguckt, aber scheint nach den Kommentaren zu urteilen zu klappen)

Dann die Funktion für jede Kante von A mit jeder Kante von B aufrufen, bis ein Schnittpunkt gefunden ist oder bis alle Kanten getestet wurden.

Zur anderen Sache habe ich eben das hier gebastelt:

PHP:
<?php

class Point
{
    public $x;
    public $y;

    public function __construct($x, $y)
    {
        $this->x = $x;
        $this->y = $y;
    }
}

// Copyright 2000 softSurfer, 2012 Dan Sunday
// This code may be freely used and modified for any purpose
// providing that this copyright notice is included with it.
// SoftSurfer makes no warranty for this code, and cannot be held
// liable for any real or imagined damage resulting from its use.
// Users of this code must verify correctness for their application.

// Source: http://geomalgorithms.com/a03-_inclusion.html (ported to PHP)

// cn_PnPoly(): crossing number test for a point in a polygon
//      Input:   P = a point,
//               V[] = vertex points of a polygon V[n+1] with V[n]=V[0]
//      Return:  0 = outside, 1 = inside
// This code is patterned after [Franklin, 2000]
function cn_PnPoly( Point $P, array $V)
{
    $V[] = $V[0]; // Liste von Punkten schließen
    $n = count($V) - 1;
    $cn = 0;    // the  crossing number counter

    // loop through all edges of the polygon
    for ($i=0; $i<$n; $i++) {    // edge from V[i]  to V[i+1]
       if ((($V[$i]->y <= $P->y) && ($V[$i+1]->y > $P->y))     // an upward crossing
        || (($V[$i]->y > $P->y) && ($V[$i+1]->y <=  $P->y))) { // a downward crossing
            // compute  the actual edge-ray intersect x-coordinate
            $vt = (float)($P->y  - $V[$i]->y) / ($V[$i+1]->y - $V[$i]->y);
            if ($P->x <  $V[$i]->x + $vt * ($V[$i+1]->x - $V[$i]->x)) // P->x < intersect
                 ++$cn;   // a valid crossing of y=P->y right of P->x
        }
    }
    return ($cn&1);    // 0 if even (out), and 1 if  odd (in)
}

function test(array $poly1, array $poly2)
{
    // Testen, ob ein Punkt von $poly1 in $poly2 liegt:
    foreach ($poly1 as $point) {
        if (cn_PnPoly($point, $poly2)) {
            //printf("Punkt (%s,%s) liegt im Polygon\n", $point->x, $point->y);
            return true;
        }
    }

    return false;
}

$poly1 = array(
    new Point(0, 0),
    new Point(0, 5),
    new Point(5, 5),
    new Point(5, 0)
);

$poly2 = array(
    new Point(4, 0),
    new Point(6, 5),
    new Point(11, 5),
    new Point(11, 0)
);

var_dump(test($poly1, $poly2));

Das ist die im Code verlinkte Funktion mit etwas Test-Infrastruktur drumrum.



Das ist insgesamt eine ganz spannende Angelegenheit, aber eine Frage bleibt: Was passiert, wenn eine Überlappung vorliegt?

Je nach genauer Anwendung landet man da in übelsten Problemen der kombinatorischen Optimierung. Falls es letztlich darum geht, Polygone möglichst platzsparend in eine endliche Fläche einzupassen, sage ich schon mal vorsichtig: forget about it.

Was geht, ist: „Probier x-mal, das Polygon woanders zu platzieren und breche ab, falls das in der Anzahl der Versuche nicht klappt.“
 
Zuletzt bearbeitet:
Beispielausgabe: http://www.ld-host.de/uploads/images/ac5a75c28e5f6c44dd92451c741d158e.jpg

PHP:
<?php
/**
 * Author: Marc Ermshaus <marc@ermshaus.org>
 * License: Free to use (at own risk).
 */

/**
 *
 */
interface MatrixInterface
{
    public function get($row, $column);
    public function set($row, $column, $value);
    public function getColumnCount();
    public function getRowCount();
    public function multiply(MatrixInterface $matrix);
}

/**
 *
 */
class MatrixFactory
{
    /**
     *
     * @param float $dx
     * @param float $dy
     * @return Matrix
     */
    public function createTranslationMatrix($dx, $dy)
    {
        $m = new Matrix(3, 3);

        $m->set(0, 0, 1); $m->set(0, 1, 0); $m->set(0, 2, $dx);
        $m->set(1, 0, 0); $m->set(1, 1, 1); $m->set(1, 2, $dy);
        $m->set(2, 0, 0); $m->set(2, 1, 0); $m->set(2, 2, 1);

        return $m;
    }

    /**
     *
     * @param float $radiant
     * @param Vector2D $rotationCenter
     * @return Matrix
     */
    public function createRotationMatrix($radiant, Vector2D $rotationCenter)
    {
        $m = $this->createTranslationMatrix($rotationCenter->getX(), $rotationCenter->getY());

        $n = new Matrix(3, 3);

        $n->set(0, 0,  cos($radiant));
        $n->set(0, 1, -sin($radiant));
        $n->set(0, 2,              0);

        $n->set(1, 0,  sin($radiant));
        $n->set(1, 1,  cos($radiant));
        $n->set(1, 2,              0);

        $n->set(2, 0,              0);
        $n->set(2, 1,              0);
        $n->set(2, 2,              1);

        $o = $this->createTranslationMatrix(-$rotationCenter->getX(), -$rotationCenter->getY());

        return $m->multiply($n)->multiply($o);
    }
}

/**
 *
 */
class Matrix implements MatrixInterface
{
    protected $rows;
    protected $columns;
    protected $data;

    public function __construct($rows, $columns)
    {
        $this->rows    = $rows;
        $this->columns = $columns;

        $this->data = array_fill(0, $rows, array_fill(0, $columns, 0));
    }

    public function set($row, $column, $value)
    {
        $this->data[$row][$column] = $value;
    }

    public function get($row, $column)
    {
        if (!isset($this->data[$row][$column])) {
            throw new Exception();
        }

        return $this->data[$row][$column];
    }

    public function getRowCount()
    {
        return $this->rows;
    }

    public function getColumnCount()
    {
        return $this->columns;
    }

    public function multiply(MatrixInterface $matrix)
    {
        if ($this->columns !== $matrix->getRowCount()) {
            throw new Exception();
        }

        $rows    = $matrix->getRowCount();
        $columns = $matrix->getColumnCount();

        $resultMatrix = new Matrix($rows, $columns);

        for ($row = 0; $row < $rows; $row++) {
            for ($column = 0; $column < $columns; $column++) {
                $sum = 0;
                for ($k = 0; $k < $rows; $k++) {
                    $sum += $this->get($row, $k) * $matrix->get($k, $column);
                }
                $resultMatrix->set($row, $column, $sum);
            }
        }

        return $resultMatrix;
    }
}

/**
 *
 */
class Vector2D
{
    protected $x;
    protected $y;

    public function __construct($x, $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function getX()
    {
        return $this->x;
    }

    public function setX($x)
    {
        $this->x = $x;
    }

    public function getY()
    {
        return $this->y;
    }

    public function setY($y)
    {
        $this->y = $y;
    }

    public function transform(MatrixInterface $matrix)
    {
        $res = $matrix->multiply($this->convertToMatrix());
        $this->x = $res->get(0, 0);
        $this->y = $res->get(1, 0);
    }

    public function convertToMatrix()
    {
        $matrix = new Matrix(3, 1);
        $matrix->set(0, 0, $this->x);
        $matrix->set(1, 0, $this->y);
        $matrix->set(2, 0, 1);

        return $matrix;
    }
}

/**
 *
 */
class TextDrawer
{
    /**
     *
     * @var MatrixFactory
     */
    protected $matrixFactory;

    /**
     *
     * @param MatrixFactory $matrixFactory
     */
    public function __construct(MatrixFactory $matrixFactory, Imagick $img)
    {
        $this->matrixFactory = $matrixFactory;
        $this->img = $img;
    }

    /**
     * Crossing number test for a point in a polygon
     *
     * This code is patterned after Franklin
     * <http://www.ecse.rpi.edu/Homepages/wrf/research/geom/pnpoly.html>.
     *
     * Copyright 2000 softSurfer, 2012 Dan Sunday
     *
     * This code may be freely used and modified for any purpose providing that
     * this copyright notice is included with it.SoftSurfer makes no warranty
     * for this code, and cannot be held liable for any real or imagined damage
     * resulting from its use. Users of this code must verify correctness for
     * their application.
     *
     * Source: <http://geomalgorithms.com/a03-_inclusion.html>
     * (Ported to PHP by Marc Ermshaus.)
     *
     *
     * @param Vector2D $P A point
     * @param array $V Vertex points of a polygon V[n+1] with V[n]=V[0]
     * @return int 0 = outside, 1 = inside
     */
    protected function cn_PnPoly(Vector2D $P, array $V)
    {
        $n = count($V) - 1;
        $cn = 0;    // the  crossing number counter

        // loop through all edges of the polygon
        for ($i=0; $i<$n; $i++) {    // edge from V[i]  to V[i+1]
           if ((($V[$i]->getY() <= $P->getY()) && ($V[$i+1]->getY() > $P->getY()))     // an upward crossing
            || (($V[$i]->getY() > $P->getY()) && ($V[$i+1]->getY() <=  $P->getY()))) { // a downward crossing
                // compute  the actual edge-ray intersect x-coordinate
                $vt = (float)($P->getY()  - $V[$i]->getY()) / ($V[$i+1]->getY() - $V[$i]->getY());
                if ($P->getX() <  $V[$i]->getX() + $vt * ($V[$i+1]->getX() - $V[$i]->getX())) // P->getX() < intersect
                     ++$cn;   // a valid crossing of y=P->getY() right of P->getX()
            }
        }

        return ($cn&1);    // 0 if even (out), and 1 if  odd (in)
    }

    /**
     *
     *
     * Source: <http://stackoverflow.com/questions/1119451/how-to-tell-if-a-line-intersects-a-polygon-in-c/1120126#1120126>
     *
     * @param Vector2D $start1
     * @param Vector2D $end1
     * @param Vector2D $start2
     * @param Vector2D $end2
     * @return null|Vector2D
     */
    protected function findLineIntersection(Vector2D $start1, Vector2D $end1, Vector2D $start2, Vector2D $end2)
    {
        $denom = (($end1->getX() - $start1->getX()) * ($end2->getY() - $start2->getY())) - (($end1->getY() - $start1->getY()) * ($end2->getX() - $start2->getX()));

        //  AB & CD are parallel
        if ($denom == 0) {
            return null;
        }

        $numer = (($start1->getY() - $start2->getY()) * ($end2->getX() - $start2->getX())) - (($start1->getX() - $start2->getX()) * ($end2->getY() - $start2->getY()));

        $r = $numer / $denom;

        $numer2 = (($start1->getY() - $start2->getY()) * ($end1->getX() - $start1->getX())) - (($start1->getX() - $start2->getX()) * ($end1->getY() - $start1->getY()));

        $s = $numer2 / $denom;

        if (($r < 0 || $r > 1) || ($s < 0 || $s > 1)) {
            return null;
        }

        // Find intersection point
        $result = new Vector2D(0, 0);

        $result->setX($start1->getX() + ($r * ($end1->getX() - $start1->getX())));
        $result->setY($start1->getY() + ($r * ($end1->getY() - $start1->getY())));

        return $result;
    }

    /**
     *
     * @param array $points Array of Vector2D
     * @return array
     */
    protected function bigBoundingBox(array $points = array())
    {
        $x_min = null;
        $y_min = null;
        $x_max = null;
        $y_max = null;

        foreach ($points as $point) {
            $x = $point->getX();
            $y = $point->getY();

            if ($x_min === null || $x < $x_min) {
                $x_min = $x;
            }
            if ($x_max === null || $x > $x_max) {
                $x_max = $x;
            }
            if ($y_min === null || $y < $y_min) {
                $y_min = $y;
            }
            if ($y_max === null || $y > $y_max) {
                $y_max = $y;
            }
        }

        return array($x_min, $y_min, $x_max, $y_max);
    }

    /**
     *
     * @param Imagick $img
     * @param ImagickDraw $draw
     * @param int $width
     * @param int $height
     * @param string $text
     * @return array
     */
    private function macgyverSomeStuff(ImagickDraw $draw, $text, $angleRange, $fontRange)
    {
        $fontSize = rand($fontRange[0], $fontRange[1]);
        $x        = rand(0, $this->img->getImageWidth());
        $y        = rand(0, $this->img->getImageHeight());
        $rotation = mt_rand($angleRange[0], $angleRange[1]);

        $draw->setFontSize($fontSize);
        $fontMetrics = $this->img->queryFontMetrics($draw, $text);

        // Rotation um ($x, $y)
        $m = $this->matrixFactory->createRotationMatrix(deg2rad($rotation), new Vector2D($x, $y));

        $a = new Vector2D($x, $y);
        $b = new Vector2D($x + $fontMetrics['textWidth'], $y);
        $c = new Vector2D($x + $fontMetrics['textWidth'], $y + $fontMetrics['textHeight']);
        $d = new Vector2D($x, $y + $fontMetrics['textHeight']);

        $a->transform($m);
        $b->transform($m);
        $c->transform($m);
        $d->transform($m);

        // $x und $y so anpassen, dass Ausgabe komplett im sichtbaren Bereich
        // liegt

        list($x0, $y0, $x1, $y1) = $this->bigBoundingBox(array($a, $b, $c, $d));

        $dx = 0.0;
        $dy = 0.0;

        if ($x0 < 0) {
            $dx = -$x0;
        }

        if ($y0 < 0) {
            $dy = -$y0;
        }

        if ($x1 > $this->img->getImageWidth()) {
            $dx = $this->img->getImageWidth() - $x1;
        }

        if ($y1 > $this->img->getImageHeight()) {
            $dy = $this->img->getImageHeight() - $y1;
        }

        $n = $this->matrixFactory->createTranslationMatrix($dx, $dy);

        $a->transform($n);
        $b->transform($n);
        $c->transform($n);
        $d->transform($n);

        $x += $dx;
        $y += $dy;

        return array($x, $y, $fontSize, $rotation, array($a, $b, $c, $d, $a));
    }

    /**
     *
     * @param type $polygons
     * @param type $test
     * @return boolean
     */
    protected function isOverlap($polygons, $test)
    {
        // Testen, ob ein Punkt von $poly1 in $poly2 liegt:
        foreach ($polygons as $poly) {
            foreach ($test as $point) {
                if ($this->cn_PnPoly($point, $poly)) {
                    return true;
                }
            }

            // Umgekehrt
            foreach ($poly as $point) {
                if ($this->cn_PnPoly($point, $test)) {
                    return true;
                }
            }
        }

        foreach ($polygons as $polygon) {
            $c_poly = count($polygon);
            $c_test = count($test);

            // Für jede Kante aus $polygon...
            for ($i = 0; $i < $c_poly - 1; $i++) {
                // ...mit jeder Kante aus $test...
                for ($j = 0; $j < $c_test - 1; $j++) {
                    $res = $this->findLineIntersection($polygon[$i], $polygon[$i+1], $test[$j], $test[$j+1]);

                    if ($res !== null) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    /**
     *
     * @param array $data
     */
    public function positionTexts($data, array $angleRange, array $fontRange)
    {
        $draw = new ImagickDraw();
        $draw->setGravity(Imagick::GRAVITY_NORTHWEST);

        $polygons = array();

        $polygonCounter = 0;

        foreach ($data as $entry) {
            do {
                list($x, $y, $fontSize, $rotation, $polygon)
                    = $this->macgyverSomeStuff($draw, $entry, $angleRange, $fontRange);
                $polygonCounter++;
            } while ($this->isOverlap($polygons, $polygon));

            // ---------------------------------------------------------------------

            $draw->setStrokeColor(new ImagickPixel('green'));
            $draw->setFillOpacity(0.0);

            // Draw bounding box for texts

            $c = count($polygon);

            for ($i = 0; $i < $c - 1; $i++) {
                $draw->line(
                    $polygon[$i]->getX(), $polygon[$i]->getY(),
                    $polygon[$i + 1]->getX(), $polygon[$i + 1]->getY()
                );
            }

            // ---

            $polygons[] = $polygon;

            $draw->setStrokeColor('black');
            $draw->setFontSize($fontSize);
            $draw->setFillColor('black');
            $draw->setFillOpacity(1.0);
            $draw->setStrokeOpacity(1.0);
            $this->img->annotateImage($draw, $x, $y, $rotation, $entry);
        }

        $draw->setFontSize(10);
        $draw->setStrokeOpacity(0.0);
        $draw->annotation(0, 0, 'Verworfene Polygone: ' . ($polygonCounter - count($data)));

        $this->img->setImageFormat('jpg');
        $this->img->drawimage($draw);
    }
}



error_reporting(-1);
ini_set('display_errors', 1);

$texts = array(
    'Borussia Dortmund',
    'FC Bayern München',
    'Hamburger SV',
    'FC Schalke 04',
    'FC Augsburg',
    'VfB Stuttgart',
    'Werder Bremen',
    'Hannover 96',
    'Bayer Leverkusen'
);

$img = new Imagick();
$img->newImage(640, 480, new ImagickPixel('white'));

$textDrawer = new TextDrawer(new MatrixFactory(), $img);

$textDrawer->positionTexts($texts, array(-50, 50), array(30, 60));

header('Content-Type: image/jpeg');
echo $img;
 

Neue Beiträge

Zurück