src/Bitverse/Identicon/Generator/RingsGenerator.php
<?php
namespace Bitverse\Identicon\Generator;
use Bitverse\Identicon\Color\Color;
use Bitverse\Identicon\SVG\Svg;
use Bitverse\Identicon\SVG\Circle;
use Bitverse\Identicon\SVG\Path;
class RingsGenerator extends BaseGenerator
{
/**
* @var integer
*/
private $sideLength = 1000;
/**
* {@inheritDoc}
*/
public function generate($hash)
{
$svg = (new Svg($this->sideLength, $this->sideLength))->addChild(
$this->getBackground()
);
if ($this->showCenter($hash)) {
$svg->addChild($this->getCenter($this->getColor($hash)));
}
$fgColor = $this->getForegroundColor();
if (!$fgColor) {
$fgColor = $this->getColor($hash);
}
for ($i=1; $i<4; ++$i) {
$svg->addChild(
$this->getArc(
$fgColor,
$this->getX(),
$this->getY(),
$this->getRingRadius($i),
$this->getRingAngle($i, $hash),
$this->getRingWidth(),
$this->getRingRotation($i, $hash)
)
);
}
return (string) $svg;
}
/**
* Returns the background for the image.
*
* @return SvgNode
*/
private function getBackground()
{
return (new Circle($this->getX(), $this->getY(), $this->getRadius()))
->setFillColor($this->getBackgroundColor())
->setStrokeWidth(0);
}
/**
* Returns the center dot for the image.
*
* @param Color $color Color for the dot.
*
* @return SvgNode
*/
private function getCenter(Color $color)
{
return (new Circle($this->getX(), $this->getY(), $this->getCenterRadius()))
->setFillColor($color)
->setStrokeWidth(0);
}
/**
* Returns an arc drawn according to the passed specification.
*
* @param float $radius Radius of the arc.
* @param float $angle Angle of the arc.
* @param float $start Starting angle for the arc.
*
* @return SvgNode
*/
private function getArc(Color $color, $x, $y, $radius, $angle, $width, $start = 0)
{
return (new Path(
$x + $radius * cos(deg2rad($start)),
$y + $radius * sin(deg2rad($start))
))
->setFillColor($color)
->setStrokeColor($color)
->setStrokeWidth(1)
->arcTo(
$x + $radius * cos(deg2rad($start + $angle)),
$y + $radius * sin(deg2rad($start + $angle)),
$radius,
$radius,
0,
$angle > 180,
1
)
->lineTo(
$x + ($radius + $width) * cos(deg2rad($start + $angle)),
$y + ($radius + $width) * sin(deg2rad($start + $angle))
)
->arcTo(
$x + ($radius + $width) * cos(deg2rad($start)),
$y + ($radius + $width) * sin(deg2rad($start)),
$radius + $width,
$radius + $width,
0,
$angle > 180,
0
)
->lineTo(
$x + $radius * cos(deg2rad($start)),
$y + $radius * sin(deg2rad($start))
);
}
/**
* @return float
*/
private function getRadius()
{
return $this->sideLength / 2;
}
/**
* @return float
*/
private function getX()
{
return $this->getRadius();
}
/**
* @return float
*/
private function getY()
{
return $this->getRadius();
}
/**
* @return float
*/
private function getMultiplier()
{
return $this->sideLength / 1000;
}
/**
* @return float
*/
private function getCenterRadius()
{
return 125 * $this->getMultiplier();
}
/**
* Returns the inner radius for a ring
*
* @param integer $ring Ring number.
*
* @return float
*/
private function getRingRadius($ring)
{
return $ring * 120 * $this->getMultiplier();
}
/**
* Returns the angle in degrees for the given ring, based on the hash.
*
* @param integer $ring
* @param string $hash
*
* @return integer
*/
private function getRingAngle($ring, $hash)
{
return 10 * pow(2, 3 - $ring) * array_reduce(
str_split($hash, pow(2, 3 - $ring)),
function ($total, $substr) {
return $total + (hexdec($substr) % 2);
},
0
);
}
/**
* Returns the ring width.
*
* @return float
*/
private function getRingWidth()
{
return 125 * $this->getMultiplier();
}
/**
* @param integer $ring
* @param string $hash
*
* @return integer
*/
private function getRingRotation($ring, $hash)
{
return 36 * array_reduce(
str_split(substr($hash, 0, 30), 3),
function ($total, $substr) use ($ring) {
return $total + (hexdec($substr[$ring - 1]) % 2);
},
0
);
}
/**
* Determines whether the center dot should be displayed.
*
* @param string $hash
*
* @return boolean
*/
public function showCenter($hash)
{
return hexdec(substr($hash, 24, 8)) % 2 === 0;
}
}