add new Jdenticon comment icon library, set it as default, fixes #793

This commit is contained in:
El RIDO 2022-10-26 06:53:56 +02:00
parent 78aa70e3ab
commit 8ac69590cf
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
50 changed files with 6996 additions and 401 deletions

View file

@ -67,8 +67,9 @@ languageselection = false
; (optional) IP based icons are a weak mechanism to detect if a comment was from
; a different user when the same username was used in a comment. It might be
; used to get the IP of a non anonymous comment poster if the server salt is
; leaked and a SHA256 HMAC rainbow table is generated for all (relevant) IPs.
; Can be set to one these values: "none" / "vizhash" / "identicon" (default).
; leaked and a SHA512 HMAC rainbow table is generated for all (relevant) IPs.
; Can be set to one these values:
; "none" / "vizhash" / "identicon" / "jdenticon" (default).
; icon = "none"
; Content Security Policy headers allow a website to restrict what sources are

View file

@ -27,7 +27,8 @@
"php" : "^5.6.0 || ^7.0 || ^8.0",
"paragonie/random_compat" : "2.0.21",
"yzalis/identicon" : "2.0.0",
"mlocati/ip-lib" : "1.18.0"
"mlocati/ip-lib" : "1.18.0",
"jdenticon/jdenticon": "^1.0"
},
"suggest" : {
"google/cloud-storage" : "1.26.1",

695
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -53,7 +53,7 @@ class Configuration
'languagedefault' => '',
'urlshortener' => '',
'qrcode' => true,
'icon' => 'identicon',
'icon' => 'jdenticon',
'cspheader' => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
'zerobincompatibility' => false,
'httpwarning' => true,

View file

@ -14,6 +14,7 @@ namespace PrivateBin\Model;
use Exception;
use Identicon\Identicon;
use Jdenticon\Identicon as Jdenticon;
use PrivateBin\Persistence\TrafficLimiter;
use PrivateBin\Vizhash16x16;
@ -164,7 +165,17 @@ class Comment extends AbstractModel
if ($icon != 'none') {
$pngdata = '';
$hmac = TrafficLimiter::getHash();
if ($icon == 'identicon') {
if ($icon == 'jdenticon') {
$jdenticon = new Jdenticon(array(
'hash' => $hmac,
'size' => 16,
'style' => array(
'backgroundColor' => '#fff0', // fully transparent, for dark mode
'padding' => 0,
),
));
$pngdata = $jdenticon->getImageDataUri('png');
} elseif ($icon == 'identicon') {
$identicon = new Identicon();
$pngdata = $identicon->getImageDataUri($hmac, 16);
} elseif ($icon == 'vizhash') {

View file

@ -1,6 +1,6 @@
<?php
use Identicon\Identicon;
use Jdenticon\Identicon;
use PrivateBin\Configuration;
use PrivateBin\Data\Database;
use PrivateBin\Model;
@ -314,8 +314,15 @@ class ModelTest extends PHPUnit_Framework_TestCase
$comment->get();
$comment->store();
$identicon = new Identicon();
$pngdata = $identicon->getImageDataUri(TrafficLimiter::getHash(), 16);
$identicon = new Identicon(array(
'hash' => TrafficLimiter::getHash(),
'size' => 16,
'style' => array(
'backgroundColor' => '#fff0', // fully transparent, for dark mode
'padding' => 0,
),
));
$pngdata = $identicon->getImageDataUri('png');
$comment = current($this->_model->getPaste(Helper::getPasteId())->get()['comments']);
$this->assertEquals($pngdata, $comment['meta']['icon'], 'icon gets set');
}

View file

@ -37,57 +37,130 @@ namespace Composer\Autoload;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-return array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
@ -103,8 +176,10 @@ class ClassLoader
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
@ -148,10 +223,12 @@ class ClassLoader
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
@ -196,7 +273,9 @@ class ClassLoader
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
@ -212,9 +291,11 @@ class ClassLoader
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
@ -234,6 +315,8 @@ class ClassLoader
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
@ -256,6 +339,8 @@ class ClassLoader
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
@ -276,6 +361,8 @@ class ClassLoader
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
@ -296,25 +383,44 @@ class ClassLoader
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
@ -323,6 +429,8 @@ class ClassLoader
return true;
}
return null;
}
/**
@ -367,6 +475,21 @@ class ClassLoader
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
@ -438,6 +561,10 @@ class ClassLoader
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{

350
vendor/composer/InstalledVersions.php vendored Normal file
View file

@ -0,0 +1,350 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

View file

@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php',
'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php',
'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php',
@ -28,12 +29,49 @@ return array(
'Identicon\\Generator\\ImageMagickGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/ImageMagickGenerator.php',
'Identicon\\Generator\\SvgGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/SvgGenerator.php',
'Identicon\\Identicon' => $vendorDir . '/yzalis/identicon/src/Identicon/Identicon.php',
'Jdenticon\\Canvas\\Canvas' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Canvas.php',
'Jdenticon\\Canvas\\CanvasContext' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/CanvasContext.php',
'Jdenticon\\Canvas\\ColorUtils' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/ColorUtils.php',
'Jdenticon\\Canvas\\Matrix' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Matrix.php',
'Jdenticon\\Canvas\\Png\\PngBuffer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngBuffer.php',
'Jdenticon\\Canvas\\Png\\PngEncoder' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngEncoder.php',
'Jdenticon\\Canvas\\Png\\PngPalette' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngPalette.php',
'Jdenticon\\Canvas\\Point' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Point.php',
'Jdenticon\\Canvas\\Rasterization\\Edge' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Edge.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeIntersection' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeIntersection.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeSuperSampleIntersection' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeSuperSampleIntersection.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeTable' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeTable.php',
'Jdenticon\\Canvas\\Rasterization\\Layer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Layer.php',
'Jdenticon\\Canvas\\Rasterization\\LayerManager' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/LayerManager.php',
'Jdenticon\\Canvas\\Rasterization\\Rasterizer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php',
'Jdenticon\\Canvas\\Rasterization\\SuperSampleBuffer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php',
'Jdenticon\\Canvas\\Rasterization\\SuperSampleRange' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleRange.php',
'Jdenticon\\Color' => $vendorDir . '/jdenticon/jdenticon/src/Color.php',
'Jdenticon\\Identicon' => $vendorDir . '/jdenticon/jdenticon/src/Identicon.php',
'Jdenticon\\IdenticonStyle' => $vendorDir . '/jdenticon/jdenticon/src/IdenticonStyle.php',
'Jdenticon\\Rendering\\AbstractRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/AbstractRenderer.php',
'Jdenticon\\Rendering\\ColorTheme' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/ColorTheme.php',
'Jdenticon\\Rendering\\IconGenerator' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/IconGenerator.php',
'Jdenticon\\Rendering\\ImagickRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php',
'Jdenticon\\Rendering\\InternalPngRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php',
'Jdenticon\\Rendering\\Point' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Point.php',
'Jdenticon\\Rendering\\Rectangle' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Rectangle.php',
'Jdenticon\\Rendering\\RendererInterface' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/RendererInterface.php',
'Jdenticon\\Rendering\\SvgPath' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/SvgPath.php',
'Jdenticon\\Rendering\\SvgRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/SvgRenderer.php',
'Jdenticon\\Rendering\\Transform' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Transform.php',
'Jdenticon\\Rendering\\TriangleDirection' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/TriangleDirection.php',
'Jdenticon\\Shapes\\Shape' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/Shape.php',
'Jdenticon\\Shapes\\ShapeCategory' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapeCategory.php',
'Jdenticon\\Shapes\\ShapeDefinitions' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php',
'Jdenticon\\Shapes\\ShapePosition' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapePosition.php',
'PrivateBin\\Configuration' => $baseDir . '/lib/Configuration.php',
'PrivateBin\\Controller' => $baseDir . '/lib/Controller.php',
'PrivateBin\\Data\\AbstractData' => $baseDir . '/lib/Data/AbstractData.php',
'PrivateBin\\Data\\Database' => $baseDir . '/lib/Data/Database.php',
'PrivateBin\\Data\\Filesystem' => $baseDir . '/lib/Data/Filesystem.php',
'PrivateBin\\Data\\GoogleCloudStorage' => $baseDir . '/lib/Data/GoogleCloudStorage.php',
'PrivateBin\\Data\\S3Storage' => $baseDir . '/lib/Data/S3Storage.php',
'PrivateBin\\Filter' => $baseDir . '/lib/Filter.php',
'PrivateBin\\FormatV2' => $baseDir . '/lib/FormatV2.php',
'PrivateBin\\I18n' => $baseDir . '/lib/I18n.php',
@ -49,4 +87,5 @@ return array(
'PrivateBin\\Request' => $baseDir . '/lib/Request.php',
'PrivateBin\\View' => $baseDir . '/lib/View.php',
'PrivateBin\\Vizhash16x16' => $baseDir . '/lib/Vizhash16x16.php',
'PrivateBin\\YourlsProxy' => $baseDir . '/lib/YourlsProxy.php',
);

View file

@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
return array(
'PrivateBin\\' => array($baseDir . '/lib'),
'Jdenticon\\' => array($vendorDir . '/jdenticon/jdenticon/src'),
'Identicon\\' => array($vendorDir . '/yzalis/identicon/src/Identicon'),
'IPLib\\' => array($vendorDir . '/mlocati/ip-lib/src'),
);

View file

@ -22,13 +22,15 @@ class ComposerAutoloaderInitDontChange
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitDontChange', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitDontChange', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitDontChange::getInitializer($loader));
} else {
@ -63,11 +65,16 @@ class ComposerAutoloaderInitDontChange
}
}
/**
* @param string $fileIdentifier
* @param string $file
* @return void
*/
function composerRequireDontChange($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}

View file

@ -15,6 +15,10 @@ class ComposerStaticInitDontChange
array (
'PrivateBin\\' => 11,
),
'J' =>
array (
'Jdenticon\\' => 10,
),
'I' =>
array (
'Identicon\\' => 10,
@ -27,6 +31,10 @@ class ComposerStaticInitDontChange
array (
0 => __DIR__ . '/../..' . '/lib',
),
'Jdenticon\\' =>
array (
0 => __DIR__ . '/..' . '/jdenticon/jdenticon/src',
),
'Identicon\\' =>
array (
0 => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon',
@ -38,6 +46,7 @@ class ComposerStaticInitDontChange
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php',
'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php',
'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php',
@ -60,12 +69,49 @@ class ComposerStaticInitDontChange
'Identicon\\Generator\\ImageMagickGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/ImageMagickGenerator.php',
'Identicon\\Generator\\SvgGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/SvgGenerator.php',
'Identicon\\Identicon' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Identicon.php',
'Jdenticon\\Canvas\\Canvas' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Canvas.php',
'Jdenticon\\Canvas\\CanvasContext' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/CanvasContext.php',
'Jdenticon\\Canvas\\ColorUtils' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/ColorUtils.php',
'Jdenticon\\Canvas\\Matrix' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Matrix.php',
'Jdenticon\\Canvas\\Png\\PngBuffer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngBuffer.php',
'Jdenticon\\Canvas\\Png\\PngEncoder' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngEncoder.php',
'Jdenticon\\Canvas\\Png\\PngPalette' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngPalette.php',
'Jdenticon\\Canvas\\Point' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Point.php',
'Jdenticon\\Canvas\\Rasterization\\Edge' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Edge.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeIntersection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeIntersection.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeSuperSampleIntersection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeSuperSampleIntersection.php',
'Jdenticon\\Canvas\\Rasterization\\EdgeTable' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeTable.php',
'Jdenticon\\Canvas\\Rasterization\\Layer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Layer.php',
'Jdenticon\\Canvas\\Rasterization\\LayerManager' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/LayerManager.php',
'Jdenticon\\Canvas\\Rasterization\\Rasterizer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php',
'Jdenticon\\Canvas\\Rasterization\\SuperSampleBuffer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php',
'Jdenticon\\Canvas\\Rasterization\\SuperSampleRange' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleRange.php',
'Jdenticon\\Color' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Color.php',
'Jdenticon\\Identicon' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Identicon.php',
'Jdenticon\\IdenticonStyle' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/IdenticonStyle.php',
'Jdenticon\\Rendering\\AbstractRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/AbstractRenderer.php',
'Jdenticon\\Rendering\\ColorTheme' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/ColorTheme.php',
'Jdenticon\\Rendering\\IconGenerator' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/IconGenerator.php',
'Jdenticon\\Rendering\\ImagickRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php',
'Jdenticon\\Rendering\\InternalPngRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php',
'Jdenticon\\Rendering\\Point' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Point.php',
'Jdenticon\\Rendering\\Rectangle' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Rectangle.php',
'Jdenticon\\Rendering\\RendererInterface' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/RendererInterface.php',
'Jdenticon\\Rendering\\SvgPath' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/SvgPath.php',
'Jdenticon\\Rendering\\SvgRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/SvgRenderer.php',
'Jdenticon\\Rendering\\Transform' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Transform.php',
'Jdenticon\\Rendering\\TriangleDirection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/TriangleDirection.php',
'Jdenticon\\Shapes\\Shape' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/Shape.php',
'Jdenticon\\Shapes\\ShapeCategory' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapeCategory.php',
'Jdenticon\\Shapes\\ShapeDefinitions' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php',
'Jdenticon\\Shapes\\ShapePosition' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapePosition.php',
'PrivateBin\\Configuration' => __DIR__ . '/../..' . '/lib/Configuration.php',
'PrivateBin\\Controller' => __DIR__ . '/../..' . '/lib/Controller.php',
'PrivateBin\\Data\\AbstractData' => __DIR__ . '/../..' . '/lib/Data/AbstractData.php',
'PrivateBin\\Data\\Database' => __DIR__ . '/../..' . '/lib/Data/Database.php',
'PrivateBin\\Data\\Filesystem' => __DIR__ . '/../..' . '/lib/Data/Filesystem.php',
'PrivateBin\\Data\\GoogleCloudStorage' => __DIR__ . '/../..' . '/lib/Data/GoogleCloudStorage.php',
'PrivateBin\\Data\\S3Storage' => __DIR__ . '/../..' . '/lib/Data/S3Storage.php',
'PrivateBin\\Filter' => __DIR__ . '/../..' . '/lib/Filter.php',
'PrivateBin\\FormatV2' => __DIR__ . '/../..' . '/lib/FormatV2.php',
'PrivateBin\\I18n' => __DIR__ . '/../..' . '/lib/I18n.php',
@ -81,6 +127,7 @@ class ComposerStaticInitDontChange
'PrivateBin\\Request' => __DIR__ . '/../..' . '/lib/Request.php',
'PrivateBin\\View' => __DIR__ . '/../..' . '/lib/View.php',
'PrivateBin\\Vizhash16x16' => __DIR__ . '/../..' . '/lib/Vizhash16x16.php',
'PrivateBin\\YourlsProxy' => __DIR__ . '/../..' . '/lib/YourlsProxy.php',
);
public static function getInitializer(ClassLoader $loader)

59
vendor/composer/installed.php vendored Normal file
View file

@ -0,0 +1,59 @@
<?php return array(
'root' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '78aa70e3ab9277172489f9d88f8fd08cc1d03c97',
'name' => 'privatebin/privatebin',
'dev' => false,
),
'versions' => array(
'jdenticon/jdenticon' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../jdenticon/jdenticon',
'aliases' => array(),
'reference' => '994ee07293fb978f983393ffcb2c0250592a6ac4',
'dev_requirement' => false,
),
'mlocati/ip-lib' => array(
'pretty_version' => '1.18.0',
'version' => '1.18.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../mlocati/ip-lib',
'aliases' => array(),
'reference' => 'c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2',
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'pretty_version' => 'v2.0.21',
'version' => '2.0.21.0',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'reference' => '96c132c7f2f7bc3230723b66e89f8f150b29d5ae',
'dev_requirement' => false,
),
'privatebin/privatebin' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '78aa70e3ab9277172489f9d88f8fd08cc1d03c97',
'dev_requirement' => false,
),
'yzalis/identicon' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../yzalis/identicon',
'aliases' => array(),
'reference' => 'ff5ed090129cab9bfa2a322857d4a01d107aa0ae',
'dev_requirement' => false,
),
),
);

26
vendor/composer/platform_check.php vendored Normal file
View file

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 50600)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View file

@ -0,0 +1,130 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
use Jdenticon\Canvas\Rasterization\Edge;
use Jdenticon\Canvas\Rasterization\EdgeTable;
use Jdenticon\Canvas\Rasterization\Rasterizer;
use Jdenticon\Canvas\Png\PngPalette;
use Jdenticon\Canvas\Png\PngEncoder;
use Jdenticon\Canvas\CanvasContext;
use Jdenticon\Canvas\ColorUtils;
class Canvas
{
private $edges;
/**
* Creates a new canvas with the specified dimensions given in pixels.
*
* @param integer $width Canvas width in pixels.
* @param integer $height Canvas height in pixels.
*/
public function __construct($width, $height)
{
$this->width = $width;
$this->height = $height;
$this->edges = new EdgeTable($width, $height);
}
/**
* The width of the canvas in pixels.
*
* @var integer
*/
public $width = 0;
/**
* The height of the canvas in pixels.
*
* @var integer
*/
public $height = 0;
/**
* Specifies the background color. Allowed values are:
* - 32 bit integers on the format 0xRRGGBBAA
* - strings on the format #RGB
* - strings on the format #RRGGBB
* - strings on the format #RRGGBBAA
*
* @var integer|string
*/
public $backColor = 0x00000000;
/**
* Gets a context used to draw polygons on this canvas.
*
* @returns \Jdenticon\Canvas\CanvasContext
*/
public function getContext()
{
return new CanvasContext($this, $this->edges);
}
/**
* Renders the canvas as a PNG data stream.
*
* @param array $keywords Keywords to be written to the PNG stream.
* See https://www.w3.org/TR/PNG/#11keywords.
* @returns string
*/
public function toPng($keywords = array())
{
$colorRanges = array();
Rasterizer::rasterize(
$colorRanges, $this->edges,
$this->width, $this->height);
$backColor = ColorUtils::parse($this->backColor);
if (ColorUtils::alpha($backColor) > 0) {
$isColor = false;
foreach ($colorRanges as & $value) {
if ($isColor) {
$value = ColorUtils::over($value, $backColor);
$isColor = false;
} else {
$isColor = true;
}
}
unset($value);
}
$palette = new PngPalette($colorRanges);
$png = new PngEncoder();
$png->writeImageHeader($this->width, $this->height, $palette->isValid ?
PngEncoder::INDEXED_COLOR : PngEncoder::TRUE_COLOR_WITH_ALPHA);
$png->writeImageGamma();
foreach ($keywords as $key => $value) {
$png->writeTextualData($key, $value);
}
if ($palette && $palette->isValid) {
$png->writePalette($palette);
$png->writeTransparency($palette);
$png->writeIndexed($colorRanges, $palette,
$this->width, $this->height);
} else {
$png->writeTrueColorWithAlpha($colorRanges,
$this->width, $this->height);
}
$png->writeImageEnd();
return $png->getBuffer();
}
}

View file

@ -0,0 +1,408 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
use Jdenticon\Canvas\ColorUtils;
use Jdenticon\Canvas\CanvasState;
use Jdenticon\Canvas\Rasterization\EdgeTable;
use Jdenticon\Canvas\Rasterization\Edge;
use Jdenticon\Canvas\Matrix;
class CanvasContext
{
private $savedStates = array();
private $edges;
private $transform;
private $paths;
private $canvas;
/**
* Creates a new canvas with the specified dimensions given in pixels.
*
* @param \Jdenticon\Canvas\Canvas $canvas The owner canvas.
* @param array $edges The owner canvas' edge buffer.
*/
public function __construct($canvas, &$edges)
{
$this->edges = $edges;
$this->canvas = $canvas;
$this->beginPath();
$this->resetTransform();
}
/**
* Specifies the fill color that is used when the fill method is called. Allowed values are:
* - 32 bit integers on the format 0xRRGGBBAA
* - strings on the format #RGB
* - strings on the format #RRGGBB
* - strings on the format #RRGGBBAA
*
* @var integer|string
*/
public $fillStyle = 0x000000ff;
/**
* Saves the current state to the state stack.
*/
public function save()
{
array_push($this->savedStates, array(
'transform' => $this->transform,
'fillStyle' => $this->fillStyle
));
}
/**
* Restores the last saved state of the CanvasContext.
*/
public function restore()
{
$state = array_pop($this->savedStates);
if ($state != NULL) {
$this->transform = $state['transform'];
$this->fillStyle = $state['fillStyle'];
}
}
/**
* Resets the internal path buffer and begins a new path.
*/
public function resetTransform()
{
$this->transform = new Matrix(1, 0, 0, 1, 0, 0);
}
/**
* Multiplies the current transformation matrix with the specified values.
*/
public function transform($a, $b, $c, $d, $e, $f)
{
if (gettype($a) != 'integer' ||
gettype($b) != 'integer' ||
gettype($c) != 'integer' ||
gettype($d) != 'integer' ||
gettype($e) != 'integer' ||
gettype($f) != 'integer'
) {
return;
}
$this->transform = $this->transform->multiply($a, $b, $c, $d, $e, $f);
}
/**
* Sets the transformation matrix to the specified matrix.
*/
public function setTransform($a, $b, $c, $d, $e, $f)
{
if (gettype($a) != 'integer' ||
gettype($b) != 'integer' ||
gettype($c) != 'integer' ||
gettype($d) != 'integer' ||
gettype($e) != 'integer' ||
gettype($f) != 'integer'
) {
return;
}
$this->transform = new Matrix($a, $b, $c, $d, $e, $f);
}
/**
* Applies a translation transformation to the CanvasContext.
*
* @param float $x Distance to move in the horizontal direction in pixels.
* @param float $y Distance to move in the vertical direction in pixels.
*/
public function translate($x, $y)
{
$this->transform = $this->transform->translate($x, $y);
}
/**
* Applies a scale transformation to the CanvasContext.
*
* @param float $x Scale in the horizontal direction. 1 means no scale.
* @param float $y Scale in the vertical direction. 1 means no scale.
*/
public function scale($x, $y)
{
$this->transform = $this->transform->scale($x, $y);
}
/**
* Applies a rotation transformation to the canvas around its current origo.
*
* @param float $angle Angle in radians measured clockwise from the
* positive x axis.
*/
public function rotate($angle)
{
$this->transform = $this->transform->rotate($angle);
}
/**
* Removes all existing subpaths and begins a new path.
*/
public function beginPath()
{
$this->paths = array();
}
/**
* Starts a new subpath that begins in the same point as the start and end
* point of the previous one.
*/
public function closePath()
{
$pathsCount = count($this->paths);
if ($pathsCount > 0) {
$path = $this->paths[$pathsCount - 1];
$pathCount = count($path);
if ($pathCount > 2) {
// Close path
if ($path[0] != $path[$pathCount - 2] ||
$path[1] != $path[$pathCount - 1]
) {
$path[] = $path[0];
$path[] = $path[1];
}
// Begin a new path
$this->paths[] = array($path[0], $path[1]);
}
}
}
/**
* Begins a new subpath by moving the cursor to the specified position.
*
* @param float $x X coordinate.
* @param float $y Y coordinate.
*/
public function moveTo($x, $y)
{
$p = $this->transform->multiplyPoint($x, $y);
$this->paths[] = array($p->x, $p->y);
}
/**
* Inserts an edge between the last and specified position.
*
* @param float $x Target X coordinate.
* @param float $y Target Y coordinate.
* @public
*/
public function lineTo($x, $y)
{
$pathsCount = count($this->paths);
if ($pathsCount == 0) {
$this->paths[] = array();
$pathsCount++;
}
$p = $this->transform->multiplyPoint($x, $y);
$path = &$this->paths[$pathsCount - 1];
$path[] = $p->x;
$path[] = $p->y;
}
/**
* Adds an arc to the current path.
*
* @param float $x X coordinate of the center of the arc.
* @param float $y Y coordinate of the center of the arc.
* @param float $radius Radius of the arc.
* @param float $startAngle The angle in radians at which the arc starts,
* measured clockwise from the positive x axis.
* @param float $endAngle The angle in radians at which the arc end,
* measured clockwise from the positive x axis.
* @param boolean $anticlockwise Specifies whether the arc will be drawn
* counter clockwise. Default is clockwise.
*/
public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise)
{
$TARGET_CHORD_LENGTH_PIXELS = 3;
$sectors = floor((M_PI * $radius * 2) / $TARGET_CHORD_LENGTH_PIXELS);
if ($sectors < 9) {
$sectors = 9;
}
$sectorAngle = M_PI * 2 / $sectors;
if ($startAngle == $endAngle) {
return;
}
if ($anticlockwise) {
$sectorAngle = -$sectorAngle;
if ($startAngle - $endAngle >= M_PI * 2) {
$endAngle = $startAngle - M_PI * 2;
} else {
// Normalize end angle so that the sweep angle is in the range
// (0, -2PI]
$endAngle +=
M_PI * 2 * ceil(($startAngle - $endAngle) /
(M_PI * 2) - 1);
}
} else {
if ($endAngle - $startAngle >= M_PI * 2) {
$endAngle = $startAngle + M_PI * 2;
} else {
// Normalize end angle so that the sweep angle is in the range
// (0, 2PI]
$endAngle -=
M_PI * 2 * ceil(($endAngle - $startAngle) /
(M_PI * 2) - 1);
}
}
$dx;
$dy;
$sectors = ($endAngle - $startAngle) / $sectorAngle;
$angle = $startAngle;
for ($i = 0; $i < $sectors; $i++) {
$dx = cos($angle) * $radius;
$dy = sin($angle) * $radius;
$this->lineTo($x + $dx, $y + $dy);
$angle += $sectorAngle;
}
$dx = cos($endAngle) * $radius;
$dy = sin($endAngle) * $radius;
$this->lineTo($x + $dx, $y + $dy);
}
/**
* Fills the specified rectangle with fully transparent black without
* affecting the current paths.
*
* @param float $x X coordinate of the left side of the rectangle.
* @param float $y Y coordinate of the top of the rectangle.
* @param float $width Width of the rectangle.
* @param float $height Height of the rectangle.
*/
public function clearRect($x, $y, $width, $height)
{
$fullCanvas = false;
if (!$this->transform->hasSkewing()) {
// Check if the whole canvas is cleared
$topLeft = $this->transform->multiplyPoint($x, $y);
if ($topLeft->x <= 0 && $topLeft->y <= 0) {
$bottomRight = $this->transform->multiplyPoint(
$x + $width, $y + $height);
if ($bottomRight->x >= $this->canvas->width &&
$bottomRight->y >= $this->canvas->height
) {
$fullCanvas = true;
}
}
}
if ($fullCanvas) {
$this->edges->clear();
} else {
$this->_fillRect(ColorUtils::FORCE_TRANSPARENT,
$x, $y, $width, $height);
}
}
/**
* Fills the specified rectangle without affecting the current paths.
*
* @param float $x X coordinate of the left side of the rectangle.
* @param float $y Y coordinate of the top of the rectangle.
* @param float $width Width of the rectangle.
* @param float $height Height of the rectangle.
*/
public function fillRect($x, $y, $width, $height)
{
$fillColor = ColorUtils::parse($this->fillStyle);
$this->_fillRect($fillColor, $x, $y, $width, $height);
}
private function _fillRect($fillColor, $x, $y, $width, $height)
{
$polygonId = $this->edges->getNextPolygonId();
$points = array(
$this->transform->multiplyPoint($x, $y),
$this->transform->multiplyPoint($x + $width, $y),
$this->transform->multiplyPoint($x + $width, $y + $height),
$this->transform->multiplyPoint($x, $y + $height),
$this->transform->multiplyPoint($x, $y)
);
$pointsCount = count($points);
for ($i = 1; $i < $pointsCount; $i++) {
$this->edges->add(new Edge(
$polygonId,
$points[$i - 1]->x,
$points[$i - 1]->y,
$points[$i]->x,
$points[$i]->y,
$fillColor));
}
}
/**
* Fills the defined paths.
*
* @param string $windingRule The winding rule to be used for determining
* which areas are covered by the current path. Valid values are
* "evenodd" and "nonzero". Default is "nonzero".
*/
public function fill($windingRule = "nonzero")
{
$polygonId = $this->edges->getNextPolygonId();
$fillColor = ColorUtils::parse($this->fillStyle);
foreach ($this->paths as $points) {
$pointsCount = count($points);
if ($pointsCount <= 2) {
// Nothing to fill
continue;
}
for ($i = 2; $i < $pointsCount; $i += 2) {
$this->edges->add(new Edge(
$polygonId,
$points[$i - 2],
$points[$i - 1],
$points[$i],
$points[$i + 1],
$fillColor,
$windingRule));
}
// Close path
if ($points[0] != $points[$pointsCount - 2] ||
$points[1] != $points[$pointsCount - 1]
) {
$this->edges->add(new Edge(
$polygonId,
$points[$pointsCount - 2],
$points[$pointsCount - 1],
$points[0],
$points[1],
$fillColor,
$windingRule));
}
}
}
}

View file

@ -0,0 +1,228 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
class ColorUtils
{
/**
* Transparent color.
* @var integer
*/
const TRANSPARENT = 0;
/**
* Specifies a transparent color that will not blend with layers below the
* current layer.
*
* @var float
*/
const FORCE_TRANSPARENT = INF;
/**
* Creates a color on the format 0xRRGGBBAA from the specified
* color components.
*
* @return integer
*/
public static function from($a, $r, $g, $b)
{
return ($r << 24) | ($g << 16) | ($b << 8) | $a;
}
/**
* Gets the alpha component of a color.
*
* @param integer $color 32-bit color value on the format 0xRRGGBBAA.
* @return integer Alpha in the range [0, 255].
*/
public static function alpha($color)
{
return $color & 0xff;
}
/**
* Gets the red component of a color.
*
* @param integer $color 32-bit color value on the format 0xRRGGBBAA.
* @return integer Red component in the range [0, 255].
*/
public static function red($color)
{
return ($color >> 24) & 0xff;
}
/**
* Gets the green component of a color.
*
* @param integer $color 32-bit color value on the format 0xRRGGBBAA.
* @return integer Green component in the range [0, 255].
*/
public static function green($color)
{
return ($color >> 16) & 0xff;
}
/**
* Gets the blue component of a color.
*
* @param integer $color 32-bit color value on the format 0xRRGGBBAA.
* @return integer Blue component in the range [0, 255].
*/
public static function blue($color)
{
return ($color >> 8) & 0xff;
}
/**
* Formats a color as a string.
*
* @param integer $color Color to format.
* @return string
*/
public static function format($color)
{
return bin2hex(pack('N', $color));
}
/**
* Computes a mix of the two specified colors, with the proportion given
* by the specified weight.
*
* @param integer $color1 First color to mix.
* @param integer $color2 Second color to mix.
* @param float $weight Weight in the range [0,1].
* 0 gives $color1, 1 gives $color2.
* @return integer Mixed color.
*/
public static function mix($color1, $color2, $weight)
{
if ($weight < 0) {
$weight = 0;
} elseif ($weight > 1) {
$weight = 1;
}
$a = ($color1 & 0xff) * (1 - $weight) + ($color2 & 0xff) * $weight;
if ($a <= 0.1) {
return 0;
}
$r = (
($color1 >> 24) * ($color1 & 0xff) * (1 - $weight) +
($color2 >> 24) * ($color2 & 0xff) * $weight
) / $a;
$g = (
(($color1 >> 16) & 0xff) * ($color1 & 0xff) * (1 - $weight) +
(($color2 >> 16) & 0xff) * ($color2 & 0xff) * $weight
) / $a;
$b = (
(($color1 >> 8) & 0xff) * ($color1 & 0xff) * (1 - $weight) +
(($color2 >> 8) & 0xff) * ($color2 & 0xff) * $weight
) / $a;
if ($a > 255) $a = 255;
if ($r > 255) $r = 255;
if ($g > 255) $g = 255;
if ($b > 255) $b = 255;
return ((int)$r << 24) | ((int)$g << 16) | ((int)$b << 8) | (int)$a;
}
/**
* Parses a value to a 32-bit color on the format 0xRRGGBBAA.
*
* @param integer|string $color The value to parse.
* @return integer
*/
public static function parse($color)
{
if (gettype($color) == "integer") {
return $color & 0xffffffff;
}
$color = "$color";
if (preg_match('/^#?[0-9a-fA-F]+$/', $color)) {
$hexColor = $color;
if ($hexColor[0] == '#') {
$hexColor = substr($hexColor, 1);
}
switch (strlen($hexColor)) {
case 3:
$numeric = intval($hexColor, 16);
return (
(($numeric & 0xf00) << 20) |
(($numeric & 0xf00) << 16) |
(($numeric & 0x0f0) << 16) |
(($numeric & 0x0f0) << 12) |
(($numeric & 0x00f) << 12) |
(($numeric & 0x00f) << 8) |
0xff);
case 6:
return (intval($hexColor, 16) << 8) | 0xff;
case 8:
// Workaround to cope with PHP limitation of intval
$numeric =
(intval(substr($hexColor, 0, 4), 16) << 16) |
(intval(substr($hexColor, 4, 4), 16));
return $numeric;
}
}
throw new \InvalidArgumentException("Invalid color '$color'.");
}
/**
* Blends this color with another color using the over blending operation.
*
* @param integer $fore The foreground color.
* @param integer $back The background color.
* @return integer
*/
public static function over($fore, $back)
{
$foreA = ($fore & 0xff);
$backA = ($back & 0xff);
if ($foreA < 1) {
return $back;
} elseif ($foreA > 254 || $backA < 1) {
return $fore;
}
// Source:
// https://en.wikipedia.org/wiki/Alpha_compositing#Description
$forePA = $foreA * 255;
$backPA = $backA * (255 - $foreA);
$pa = ($forePA + $backPA);
$b = (int) (
($forePA * (($fore >> 8) & 0xff) + $backPA * (($back >> 8) & 0xff)) /
$pa);
$g = (int) (
($forePA * (($fore >> 16) & 0xff) + $backPA * (($back >> 16) & 0xff)) /
$pa);
$r = (int) (
($forePA * (($fore >> 24) & 0xff) + $backPA * (($back >> 24) & 0xff)) /
$pa);
$a = (int) ($pa / 255);
return ($r << 24) | ($g << 16) | ($b << 8) | $a;
}
};

View file

@ -0,0 +1,141 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
use Jdenticon\Canvas\Point;
class Matrix
{
private $a;
private $b;
private $c;
private $d;
private $e;
private $f;
/**
* Creates a new transformation matrix.
*/
public function __construct($a, $b, $c, $d, $e, $f)
{
$this->a = $a;
$this->b = $b;
$this->c = $c;
$this->d = $d;
$this->e = $e;
$this->f = $f;
}
/**
* Gets a value determining if this matrix has skewing values.
*
* @return boolean
*/
public function hasSkewing()
{
return $this->b || $this->c;
}
/**
* Gets a value determining if this matrix has translation values.
*
* @return boolean
*/
public function hasTranslation()
{
return $this->e || $this->f;
}
/**
* Gets a value determining if this matrix has scaling values.
*
* @return boolean
*/
public function hasScaling()
{
return $this->a != 1 || $this->d != 1;
}
/**
* Returns a new matrix based on the current matrix multiplied with the
* specified matrix values.
*
* @return \Jdenticon\Canvas\Matrix
*/
public function multiply($a, $b, $c, $d, $e, $f)
{
return new Matrix(
$this->a * $a + $this->c * $b,
$this->b * $a + $this->d * $b,
$this->a * $c + $this->c * $d,
$this->b * $c + $this->d * $d,
$this->a * $e + $this->c * $f + $this->e,
$this->b * $e + $this->d * $f + $this->f
);
}
/**
* Multiplies the specified point with the current matrix and returns the
* resulting point.
*
* @param float $x X coordinate.
* @param float $y Y coordinate.
* @return \Jdenticon\Canvas\Point
*/
public function multiplyPoint($x, $y)
{
return new Point(
$this->a * $x + $this->c * $y + $this->e,
$this->b * $x + $this->d * $y + $this->f
);
}
/**
* Returns a new matrix based on the current matrix with a rotation
* transformation applied.
*
* @param float $angle Rotation angle in radians.
* @return \Jdenticon\Canvas\Matrix
*/
public function rotate($angle)
{
$sin = sin($angle);
$cos = cos($angle);
return $this->multiply($cos, $sin, -$sin, $cos, 0, 0);
}
/**
* Returns a new matrix based on the current matrix with a translation
* transformation applied.
*
* @param float $x Horizontal move distance.
* @param float $y Vertical move distance.
* @return \Jdenticon\Canvas\Matrix
*/
public function translate($x, $y)
{
return $this->multiply(1, 0, 0, 1, $x, $y);
}
/**
* Returns a new matrix based on the current matrix with a scaling
* transformation applied.
*
* @param float $x Horizontal scale.
* @param float $y Vertical scale.
* @return \Jdenticon\Canvas\Matrix
*/
public function scale($x, $y)
{
return $this->multiply($x, 0, 0, $y, 0, 0);
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Png;
class PngBuffer
{
private $buffer = '';
private $chunkPreviousBuffer = '';
/**
* Writes a string to the buffer.
*
* @param string $str String to write.
*/
public function writeString($str)
{
$this->buffer .= $str;
}
/**
* Writes a 32 bit unsigned int to the buffer in Big Endian format.
*
* @param integer $value Value to write.
*/
public function writeUInt32BE($value)
{
$this->buffer .= pack('N', $value);
}
/**
* Writes an 8 bit unsigned int to the buffer.
*
* @param integer $value Value to write.
*/
public function writeUInt8($value)
{
$this->buffer .= pack('C', $value);
}
/**
* Starts a new PNG chunk.
*
* @param string $type Name of the chunk. Must contain exactly 4
* ASCII characters.
*/
public function startChunk($type)
{
$this->chunkPreviousBuffer = $this->buffer;
$this->buffer = $type;
}
/**
* Closes the current PNG chunk.
*/
public function endChunk()
{
// Compute Crc32 for type + data
$data = $this->buffer;
$crc = crc32($data);
$this->buffer =
$this->chunkPreviousBuffer .
// Length
pack('N', strlen($data) - 4) .
// Content
$data .
// Crc32
pack('N', $crc);
}
/**
* Gets a string containing the PNG encoded data.
*
* @return string
*/
public function getBuffer()
{
return $this->buffer;
}
}

View file

@ -0,0 +1,238 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Png;
use Jdenticon\Canvas\Png\PngPalette;
use Jdenticon\Canvas\Png\PngBuffer;
use Jdenticon\Canvas\ColorUtils;
class PngEncoder
{
const GRAYSCALE = 0;
const TRUE_COLOR = 2;
const INDEXED_COLOR = 3;
const GRAYSCALE_WITH_ALPHA = 4;
const TRUE_COLOR_WITH_ALPHA = 6;
private $buffer;
public function __construct()
{
$this->buffer = new PngBuffer();
$this->buffer->writeString("\x89\x50\x4e\x47\xd\xa\x1a\xa");
}
/**
* Writes an IHDR chunk to the png data stream.
*
* @param int $width Image width in pixels.
* @param int $height Image height in pixels.
* @param int $colorType Color depth, speocfy one of the constants in
* PngEncoder.
*/
public function writeImageHeader($width, $height, $colorType)
{
$this->buffer->startChunk("IHDR");
$this->buffer->writeUInt32BE($width);
$this->buffer->writeUInt32BE($height);
$this->buffer->writeUInt8(8); // Bit depth
$this->buffer->writeUInt8($colorType);
$this->buffer->writeUInt8(0); // Compression
$this->buffer->writeUInt8(0); // Filter
$this->buffer->writeUInt8(0); // Interlace
$this->buffer->endChunk();
}
/**
* Writes a gAMA chunk to the png data stream.
*
* @param int $gamma Gamma value.
*/
public function writeImageGamma($gamma = 45455)
{
$this->buffer->startChunk("gAMA");
$this->buffer->writeUInt32BE($gamma);
$this->buffer->endChunk();
}
/**
* Writes an IDAT chunk of truecolor encoded image data.
*
* @param array $colorRanges Image data on the format
* array(count0, color0, count1, color1, ...)
* @param int $width Image width in pixels.
* @param int $height Image height in pixels.
*/
public function writeTrueColorWithAlpha(
array & $colorRanges, $width, $height)
{
$this->buffer->startChunk("IDAT");
$uncompressed = '';
$count = -1;
$x = 0;
foreach ($colorRanges as $value) {
if ($count === -1) {
$count = $value;
} else {
if ($count !== 0) {
if ($x === $width) {
$x = 0;
}
if ($x === 0) {
$uncompressed .= pack('C', 0); // No filtering
}
$uncompressed .= str_repeat(pack('N', $value), $count);
$x += $count;
}
$count = -1;
}
}
$compressed = gzcompress($uncompressed, 2);
$this->buffer->writeString($compressed);
$this->buffer->endChunk();
}
/**
* Writes an IDAT chunk of indexed image data.
*
* @param array $colorRanges Image data on the format
* array(count0, color0, count1, color1, ...)
* @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the
* indexed colors.
* @param int $width Image width in pixels.
* @param int $height Image height in pixels.
*/
public function writeIndexed(
array & $colorRanges,
PngPalette $palette,
$width, $height)
{
$this->buffer->startChunk("IDAT");
$uncompressed = '';
$count = -1;
$x = 0;
foreach ($colorRanges as $value) {
if ($count === -1) {
$count = $value;
} else {
if ($count !== 0) {
if ($x === $width) {
$x = 0;
}
if ($x === 0) {
$uncompressed .= pack('C', 0); // No filtering
}
$colorIndex = $palette->lookup[$value];
$uncompressed .= str_repeat(pack('C', $colorIndex), $count);
$x += $count;
}
$count = -1;
}
}
$compressed = gzcompress($uncompressed, 2);
$this->buffer->writeString($compressed);
$this->buffer->endChunk();
}
/**
* Writes a PLTE chunk containing the indexed colors.
*
* @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the
* indexed colors.
*/
public function writePalette(PngPalette $palette)
{
if ($palette && $palette->isValid) {
$this->buffer->startChunk("PLTE");
foreach ($palette->colors as $color) {
$this->buffer->writeString(
pack('C', ($color >> 24) & 0xff) .
pack('C', ($color >> 16) & 0xff) .
pack('C', ($color >> 8) & 0xff));
}
$this->buffer->endChunk();
}
}
/**
* Writes a tRNS chunk containing the alpha values of indexed colors.
*
* @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the
* indexed colors.
*/
public function writeTransparency(PngPalette $palette)
{
if ($palette && $palette->isValid && $palette->hasAlphaChannel) {
$this->buffer->startChunk("tRNS");
$alpha = '';
foreach ($palette->colors as $color) {
$alpha .= pack('C', $color & 0xff);
}
$this->buffer->writeString($alpha);
$this->buffer->endChunk();
}
}
/**
* Writes a tEXt chunk containing the specified strings.
*
* @param string $key Key, one of
* {@link https://www.w3.org/TR/2003/REC-PNG-20031110/#11keywords}
* @param string $value Value.
*/
public function writeTextualData($key, $value)
{
$this->buffer->startChunk("tEXt");
$this->buffer->writeString($key);
$this->buffer->writeUInt8(0);
$this->buffer->writeString($value);
$this->buffer->endChunk();
}
/**
* Writes an IEND chunk to the png data stream.
*/
public function writeImageEnd()
{
$this->buffer->startChunk("IEND");
$this->buffer->endChunk();
}
/**
* Gets a binary string containing the PNG data.
*
* @return string
*/
public function getBuffer()
{
return $this->buffer->getBuffer();
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Png;
use Jdenticon\Canvas\ColorUtils;
/**
* Contains the colors of a PNG color palette.
*/
class PngPalette
{
/**
* Creates a PNG color palette for the specified bitmap data.
*
* @param array(integer) $colorRanges Array of interleaved values on the
* format array(count0, color0, count1, color1, ...).
*/
function __construct(& $colorRanges)
{
$lookup = array();
$colors = array();
$hasAlphaChannel = false;
$colorsCount = 0;
$count = -1;
foreach ($colorRanges as $value) {
if ($count === -1) {
$count = $value;
} else {
// Ignore empty ranges and already indexed colors
if ($count > 0 && !isset($lookup[$value])) {
if (!$hasAlphaChannel && ($value & 0xff) < 255) {
$hasAlphaChannel = true;
}
$lookup[$value] = $colorsCount++;
$colors[] = $value;
if ($colorsCount > 256) {
break;
}
}
$count = -1;
}
}
$this->hasAlphaChannel = $hasAlphaChannel;
$this->colors = & $colors;
$this->lookup = & $lookup;
$this->isValid = $colorsCount <= 256;
}
/**
* Specifies if the palette is valid to be used for encoding a PNG image.
*
* @var boolean
*/
public $isValid;
/**
* Specifies if the palette has any partial or fully transparent
* colors.
*
* @var boolean
*/
public $hasAlphaChannel;
/**
* Array of colors in the palette.
*
* @var array
*/
public $colors;
/**
* Lookup table from 32-bit color value to color index.
*
* @var array
*/
public $lookup;
}

View file

@ -0,0 +1,41 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas;
class Point
{
/**
* X coordinate.
*
* @var float
*/
public $x;
/**
* Y coordinate.
*
* @var float
*/
public $y;
/**
* Creates a new 2D point.
*
* @param float $x X coordinate.
* @param float $y Y coordinate.
*/
public function __construct($x, $y)
{
$this->x = $x;
$this->y = $y;
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class Edge
{
public $polygonId;
public $x0;
public $x1;
public $y0;
public $y1;
public $color;
public $windingRule;
public function __construct(
$polygonId, $x0, $y0, $x1, $y1, $color, $windingRule = null)
{
$this->polygonId = $polygonId;
$this->x0 = $x0;
$this->x1 = $x1;
$this->y0 = $y0;
$this->y1 = $y1;
$this->color = $color;
$this->windingRule = $windingRule;
}
public function intersection($y)
{
$dx =
($this->x1 - $this->x0) * ($this->y0 - $y) /
($this->y0 - $this->y1);
return $this->x0 + $dx;
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class EdgeIntersection
{
public $fromX;
public $width;
public $edge;
public function __construct($fromX, $width, $edge)
{
$this->fromX = $fromX;
$this->width = $width;
$this->edge = $edge;
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class EdgeSuperSampleIntersection
{
public $x;
public $edge;
public function __construct($x, $edge)
{
$this->x = $x;
$this->edge = $edge;
}
}

View file

@ -0,0 +1,158 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class EdgeTable
{
private $scanlines;
private $nextPolygonId;
private $width;
private $height;
/**
* Keeps a list of edges per scanline.
*
* @param integer $width Clipping width.
* @param integer $height Clipping height.
*/
public function __construct($width, $height)
{
$this->width = $width;
$this->height = $height;
$this->clear();
}
/**
* Sorts the edges of each scanline in ascending x coordinates.
*/
public function clear()
{
$this->scanlines = array();
$this->nextPolygonId = 1;
}
/**
* Gets an id for the next polygon.
*
* @return int
*/
public function getNextPolygonId()
{
return $this->nextPolygonId++;
}
/**
* Gets the scaline for the specified Y coordinate, or NULL if there are
* no edges for the specified Y coordinate.
*
* @return array|null.
*/
public function getScanline($y)
{
return isset($this->scanlines[$y]) ? $this->scanlines[$y] : null;
}
/**
* Adds an edge to the table.
*
* @param \Jdenticon\Canvas\Rasterization\Edge $edge
*/
public function add(\Jdenticon\Canvas\Rasterization\Edge $edge)
{
$minY = 0;
$maxY = 0;
if ($edge->y0 == $edge->y1) {
// Skip horizontal lines
return;
} elseif ($edge->y0 < $edge->y1) {
$minY = (int)($edge->y0);
$maxY = (int)($edge->y1 + 0.996 /* 1/255 */);
} else {
$minY = (int)($edge->y1);
$maxY = (int)($edge->y0 + 0.996 /* 1/255 */);
}
if ($maxY < 0 || $minY >= $this->height) {
return;
}
if ($minY < 0) {
$minY = 0;
}
if ($maxY > $this->height) {
$maxY = $this->height;
}
if ($minY < $maxY) {
$y = $minY;
$x1 = $edge->intersection($y);
while ($y < $maxY) {
$x2 = $edge->intersection($y + 1);
$fromX;
$width;
if ($x1 < $x2) {
$fromX = (int)($x1);
$width = (int)($x2 + 0.9999) - $fromX;
} else {
$fromX = (int)($x2);
$width = (int)($x1 + 0.9999) - $fromX;
}
if ($fromX < 0) {
$width += $fromX;
$fromX = 0;
if ($width < 0) {
$width = 0;
}
}
if ($fromX < $this->width) {
if (!isset($this->scanlines[$y])) {
$this->scanlines[$y] = array();
}
$this->scanlines[$y][] = new EdgeIntersection(
$fromX, $width, $edge);
}
$x1 = $x2;
$y++;
}
}
}
private static function edge_cmp($x, $y)
{
if ($x->fromX < $y->fromX) {
return -1;
}
if ($x->fromX > $y->fromX) {
return 1;
}
return 0;
}
/**
* Sorts the edges of each scanline in ascending x coordinates.
*/
public function sort()
{
foreach ($this->scanlines as $i => &$scanline) {
usort($scanline, array(
'Jdenticon\\Canvas\\Rasterization\\EdgeTable', 'edge_cmp'));
}
}
}

View file

@ -0,0 +1,30 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class Layer
{
public $polygonId;
public $color;
public $winding;
public $windingRule;
public $nextLayer;
public function __construct($polygonId, $color, $winding, $windingRule)
{
$this->polygonId = $polygonId;
$this->color = $color;
$this->winding = $winding;
$this->windingRule = $windingRule;
}
}

View file

@ -0,0 +1,150 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
use Jdenticon\Canvas\ColorUtils;
use Jdenticon\Canvas\Rasterization\Layer;
use Jdenticon\Canvas\Rasterization\Edge;
/**
* Keeps track of the z-order of the currently rendered polygons,
* and computes the final color from the stack of layers.
*/
class LayerManager
{
public $topLayer;
/**
* The current visible color.
* @var integer
*/
public $color;
public function __construct()
{
$this->color = ColorUtils::TRANSPARENT;
}
/**
* Copies all layers in this manager to another LayerManager.
*
* @param \Jdenticon\Canvas\Rasterization\LayerManager $other The
* LayerManager to copy all layers to.
*/
public function copyTo(LayerManager $other)
{
$other->color = $this->color;
$layer = $this->topLayer;
$previousCopy = null;
while ($layer !== null) {
$copy = new Layer(
$layer->polygonId,
$layer->color,
$layer->winding,
$layer->windingRule
);
if ($previousCopy === null) {
$other->topLayer = $copy;
}
else {
$previousCopy->nextLayer = $copy;
}
$previousCopy = $copy;
$layer = $layer->nextLayer;
}
}
/**
* Adds a layer for the specified edge. The z-order is defined by its id.
*
* @param \Jdenticon\Canvas\Rasterization\Edge edge
*/
public function add(Edge $edge)
{
$dwinding = $edge->y0 < $edge->y1 ? 1 : -1;
$layer = $this->topLayer;
$previousLayer = null;
while ($layer !== null) {
if ($layer->polygonId === $edge->polygonId) {
$layer->winding += $dwinding;
$inPath = $layer->windingRule == 'evenodd' ?
($layer->winding % 2 === 1) : ($layer->winding !== 0);
if (!$inPath) {
// Remove layer
if ($previousLayer === null) {
$this->topLayer = $layer->nextLayer;
}
else {
$previousLayer->nextLayer = $layer->nextLayer;
}
}
break;
} elseif ($layer->polygonId < $edge->polygonId) {
// Insert here
$newLayer = new Layer(
$edge->polygonId,
$edge->color,
$dwinding,
$edge->windingRule
);
$newLayer->nextLayer = $layer;
if ($previousLayer === null) {
$this->topLayer = $newLayer;
} else {
$previousLayer->nextLayer = $newLayer;
}
break;
}
$previousLayer = $layer;
$layer = $layer->nextLayer;
}
if ($layer === null) {
$newLayer = new Layer(
$edge->polygonId,
$edge->color,
$dwinding,
$edge->windingRule
);
if ($previousLayer === null) {
$this->topLayer = $newLayer;
} else {
$previousLayer->nextLayer = $newLayer;
}
}
// Update current color
$color = ColorUtils::TRANSPARENT;
$layer = $this->topLayer;
while ($layer !== null && ($color & 0xff) < 255) {
if ($layer->color === ColorUtils::FORCE_TRANSPARENT) {
break;
}
$color = ColorUtils::over($layer->color, $color);
}
$this->color = $color;
}
}

View file

@ -0,0 +1,379 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
use Jdenticon\Canvas\ColorUtils;
use Jdenticon\Canvas\Rasterization\LayerManager;
use Jdenticon\Canvas\Rasterization\SuperSampleBuffer;
use Jdenticon\Canvas\Rasterization\SuperSampleRange;
use Jdenticon\Canvas\Rasterization\EdgeSuperSampleIntersection;
class Rasterizer
{
/**
* A higher number of samples per pixel horizontally does not affect the
* performance in the same way as SAMPLES_PER_PIXEL_Y, since the rasterizer
* does not scan every subpixel horizontally.
*/
const SAMPLES_PER_PIXEL_X = 10;
/**
* A higher number of samples vertically means lower performance, since
* the rasterizer does a scan for every subpixel vertically.
*/
const SAMPLES_PER_PIXEL_Y = 3;
const SAMPLE_HEIGHT = 0.33333; // 1 / self::SAMPLES_PER_PIXEL_Y
/**
* Rasterizes the edges in the edge table to a list of color ranges. No
* range will span multiple scanlines.
*/
public static function rasterize(& $colorData, $edgeTable, $width, $height)
{
$edgeTable->sort();
$superSampleBuffer = new SuperSampleBuffer(
$width, self::SAMPLES_PER_PIXEL_X);
$layers = array();
$color = 0;
// Keeps track of how many of the subpixellayers that are used for
// the currently rendered scanline. Until a range requiring
// supersampling is encountered only a single layer is needed.
$usedLayers = 0;
for ($i = 0; $i < self::SAMPLES_PER_PIXEL_Y; $i++) {
$layers[] = new LayerManager();
}
for ($ey = 0; $ey < $height; $ey++) {
$scanline = $edgeTable->getScanline($ey);
if ($scanline === null) {
$colorData[] = $width;
$colorData[] = 0;
continue;
}
$superSampleRanges = self::getSuperSampleRanges($scanline, $width);
$superSampleRangeCount = count($superSampleRanges);
foreach ($layers as $layer) {
$layer->topLayer = null;
$layer->color = ColorUtils::TRANSPARENT;
}
$usedLayers = 1;
if ($superSampleRanges[0]->fromX) {
$colorData[] = $superSampleRanges[0]->fromX;
$colorData[] = 0;
}
for (
$rangeIndex = 0;
$rangeIndex < $superSampleRangeCount;
$rangeIndex++
) {
$superSampleRange = $superSampleRanges[$rangeIndex];
$edge = $superSampleRange->edges[0];
// If there is exactly one edge in the supersample range, and it
// is crossing the entire scanline, we can perform the
// antialiasing by integrating the edge function.
if (!isset($superSampleRange->edges[1]) && (
$edge->y0 <= $ey && $edge->y1 >= $ey + 1 ||
$edge->y0 >= $ey + 1 && $edge->y1 <= $ey
)) {
// Determine the lower and upper x value where the edge
// intersects the scanline.
$xey = $edge->intersection($ey);
$xey1 = $edge->intersection($ey + 1);
if ($xey < $xey1) {
$x0 = $xey;
$x1 = $xey1;
} else {
$x0 = $xey1;
$x1 = $xey;
}
$rangeWidth = $x1 - $x0;
if ($usedLayers === 1) {
$subScanlineLayers = $layers[0];
$fromColor = $subScanlineLayers->color;
$subScanlineLayers->add($edge);
$toColor = $subScanlineLayers->color;
} else {
$fromColorR = 0;
$fromColorG = 0;
$fromColorB = 0;
$fromColorA = 0;
$toColorR = 0;
$toColorG = 0;
$toColorB = 0;
$toColorA = 0;
// Compute the average color of all subpixel layers
// before and after the edge intersection.
// The calculation is inlined for increased performance.
for ($i = 0; $i < $usedLayers; $i++) {
$subScanlineLayers = $layers[$i];
// Add to average from-color
$color = $subScanlineLayers->color;
$alpha = $color & 0xff;
if ($alpha > 0) {
$fromColorA += $alpha;
$fromColorR += (($color >> 24) & 0xff) * $alpha;
$fromColorG += (($color >> 16) & 0xff) * $alpha;
$fromColorB += (($color >> 8) & 0xff) * $alpha;
}
// Add the new layer
$subScanlineLayers->add($edge);
// Add to average to-color
$color = $subScanlineLayers->color;
$alpha = $color & 0xff;
if ($alpha > 0) {
$toColorA += $alpha;
$toColorR += (($color >> 24) & 0xff) * $alpha;
$toColorG += (($color >> 16) & 0xff) * $alpha;
$toColorB += (($color >> 8) & 0xff) * $alpha;
}
}
$fromColor = $fromColorA === 0 ? 0 : ColorUtils::from(
(int)($fromColorA / $usedLayers),
(int)($fromColorR / $fromColorA),
(int)($fromColorG / $fromColorA),
(int)($fromColorB / $fromColorA));
$toColor = $toColorA === 0 ? 0 : ColorUtils::from(
(int)($toColorA / $usedLayers),
(int)($toColorR / $toColorA),
(int)($toColorG / $toColorA),
(int)($toColorB / $toColorA));
}
// Render pixels
for (
$x = $superSampleRange->fromX;
$x < $superSampleRange->toXExcl;
$x++
) {
if ($x0 >= $x + 1) {
// Pixel not covered
$colorData[] = 1;
$colorData[] = $fromColor;
continue;
}
if ($x1 <= $x) {
// Pixel fully covered
$colorData[] = 1;
$colorData[] = $toColor;
continue;
}
// toColor coverage in the range [0.0, 1.0]
// Initialize to the fully covered range of the pixel.
$coverage = $x1 < $x + 1 ? $x + 1 - $x1 : 0;
// Compute integral for non-vertical edges
if ($rangeWidth > 0.001) {
// Range to integrate
$integralFrom = $x0 > $x ? $x0 : $x;;
$integralTo = $x1 < $x + 1 ? $x1 : $x + 1;
$coverage +=
(
(
$integralTo * $integralTo -
$integralFrom * $integralFrom
) / 2 +
$x0 * ($integralFrom - $integralTo)
) / $rangeWidth;
}
$colorData[] = 1;
$colorData[] = ColorUtils::mix(
$fromColor, $toColor, $coverage);
}
$color = $toColor;
} // /simplified antialiasing
else {
// Super sampling
$y = $ey + self::SAMPLE_HEIGHT / 2;
// Ensure all subpixel layers are initialized
while ($usedLayers < self::SAMPLES_PER_PIXEL_Y) {
$layers[0]->copyTo($layers[$usedLayers]);
$usedLayers++;
}
foreach ($layers as $subScanlineLayers) {
$color = $subScanlineLayers->color;
$intersections = self::getIntersections(
$superSampleRange->edges, $y);
foreach ($intersections as $intersection) {
$superSampleBuffer->add($color,
$intersection->x - $superSampleRange->fromX);
$subScanlineLayers->add($intersection->edge);
$color = $subScanlineLayers->color;
}
$superSampleBuffer->add(
$color, $superSampleRange->width + 1);
$superSampleBuffer->rewind();
$y += self::SAMPLE_HEIGHT;
} // /subpixel
// Blend subpixels
$color = $superSampleBuffer->colorAt(
$superSampleRange->width);
$superSampleBuffer->emptyTo(
$colorData, $superSampleRange->width);
//$color = end($colorData);
} // /super sampling
// Forward last color
if ($rangeIndex + 1 < $superSampleRangeCount) {
$count =
$superSampleRanges[$rangeIndex + 1]->fromX -
$superSampleRange->toXExcl;
if ($count > 0) {
$colorData[] = $count;
$colorData[] = $color;
}
} else {
$count = $width - $superSampleRange->toXExcl;
if ($count > 0) {
$colorData[] = $count;
$colorData[] = $color;
}
}
} // /range
}
return $colorData;
}
private static function intersection_cmp($a, $b)
{
if ($a->x < $b->x) {
return -1;
}
if ($a->x > $b->x) {
return 1;
}
return 0;
}
/**
* Determines what edges that intersect a horizontal line with the specified
* y coordinate. For each intersecting edge the intersecting x coordinate is
* returned.
*
* @param array $edges Array of edges in the current scanline.
* @param int $y Y coordinate of the current scanline.
* @return array Array containing EdgeSuperSampleIntersection. Objects
* are sorted ascending by x coordinate.
*/
private static function getIntersections($edges, $y)
{
$intersections = array();
foreach ($edges as $edge) {
if ($edge->y0 < $y && $edge->y1 >= $y ||
$edge->y0 >= $y && $edge->y1 < $y
) {
$x = $edge->x0 +
($edge->x1 - $edge->x0) * ($y - $edge->y0) /
($edge->y1 - $edge->y0);
$intersections[] = new EdgeSuperSampleIntersection($x, $edge);
}
}
usort($intersections, array(
'Jdenticon\\Canvas\\Rasterization\\Rasterizer',
'intersection_cmp'));
return $intersections;
}
/**
* Determines what ranges of a scanline that needs to be supersampled.
*
* @param array $scanline Array of edges in the current scanline.
* @return array Array of SuperSampleRange.
*/
private static function getSuperSampleRanges(&$scanline, $width)
{
$superSampleRanges = array();
$rangeIndex = 0;
$scanlineCount = count($scanline);
while ($rangeIndex < $scanlineCount) {
$range = $scanline[$rangeIndex];
if ($range->fromX >= $width) {
break;
}
$superSampleRange = new SuperSampleRange(
$range->fromX,
$range->fromX + $range->width
);
$superSampleRange->edges[] = $range->edge;
$rangeIndex++;
for ($i = $rangeIndex; $i < $scanlineCount; $i++) {
$range = $scanline[$i];
if ($range->fromX < $superSampleRange->toXExcl) {
$superSampleRange->toXExcl = max(
$superSampleRange->toXExcl,
$range->fromX + $range->width);
$superSampleRange->edges[] = $range->edge;
$rangeIndex++;
} else {
break;
}
}
$superSampleRange->toXExcl = min($superSampleRange->toXExcl, $width);
$superSampleRange->width =
$superSampleRange->toXExcl - $superSampleRange->fromX;
$superSampleRanges[] = $superSampleRange;
}
return $superSampleRanges;
}
}

View file

@ -0,0 +1,198 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
use Jdenticon\Canvas\ColorUtils;
class SuperSampleBuffer
{
const IDX_COUNT = 0;
const IDX_A = 1;
const IDX_R = 2;
const IDX_G = 3;
const IDX_B = 4;
/**
* Creates a color buffer keeping an average color out of several
* color samples per pixel.
*
* @param integer $width Width of the buffer in pixels.
* @param integer $samplesPerPixel Number of samples to keep per pixel.
*/
public function __construct($width, $samplesPerPixel)
{
$this->samples = array();
$this->samplesPerPixel = $samplesPerPixel;
$this->pixelOffset = 0;
$this->subPixelOffset = 0;
$this->width = $width;
$this->used = -1;
}
/**
* Rewinds the cursor to the beginning of the buffer.
*/
public function rewind()
{
$this->pixelOffset = 0;
$this->subPixelOffset = 0;
}
/**
* Clears the samples in this buffer.
*/
public function clear()
{
$this->pixelOffset = 0;
$this->subPixelOffset = 0;
$this->used = -1;
}
/**
* Writes the average color of each pixel to a specified color array.
*
* @param array $colorData The average colors will be written to this
* color array.
* @param integer $count Number of pixels to write.
*/
public function emptyTo(& $colorData, $count)
{
for ($i = 0; $i < $count; $i++) {
$sampleCount = $this->samples[$i * 5 + self::IDX_COUNT];
$a = $this->samples[$i * 5 + self::IDX_A];
$color = $sampleCount == 0 || $a == 0 ? 0 :
ColorUtils::from(
(int)($a / $sampleCount),
(int)($this->samples[$i * 5 + self::IDX_R] / $a),
(int)($this->samples[$i * 5 + self::IDX_G] / $a),
(int)($this->samples[$i * 5 + self::IDX_B] / $a)
);
$colorData[] = 1;
$colorData[] = $color;
}
$this->pixelOffset = 0;
$this->subPixelOffset = 0;
$this->used = -1;
}
/**
* Gets the average color of the pixel at a specified index.
*
* @param integer $index The index of the pixel.
* @return integer
*/
public function colorAt($index)
{
$sampleCount = $this->samples[$index * 5 + self::IDX_COUNT];
$alphaSum = $this->samples[$index * 5 + self::IDX_A];
return $sampleCount == 0 || $alphaSum == 0 ? 0 :
ColorUtils::from(
(int)($alphaSum / $sampleCount),
(int)($this->samples[$index * 5 + self::IDX_R] / $alphaSum),
(int)($this->samples[$index * 5 + self::IDX_G] / $alphaSum),
(int)($this->samples[$index * 5 + self::IDX_B] / $alphaSum)
);
}
/**
* Adds a color to the current pixel in the buffer.
*
* @param integer $count Number of samples of the color to be added to
* the buffer.
*/
private function _add($count, $a, $r, $g, $b)
{
if ($this->used < $this->pixelOffset) {
$this->used = $this->pixelOffset;
$this->samples[$this->pixelOffset * 5 + self::IDX_COUNT] = $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_A] = $a * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_R] = $a * $r * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_G] = $a * $g * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_B] = $a * $b * $count;
} else {
$this->samples[$this->pixelOffset * 5 + self::IDX_COUNT] += $count;
if ($a > 0) {
$this->samples[$this->pixelOffset * 5 + self::IDX_A] += $a * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_R] += $a * $r * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_G] += $a * $g * $count;
$this->samples[$this->pixelOffset * 5 + self::IDX_B] += $a * $b * $count;
}
}
}
/**
* Adds a color to the buffer up until the specified x index.
*
* @param integer $color Color to write.
* @param float $untilX Samples of the color will be added the buffer until
* the cursor reaches this coordinate.
*/
public function add($color, $untilX)
{
$samplesLeft =
(int)($untilX * $this->samplesPerPixel) -
$this->subPixelOffset -
$this->pixelOffset * $this->samplesPerPixel;
// ColorUtils methods inlined for performance reasons
$a = ($color) & 0xff;
$r = ($color >> 24) & 0xff;
$g = ($color >> 16) & 0xff;
$b = ($color >> 8) & 0xff;
// First partial pixel
if ($this->subPixelOffset > 0) {
$samples = $this->samplesPerPixel - $this->subPixelOffset;
if ($samples > $samplesLeft) {
$samples = $samplesLeft;
}
$samplesLeft -= $samples;
$this->_add($samples, $a, $r, $g, $b);
$this->subPixelOffset += $samples;
if ($this->subPixelOffset == $this->samplesPerPixel) {
$this->subPixelOffset = 0;
$this->pixelOffset++;
}
}
// Full pixels
$fullPixels = (int)($samplesLeft / $this->samplesPerPixel);
if ($fullPixels > 0) {
for ($i = 0; $i < $fullPixels; $i++) {
$this->_add($this->samplesPerPixel, $a, $r, $g, $b);
$this->pixelOffset++;
}
$samplesLeft -= $fullPixels * $this->samplesPerPixel;
}
// Last partial pixel
if ($samplesLeft > 0) {
$this->_add($samplesLeft, $a, $r, $g, $b);
$this->subPixelOffset += $samplesLeft;
if ($this->subPixelOffset == $this->samplesPerPixel) {
$this->subPixelOffset = 0;
$this->pixelOffset++;
}
}
}
}

View file

@ -0,0 +1,27 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Canvas\Rasterization;
class SuperSampleRange
{
public $fromX;
public $toXExcl;
public $edges;
public $width;
public function __construct($fromX, $toXExcl)
{
$this->fromX = $fromX;
$this->toXExcl = $toXExcl;
$this->edges = array();
}
}

605
vendor/jdenticon/jdenticon/src/Color.php vendored Normal file
View file

@ -0,0 +1,605 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon;
/**
* Represents a 24-bit color with a 8-bit alpha channel.
*/
class Color
{
private static $lightnessCompensations = array(
0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55);
/**
* The red component of the color in the range [0, 255].
* @var int
*/
public $r;
/**
* The green component of the color in the range [0, 255].
* @var int
*/
public $g;
/**
* The blue component of the color in the range [0, 255].
* @var int
*/
public $b;
/**
* The alpha component of the color in the range [0, 255].
* @var int
*/
public $a;
// Users of the struct should use the static factory methods
// to create Color value.
private function __construct()
{
}
/**
* Creates a Color from an RGB value.
*
* @param int $alpha Alpha channel value in the range [0, 255].
* @param int $red Red component in the range [0, 255].
* @param int $green GReen component in the range [0, 255].
* @param int $blue Blue component in the range [0, 255].
*/
public static function fromRgb($red, $green, $blue, $alpha = 255)
{
$color = new Color();
$color->r = $red;
$color->g = $green;
$color->b = $blue;
$color->a = $alpha;
return $color;
}
/**
* Creates a Color instance from HSL color parameters.
*
* @param float $hue Hue in the range [0, 1]
* @param float $saturation Saturation in the range [0, 1]
* @param float $lightness Lightness in the range [0, 1]
* @param float $alpha Alpha channel value in the range [0, 1].
*/
public static function fromHsl($hue, $saturation, $lightness, $alpha = 1.0)
{
if ($hue < 0) $hue = 0;
if ($hue > 1) $hue = 1;
if ($saturation < 0) $saturation = 0;
if ($saturation > 1) $saturation = 1;
if ($lightness < 0) $lightness = 0;
if ($lightness > 1) $lightness = 1;
if ($alpha < 0) $alpha = 0;
if ($alpha > 1) $alpha = 1;
// Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
if ($saturation == 0) {
$value = (int)($lightness * 255);
return self::fromRgb($value, $value, $value, (int)($alpha * 255));
} else {
if ($lightness <= 0.5) {
$m2 = $lightness * ($saturation + 1);
} else {
$m2 = $lightness + $saturation - $lightness * $saturation;
}
$m1 = $lightness * 2 - $m2;
return self::fromRgb(
self::hueToRgb($m1, $m2, $hue * 6 + 2),
self::hueToRgb($m1, $m2, $hue * 6),
self::hueToRgb($m1, $m2, $hue * 6 - 2),
(int)($alpha * 255));
}
}
/**
* Creates a Color> instance from HSL color parameters and will compensate
* the lightness for hues that appear to be darker than others.
*
* @param float $hue Hue in the range [0, 1].
* @param float $saturation Saturation in the range [0, 1].
* @param float $lightness Lightness in the range [0, 1].
* @param float $alpha Alpha channel value in the range [0, 1].
*/
public static function fromHslCompensated($hue, $saturation, $lightness, $alpha = 1.0)
{
if ($hue < 0) $hue = 0;
if ($hue > 1) $hue = 1;
$lightnessCompensation = self::$lightnessCompensations[(int)($hue * 6 + 0.5)];
// Adjust the input lightness relative to the compensation
$lightness = $lightness < 0.5 ?
$lightness * $lightnessCompensation * 2 :
$lightnessCompensation + ($lightness - 0.5) * (1 - $lightnessCompensation) * 2;
return self::fromHsl($hue, $saturation, $lightness, $alpha);
}
// Helper method for FromHsl
private static function hueToRgb($m1, $m2, $h)
{
if ($h < 0) {
$h = $h + 6;
} elseif ($h > 6) {
$h = $h - 6;
}
if ($h < 1) {
$r = $m1 + ($m2 - $m1) * $h;
} elseif ($h < 3) {
$r = $m2;
} elseif ($h < 4) {
$r = $m1 + ($m2 - $m1) * (4 - $h);
} else {
$r = $m1;
}
return (int)(255 * $r);
}
/**
* Gets the argb value of this color.
*
* @return int
*/
public function toRgba()
{
return
($this->r << 24) |
($this->g << 16) |
($this->b << 8) |
($this->a);
}
/**
* Gets a hexadecimal representation of this color on the format #rrggbbaa.
*
* @return string
*/
public function __toString()
{
return '#' . bin2hex(pack('N', $this->toRgba()));
}
/**
* Gets a hexadecimal representation of this color on the format #rrggbbaa.
*
* @return string
*/
public function toHexString($length = 8)
{
if ($length === 8) {
return $this->__toString();
}
return '#' . substr(bin2hex(pack('N', $this->toRgba())), 0, 6);
}
/**
* Tries to parse a value as a Color.
*
* @param mixed $value Value to parse.
* @throws InvalidArgumentException
* @return \Jdenticon\Color
*/
public static function parse($value) {
if ($value instanceof Color) {
return $value;
}
$value = strtolower("$value");
if (preg_match('/^#?[0-9a-f]{3,8}$/', $value) &&
self::parseHexColor($value, $result)
) {
return $result;
}
if (preg_match(
'/^rgba?\\(([^,]+),([^,]+),([^,]+)(?:,([^,]+))?\\)$/',
$value, $matches) &&
self::parseRgbComponent($matches[1], $r) &&
self::parseRgbComponent($matches[2], $g) &&
self::parseRgbComponent($matches[3], $b) &&
self::parseAlpha(isset($matches[4]) ? $matches[4] : null, $a)
) {
return self::fromRgb($r, $g, $b, (int)(255 * $a));
}
if (preg_match(
'/^hsla?\\(([^,]+),([^,]+),([^,]+)(?:,([^,]+))?\\)$/',
$value, $matches) &&
self::parseHue($matches[1], $h) &&
self::parsePercent($matches[2], $s) &&
self::parsePercent($matches[3], $l) &&
self::parseAlpha(isset($matches[4]) ? $matches[4] : null, $a)
) {
return self::fromHsl($h, $s, $l, $a);
}
$result = self::parseNamedColor($value);
if ($result !== null) {
return $result;
}
throw new \InvalidArgumentException(
"Cannot parse '$value' as a color.");
}
/**
* Parses a percent value.
*
* @param string $input Input string.
* @param float $result Resulting value in range [0, 1].
*
* @return boolean
*/
private static function parsePercent($input, &$result)
{
// Detect and remove percent sign
if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)%\\s*$/', $input, $matches)) {
$result = floatval($matches[1]) / 100;
if ($result < 0) $result = 0;
if ($result > 1) $result = 1;
return true;
}
return false;
}
/**
* Parses an alpha value.
*
* @param string $input Input string.
* @param float $result Resulting alpha in range [0, 1].
*
* @return boolean
*/
private static function parseAlpha($input, &$result)
{
if ($input === null ||
$input === ''
) {
$result = 1;
return true;
}
if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)(%?)\\s*$/', $input, $matches)) {
$result = floatval($matches[1]);
// Percentage
if ($matches[2] !== '') {
$result = $result / 100;
}
if ($result < 0) $result = 0;
if ($result > 1) $result = 1;
return true;
}
return false;
}
/**
* Parses an RGB component.
*
* @param string $input Input string.
* @param float $result Hue in range [0, 255].
*
* @return boolean
*/
private static function parseRgbComponent($input, &$result)
{
if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)(%?)\\s*$/', $input, $matches)) {
$result = floatval($matches[1]);
if ($matches[2] === '%') {
$result = 255 * $result / 100;
}
$result = (int)$result;
if ($result < 0) $result = 0;
if ($result > 255) $result = 255;
return true;
}
return false;
}
/**
* Parses a hue component.
*
* @param string $input Input string.
* @param float $result Hue in range [0, 1].
*
* @return boolean
*/
private static function parseHue($input, &$result)
{
if (preg_match(
'/^\s*(\d*(?:\.\d*)?)(deg|grad|rad|turn|)\s*$/',
$input, $matches)
) {
$result = floatval($matches[1]);
// Percentage
switch ($matches[2]) {
case "grad":
// Gradians: range 0 - 400
$result = $result / 400;
break;
case "rad":
// Radians: range 0 - 2pi
$result = $result / M_PI / 2;
break;
case "turn":
// Turns: range 0 - 1
$result = $result;
break;
default:
// Degree: range 0 - 360
$result = $result / 360;
break;
}
$result = fmod($result, 1);
if ($result < 0) {
$result += 1;
}
return true;
}
return false;
}
/**
* Parses a hex color string.
*
* @param string $input Input string.
* @param float $result Hue in range [0, 1].
*
* @return boolean
*/
private static function parseHexColor($input, &$result)
{
if ($input[0] === '#') {
$input = substr($input, 1);
}
// intval does not support unsigned 32-bit integers
// so we need to parse large numbers stepwise
$numeric24bit = intval(substr($input, 0, 6), 16);
$alpha8bit = intval(substr($input, 6, 2), 16);
switch (strlen($input)) {
case 3:
$result = self::fromRgb(
(($numeric24bit & 0xf00) >> 8) |
(($numeric24bit & 0xf00) >> 4),
(($numeric24bit & 0x0f0) >> 4) |
(($numeric24bit & 0x0f0)),
(($numeric24bit & 0x00f) << 4) |
(($numeric24bit & 0x00f))
);
return true;
case 4:
$result = self::fromRgb(
(($numeric24bit & 0xf000) >> 12) |
(($numeric24bit & 0xf000) >> 8),
(($numeric24bit & 0x0f00) >> 8) |
(($numeric24bit & 0x0f00) >> 4),
(($numeric24bit & 0x00f0) >> 4) |
(($numeric24bit & 0x00f0)),
(($numeric24bit & 0x000f) << 4) |
(($numeric24bit & 0x000f))
);
return true;
case 6:
$result = self::fromRgb(
0xff & ($numeric24bit >> 16),
0xff & ($numeric24bit >> 8),
0xff & ($numeric24bit)
);
return true;
case 8:
$result = self::fromRgb(
0xff & ($numeric24bit >> 16),
0xff & ($numeric24bit >> 8),
0xff & ($numeric24bit),
0xff & ($alpha8bit)
);
return true;
}
return false;
}
/**
* Looks up a named color to a Color instance.
*
* @param string $input Input string.
*
* @return \Jdenticon\Color
*/
private static function parseNamedColor($input)
{
// Source: https://www.w3.org/TR/css-color-4/#named-colors
switch ($input) {
case 'aliceblue': return self::fromRgb(240,248,255);
case 'antiquewhite': return self::fromRgb(250,235,215);
case 'aqua': return self::fromRgb(0,255,255);
case 'aquamarine': return self::fromRgb(127,255,212);
case 'azure': return self::fromRgb(240,255,255);
case 'beige': return self::fromRgb(245,245,220);
case 'bisque': return self::fromRgb(255,228,196);
case 'black': return self::fromRgb(0,0,0);
case 'blanchedalmond': return self::fromRgb(255,235,205);
case 'blue': return self::fromRgb(0,0,255);
case 'blueviolet': return self::fromRgb(138,43,226);
case 'brown': return self::fromRgb(165,42,42);
case 'burlywood': return self::fromRgb(222,184,135);
case 'cadetblue': return self::fromRgb(95,158,160);
case 'chartreuse': return self::fromRgb(127,255,0);
case 'chocolate': return self::fromRgb(210,105,30);
case 'coral': return self::fromRgb(255,127,80);
case 'cornflowerblue': return self::fromRgb(100,149,237);
case 'cornsilk': return self::fromRgb(255,248,220);
case 'crimson': return self::fromRgb(220,20,60);
case 'cyan': return self::fromRgb(0,255,255);
case 'darkblue': return self::fromRgb(0,0,139);
case 'darkcyan': return self::fromRgb(0,139,139);
case 'darkgoldenrod': return self::fromRgb(184,134,11);
case 'darkgray': return self::fromRgb(169,169,169);
case 'darkgreen': return self::fromRgb(0,100,0);
case 'darkgrey': return self::fromRgb(169,169,169);
case 'darkkhaki': return self::fromRgb(189,183,107);
case 'darkmagenta': return self::fromRgb(139,0,139);
case 'darkolivegreen': return self::fromRgb(85,107,47);
case 'darkorange': return self::fromRgb(255,140,0);
case 'darkorchid': return self::fromRgb(153,50,204);
case 'darkred': return self::fromRgb(139,0,0);
case 'darksalmon': return self::fromRgb(233,150,122);
case 'darkseagreen': return self::fromRgb(143,188,143);
case 'darkslateblue': return self::fromRgb(72,61,139);
case 'darkslategray': return self::fromRgb(47,79,79);
case 'darkslategrey': return self::fromRgb(47,79,79);
case 'darkturquoise': return self::fromRgb(0,206,209);
case 'darkviolet': return self::fromRgb(148,0,211);
case 'deeppink': return self::fromRgb(255,20,147);
case 'deepskyblue': return self::fromRgb(0,191,255);
case 'dimgray': return self::fromRgb(105,105,105);
case 'dimgrey': return self::fromRgb(105,105,105);
case 'dodgerblue': return self::fromRgb(30,144,255);
case 'firebrick': return self::fromRgb(178,34,34);
case 'floralwhite': return self::fromRgb(255,250,240);
case 'forestgreen': return self::fromRgb(34,139,34);
case 'fuchsia': return self::fromRgb(255,0,255);
case 'gainsboro': return self::fromRgb(220,220,220);
case 'ghostwhite': return self::fromRgb(248,248,255);
case 'gold': return self::fromRgb(255,215,0);
case 'goldenrod': return self::fromRgb(218,165,32);
case 'gray': return self::fromRgb(128,128,128);
case 'green': return self::fromRgb(0,128,0);
case 'greenyellow': return self::fromRgb(173,255,47);
case 'grey': return self::fromRgb(128,128,128);
case 'honeydew': return self::fromRgb(240,255,240);
case 'hotpink': return self::fromRgb(255,105,180);
case 'indianred': return self::fromRgb(205,92,92);
case 'indigo': return self::fromRgb(75,0,130);
case 'ivory': return self::fromRgb(255,255,240);
case 'khaki': return self::fromRgb(240,230,140);
case 'lavender': return self::fromRgb(230,230,250);
case 'lavenderblush': return self::fromRgb(255,240,245);
case 'lawngreen': return self::fromRgb(124,252,0);
case 'lemonchiffon': return self::fromRgb(255,250,205);
case 'lightblue': return self::fromRgb(173,216,230);
case 'lightcoral': return self::fromRgb(240,128,128);
case 'lightcyan': return self::fromRgb(224,255,255);
case 'lightgoldenrodyellow': return self::fromRgb(250,250,210);
case 'lightgray': return self::fromRgb(211,211,211);
case 'lightgreen': return self::fromRgb(144,238,144);
case 'lightgrey': return self::fromRgb(211,211,211);
case 'lightpink': return self::fromRgb(255,182,193);
case 'lightsalmon': return self::fromRgb(255,160,122);
case 'lightseagreen': return self::fromRgb(32,178,170);
case 'lightskyblue': return self::fromRgb(135,206,250);
case 'lightslategray': return self::fromRgb(119,136,153);
case 'lightslategrey': return self::fromRgb(119,136,153);
case 'lightsteelblue': return self::fromRgb(176,196,222);
case 'lightyellow': return self::fromRgb(255,255,224);
case 'lime': return self::fromRgb(0,255,0);
case 'limegreen': return self::fromRgb(50,205,50);
case 'linen': return self::fromRgb(250,240,230);
case 'magenta': return self::fromRgb(255,0,255);
case 'maroon': return self::fromRgb(128,0,0);
case 'mediumaquamarine': return self::fromRgb(102,205,170);
case 'mediumblue': return self::fromRgb(0,0,205);
case 'mediumorchid': return self::fromRgb(186,85,211);
case 'mediumpurple': return self::fromRgb(147,112,219);
case 'mediumseagreen': return self::fromRgb(60,179,113);
case 'mediumslateblue': return self::fromRgb(123,104,238);
case 'mediumspringgreen': return self::fromRgb(0,250,154);
case 'mediumturquoise': return self::fromRgb(72,209,204);
case 'mediumvioletred': return self::fromRgb(199,21,133);
case 'midnightblue': return self::fromRgb(25,25,112);
case 'mintcream': return self::fromRgb(245,255,250);
case 'mistyrose': return self::fromRgb(255,228,225);
case 'moccasin': return self::fromRgb(255,228,181);
case 'navajowhite': return self::fromRgb(255,222,173);
case 'navy': return self::fromRgb(0,0,128);
case 'oldlace': return self::fromRgb(253,245,230);
case 'olive': return self::fromRgb(128,128,0);
case 'olivedrab': return self::fromRgb(107,142,35);
case 'orange': return self::fromRgb(255,165,0);
case 'orangered': return self::fromRgb(255,69,0);
case 'orchid': return self::fromRgb(218,112,214);
case 'palegoldenrod': return self::fromRgb(238,232,170);
case 'palegreen': return self::fromRgb(152,251,152);
case 'paleturquoise': return self::fromRgb(175,238,238);
case 'palevioletred': return self::fromRgb(219,112,147);
case 'papayawhip': return self::fromRgb(255,239,213);
case 'peachpuff': return self::fromRgb(255,218,185);
case 'peru': return self::fromRgb(205,133,63);
case 'pink': return self::fromRgb(255,192,203);
case 'plum': return self::fromRgb(221,160,221);
case 'powderblue': return self::fromRgb(176,224,230);
case 'purple': return self::fromRgb(128,0,128);
case 'rebeccapurple': return self::fromRgb(102,51,153);
case 'red': return self::fromRgb(255,0,0);
case 'rosybrown': return self::fromRgb(188,143,143);
case 'royalblue': return self::fromRgb(65,105,225);
case 'saddlebrown': return self::fromRgb(139,69,19);
case 'salmon': return self::fromRgb(250,128,114);
case 'sandybrown': return self::fromRgb(244,164,96);
case 'seagreen': return self::fromRgb(46,139,87);
case 'seashell': return self::fromRgb(255,245,238);
case 'sienna': return self::fromRgb(160,82,45);
case 'silver': return self::fromRgb(192,192,192);
case 'skyblue': return self::fromRgb(135,206,235);
case 'slateblue': return self::fromRgb(106,90,205);
case 'slategray': return self::fromRgb(112,128,144);
case 'slategrey': return self::fromRgb(112,128,144);
case 'snow': return self::fromRgb(255,250,250);
case 'springgreen': return self::fromRgb(0,255,127);
case 'steelblue': return self::fromRgb(70,130,180);
case 'tan': return self::fromRgb(210,180,140);
case 'teal': return self::fromRgb(0,128,128);
case 'thistle': return self::fromRgb(216,191,216);
case 'tomato': return self::fromRgb(255,99,71);
case 'transparent': return self::fromRgb(0,0,0,0);
case 'turquoise': return self::fromRgb(64,224,208);
case 'violet': return self::fromRgb(238,130,238);
case 'wheat': return self::fromRgb(245,222,179);
case 'white': return self::fromRgb(255,255,255);
case 'whitesmoke': return self::fromRgb(245,245,245);
case 'yellow': return self::fromRgb(255,255,0);
case 'yellowgreen': return self::fromRgb(154,205,50);
default: return null;
}
}
}

View file

@ -0,0 +1,492 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon;
use Jdenticon\IdenticonStyle;
use Jdenticon\Rendering\Rectangle;
use Jdenticon\Rendering\RendererInterface;
use Jdenticon\Rendering\IconGenerator;
use Jdenticon\Rendering\InternalPngRenderer;
use Jdenticon\Rendering\ImagickRenderer;
use Jdenticon\Rendering\SvgRenderer;
/**
* Represents an identicon and its style. This is the entry class to Jdenticon.
*/
class Identicon
{
/**
* @var mixed
*/
private $value;
/**
* @var boolean
*/
private $valueSet = false;
/**
* Defaults to hash of an empty string.
*
* @var string
*/
private $hash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
/**
* @var integer
*/
private $size = 100;
/**
* @var Jdenticon\Rendering\IconGenerator
*/
private $iconGenerator;
/**
* @var Jdenticon\IdenticonStyle
*/
private $style;
/**
* @var bool
*/
private $enableImageMagick;
/**
* Creates an Identicon instance with the specified hash.
*
* @param string $hash A binary string containing the hash that will be used
* as base for this icon. The hash must contain at least 6 bytes.
* @param int|float|double $size The size of the icon in pixels (the icon
* is quadratic).
*/
public function __construct($options = null)
{
$this->iconGenerator = IconGenerator::getDefaultGenerator();
if ($options !== null) {
$this->setOptions($options);
}
if ($this->style === null) {
$this->style = new IdenticonStyle();
}
}
/**
* Creates an Identicon instance from a specified hash.
*
* @param string $hash A binary string containing the hash that will be used
* as base for this icon. The hash must contain at least 6 bytes.
* @param int $size The size of the icon in pixels (the icon is quadratic).
* @return \Jdenticon\Identicon
*/
public static function fromHash($hash, $size)
{
return new Identicon(array('hash' => $hash, 'size' => $size));
}
/**
* Creates an Identicon instance from a specified value.
*
* @param mixed $value The value that will be used as base for this icon.
* The value will be converted to a UTF8 encoded string and then hashed
* using SHA1.
* @param int $size The size of the icon in pixels (the icon is quadratic).
* @return \Jdenticon\Identicon
*/
public static function fromValue($value, $size)
{
return new Identicon(array('value' => $value, 'size' => $size));
}
/**
* Gets an associative array of all options of this identicon.
*
* @return array
*/
public function getOptions()
{
$options = array();
if ($this->valueSet) {
$options['value'] = $this->getValue();
} elseif ($this->hash !== null) {
$options['hash'] = $this->getHash();
}
$options['size'] = $this->getSize();
$options['style'] = $this->getStyle()->getOptions();
if ($this->enableImageMagick !== null) {
$options['enableImageMagick'] = $this->getEnableImageMagick();
}
if ($this->iconGenerator !== IconGenerator::getDefaultGenerator()) {
$options['iconGenerator'] = $this->getIconGenerator();
}
return $options;
}
/**
* Sets options in this identicon by specifying an associative array of
* option values.
*
* @param array $options Options to set.
* @return self
*/
public function setOptions(array $options)
{
foreach ($options as $key => $value) {
$this->__set($key, $value);
}
return $this;
}
public function __get($name)
{
switch (strtolower($name)) {
case 'size':
return $this->getSize();
case 'hash':
return $this->getHash();
case 'value':
return $this->getValue();
case 'style':
return $this->getStyle();
case 'icongenerator':
return $this->getIconGenerator();
case 'enableimagemagick':
return $this->getEnableImageMagick();
default:
throw new \InvalidArgumentException(
"Unknown Identicon option '$name'.");
}
}
public function __set($name, $value)
{
switch (strtolower($name)) {
case 'size':
$this->setSize($value);
break;
case 'hash':
$this->setHash($value);
break;
case 'value':
$this->setValue($value);
break;
case 'style':
$this->setStyle($value);
break;
case 'icongenerator':
$this->setIconGenerator($value);
break;
case 'enableimagemagick':
$this->setEnableImageMagick($value);
break;
default:
throw new \InvalidArgumentException(
"Unknown Identicon option '$name'.");
}
}
/**
* Gets the size of the icon in pixels.
*/
public function getSize()
{
return $this->size;
}
/**
* Sets the size of this icon in pixels.
*
* @param int|float|double $size The width and height of the icon.
*/
public function setSize($size)
{
if (!is_numeric($size) || $size < 1) {
throw new \InvalidArgumentException(
"An invalid identicon size was specified. ".
"A numeric value >= 1 was expected. Specified value: $size.");
}
$this->size = (int)$size;
}
/**
* Gets the size of the icon in pixels.
*/
public function getEnableImageMagick()
{
// Enable ImageMagick on PHP < 7. On PHP 7 the performance increase
// is not as obvious as on PHP 5. Since the ImageMagick renderer has a
// lot of quirks, we don't want to use it unless really needed.
if ($this->enableImageMagick === null) {
return PHP_MAJOR_VERSION < 7 && extension_loaded('imagick');
}
return $this->enableImageMagick;
}
/**
* Sets whether ImageMagick should be used to generate PNG icons.
*
* @param bool $enable true to enable ImageMagick.
*/
public function setEnableImageMagick($enable)
{
if (!is_bool($enable)) {
throw new \InvalidArgumentException(
"enableImageMagick can only assume boolean values. Specified value: $enable.");
}
// Verify that the Imagick extension is installed
if ($enable && !extension_loaded('imagick')) {
throw new \Exception(
'Failed to enable ImageMagick. '.
'The Imagick PHP extension was not found on this system.');
}
$this->enableImageMagick = $enable;
}
/**
* Gets the {@see IconGenerator} used to generate icons.
*
* @return \Jdenticon\Rendering\IconGenerator
*/
public function getIconGenerator()
{
return $this->iconGenerator;
}
/**
* Sets the {@see IconGenerator} used to generate icons.
*
* @param \Jdenticon\Rendering\IconGenerator $iconGenerator Icon generator
* that will render the shapes of the identicon.
* @return \Jdenticon\Identicon
*/
public function setIconGenerator(IconGenerator $iconGenerator)
{
if ($iconGenerator === null) {
$iconGenerator = IconGenerator::getDefaultGenerator();
}
$this->iconGenerator = $iconGenerator;
return $this;
}
/**
* Gets or sets the style of the icon.
*
* @return \Jdenticon\IdenticonStyle
*/
public function getStyle()
{
return $this->style;
}
/**
* Gets or sets the style of the icon.
*
* @param array|\Jdenticon\IdenticonStyle $style The new style of the icon.
* NULL will revert the identicon to use the default style.
* @return self
*/
public function setStyle($style)
{
if ($style == null) {
$this->style = new IdenticonStyle();
} elseif ($style instanceof IdenticonStyle) {
$this->style = $style;
} elseif (is_array($style)) {
$this->style = new IdenticonStyle($style);
} else {
throw new \InvalidArgumentException(
"Invalid indenticon style was specified. ".
"Allowed values are IdenticonStyle instances and associative ".
"arrays containing IdenticonStyle options.");
}
return $this;
}
/**
* Gets a binary string containing the hash that is used as base for this
* icon.
*/
public function getHash()
{
return $this->hash;
}
/**
* Sets a binary string containing the hash that is used as base for this
* icon. The string should contain at least 6 bytes.
*
* @param string $hash Binary string containing the hash.
*/
public function setHash($hash)
{
if (!is_string($hash)) {
throw new \InvalidArgumentException(
'An invalid $hash was passed to Identicon. ' .
'A binary string was expected.');
}
if (strlen($hash) < 6) {
throw new \InvalidArgumentException(
'An invalid $hash was passed to Identicon. ' .
'The hash was expected to contain at least 6 bytes.');
}
$this->hash = $hash;
$this->value = null;
$this->valueSet = false;
return $this;
}
/**
* Gets a binary string containing the hash that is used as base for this
* icon.
*/
public function getValue()
{
return $this->value;
}
/**
* Sets a value that will be used as base for this icon. The value will
* be converted to a string and then hashed using SHA1.
*
* @param mixed $value Value that will be hashed.
*/
public function setValue($value)
{
$this->hash = sha1("$value");
$this->value = $value;
$this->valueSet = true;
return $this;
}
/**
* Gets the bounds of the icon excluding its padding.
*
* @return \Jdenticon\Rendering\Rectangle
*/
public function getIconBounds()
{
// Round padding to nearest integer
$padding = (int)($this->style->getPadding() * $this->size + 0.5);
return new Rectangle(
$padding, $padding,
$this->size - $padding * 2,
$this->size - $padding * 2);
}
private function getRenderer($imageFormat)
{
switch (strtolower($imageFormat)) {
case 'svg':
return new SvgRenderer($this->size, $this->size);
default:
return $this->getEnableImageMagick() ?
new ImagickRenderer($this->size, $this->size) :
new InternalPngRenderer($this->size, $this->size);
}
}
/**
* Draws this icon using a specified renderer.
*
* This method is only intended for usage with custom renderers. A custom
* renderer could as an example render an Identicon in a file format not
* natively supported by Jdenticon. To implement a new file format,
* implement {@see \Jdenticon\Rendering\RendererInterface}.
*
* @param \Jdenticon\Rendering\RendererInterface $renderer The renderer used
* to render this icon.
* @param \Jdenticon\Rendering\Rectangle $rect The bounds of the rendered
* icon. No padding will be applied to the rectangle. If the parameter
* is omitted, the rectangle is calculated from the current icon
* size and padding.
*/
public function draw(
\Jdenticon\Rendering\RendererInterface $renderer,
\Jdenticon\Rendering\Rectangle $rect = null)
{
if ($rect === null) {
$rect = $this->getIconBounds();
}
$this->iconGenerator->generate(
$renderer, $rect, $this->style, $this->hash);
}
/**
* Renders the icon directly to the page output.
*
* The method will set the 'Content-Type' HTTP header. You are recommended
* to set an appropriate 'Cache-Control' header before calling this method
* to ensure the icon is cached client side.
*
* @param string $imageFormat The image format of the output.
* Supported values are 'png' and 'svg'.
*/
public function displayImage($imageFormat = 'png')
{
$renderer = $this->getRenderer($imageFormat);
$this->draw($renderer, $this->getIconBounds());
$mimeType = $renderer->getMimeType();
$data = $renderer->getData();
header("Content-Type: $mimeType");
echo $data;
}
/**
* Renders the icon to a binary string.
*
* @param string $imageFormat The image format of the output string.
* Supported values are 'png' and 'svg'.
* @return string
*/
public function getImageData($imageFormat = 'png')
{
$renderer = $this->getRenderer($imageFormat);
$this->draw($renderer, $this->getIconBounds());
return $renderer->getData();
}
/**
* Renders the icon as a data URI. It is recommended to avoid using this
* method unless really necessary, since it will effectively disable client
* caching of generated icons, and will also cause the same icon to be
* rendered multiple times, when used multiple times on a single page.
*
* @param string $imageFormat The image format of the data URI.
* Supported values are 'png' and 'svg'.
* @return string
*/
public function getImageDataUri($imageFormat = 'png')
{
$renderer = $this->getRenderer($imageFormat);
$this->draw($renderer, $this->getIconBounds());
$mimeType = $renderer->getMimeType();
$base64 = base64_encode($renderer->getData());
return "data:$mimeType;base64,$base64";
}
}

View file

@ -0,0 +1,460 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon;
use Jdenticon\Color;
/**
* Specifies the color style of an identicon.
*/
class IdenticonStyle
{
/**
* @var \Jdenticon\Color
*/
private $backgroundColor;
/**
* @var float
*/
private $padding;
/**
* @var float
*/
private $colorSaturation;
/**
* @var float
*/
private $grayscaleSaturation;
/**
* @var array(float)
*/
private $colorLightness;
/**
* @var array(float)
*/
private $grayscaleLightness;
/**
* @var array(integer)
*/
private $hues;
public function __construct(array $options = null)
{
$this->backgroundColor = self::getDefaultBackgroundColor();
$this->padding = self::getDefaultPadding();
$this->colorSaturation = self::getDefaultColorSaturation();
$this->grayscaleSaturation = self::getDefaultGrayscaleSaturation();
$this->colorLightness = self::getDefaultColorLightness();
$this->grayscaleLightness = self::getDefaultGrayscaleLightness();
if ($options !== null) {
$this->setOptions($options);
}
}
/**
* Gets an associative array of all options of this style.
*
* @return array
*/
public function getOptions()
{
$options = array();
$options['backgroundColor'] = $this->getBackgroundColor()->__toString();
$options['padding'] = $this->getPadding();
$options['colorSaturation'] = $this->getColorSaturation();
$options['grayscaleSaturation'] = $this->getGrayscaleSaturation();
$options['colorLightness'] = $this->getColorLightness();
$options['grayscaleLightness'] = $this->getGrayscaleLightness();
if ($this->hues !== null) {
$options['hues'] = $this->getHues();
}
return $options;
}
/**
* Sets options in this style by specifying an associative array of option
* values.
*
* @param array $options Options to set.
* @return self
*/
public function setOptions(array $options)
{
foreach ($options as $key => $value) {
$this->__set($key, $value);
}
return $this;
}
public function __get($name)
{
switch (strtolower($name)) {
case 'backgroundcolor':
return $this->getBackgroundColor();
case 'padding':
return $this->getPadding();
case 'colorsaturation':
return $this->getColorSaturation();
case 'grayscalesaturation':
return $this->getGrayscaleSaturation();
case 'colorlightness':
return $this->getColorLightness();
case 'grayscalelightness':
return $this->getGrayscaleLightness();
case 'hues':
return $this->getHues();
default:
throw new \InvalidArgumentException(
"Unknown IdenticonStyle option '$name'.");
}
}
public function __set($name, $value)
{
switch (strtolower($name)) {
case 'backgroundcolor':
$this->setBackgroundColor($value);
break;
case 'padding':
$this->setPadding($value);
break;
case 'colorsaturation':
$this->setColorSaturation($value);
break;
case 'grayscalesaturation':
$this->setGrayscaleSaturation($value);
break;
case 'colorlightness':
$this->setColorLightness($value);
break;
case 'grayscalelightness':
$this->setGrayscaleLightness($value);
break;
case 'hues':
$this->setHues($value);
break;
default:
throw new \InvalidArgumentException(
"Unknown IdenticonStyle option '$name'.");
}
}
/**
* Normalizes a hue to the first turn [0, 360).
*
* @param mixed $hue
* @return integer
*/
private static function normalizeHue($hue)
{
if (!is_numeric($hue)) {
throw new \InvalidArgumentException(
"'$hue' is not a valid hue.");
}
$hue = $hue % 360;
if ($hue < 0) {
$hue += 360;
}
return $hue;
}
/**
* Gets an array of allowed hues, or null if there are no restrictions.
*
* @return array(int)|null
*/
public function getHues()
{
return $this->hues;
}
/**
* Sets the allowed hues of generated icons.
*
* @param array(integer)|integer|null $value A hue specified in degrees,
* or an array of hues specified in degrees. If set to null, the hue
* list is cleared.
* @return self
*/
public function setHues($value)
{
$hues = array();
if ($value !== null) {
if (is_array($value)) {
foreach ($value as $hue) {
$hues[] = self::normalizeHue($hue);
}
} else {
$hues[] = self::normalizeHue($value);
}
}
$this->hues = empty($hues) ? null : $hues;
return $this;
}
/**
* Gets the padding of an icon in percents in the range [0.0, 0.4].
*
* @return float
*/
public function getPadding()
{
return $this->padding;
}
/**
* Sets the padding of an icon in percents.
*
* @param float $value New padding in the range [0.0, 0.4].
* @return self
*/
public function setPadding($value)
{
if (!is_numeric($value) || $value < 0 || $value > 0.4) {
throw new \InvalidArgumentException(
"Padding '$value' out of range. ".
"Values in the range [0.0, 0.4] are allowed.");
}
$this->padding = (float)$value;
return $this;
}
/**
* Gets the color of the identicon background.
*
* @return \Jdenticon\Color
*/
public function getBackgroundColor()
{
return $this->backgroundColor;
}
/**
* Sets the color of the identicon background.
*
* @param \Jdenticon\Color|string $value New background color.
* @return \Jdenticon\IdenticonStyle
*/
public function setBackgroundColor($value)
{
if ($value instanceof Color) {
$this->backgroundColor = $value;
} else {
$this->backgroundColor = Color::parse($value);
}
return $this;
}
/**
* Gets the saturation of the originally grayscale identicon shapes.
*
* @return float Saturation in the range [0.0, 1.0].
*/
public function getGrayscaleSaturation()
{
return $this->grayscaleSaturation;
}
/**
* Sets the saturation of the originally grayscale identicon shapes.
*
* @param $value float Saturation in the range [0.0, 1.0].
* @return self
*/
public function setGrayscaleSaturation($value)
{
if (!is_numeric($value) ||
$value < 0 || $value > 1
) {
throw new \InvalidArgumentException(
"The grayscale saturation was invalid. ".
"Only values in the range [0.0, 1.0] are allowed.");
}
$this->grayscaleSaturation = (float)$value;
return $this;
}
/**
* Gets the saturation of the colored identicon shapes.
*
* @return float Saturation in the range [0.0, 1.0].
*/
public function getColorSaturation()
{
return $this->colorSaturation;
}
/**
* Sets the saturation of the colored identicon shapes.
*
* @param $value float Saturation in the range [0.0, 1.0].
* @return self
*/
public function setColorSaturation($value)
{
if (!is_numeric($value) ||
$value < 0 || $value > 1
) {
throw new \InvalidArgumentException(
"The color saturation was invalid. ".
"Only values in the range [0.0, 1.0] are allowed.");
}
$this->colorSaturation = (float)$value;
return $this;
}
/**
* Gets the value of the ColorLightness property.
*
* @return array(float, float)
*/
public function getColorLightness()
{
return $this->colorLightness;
}
/**
* Sets the value of the ColorLightness property.
*
* @param $value array(float, float) Lightness range.
* @return self
*/
public function setColorLightness($value)
{
if (!is_array($value) ||
!array_key_exists(0, $value) ||
!array_key_exists(1, $value) ||
!is_numeric($value[0]) ||
!is_numeric($value[1]) ||
$value[0] < 0 || $value[0] > 1 ||
$value[1] < 0 || $value[1] > 1
) {
throw new \InvalidArgumentException(
"The value passed to setColorLightness was invalid. ".
"Please check the documentation.");
}
$this->colorLightness = array((float)$value[0], (float)$value[1]);
return $this;
}
/**
* Gets the value of the GrayscaleLightness property.
*
* @return array(float, float)
*/
public function getGrayscaleLightness()
{
return $this->grayscaleLightness;
}
/**
* Sets the value of the GrayscaleLightness property.
*
* @param $value array(float, float) Lightness range.
* @return self
*/
public function setGrayscaleLightness($value)
{
if (!is_array($value) ||
!array_key_exists(0, $value) ||
!array_key_exists(1, $value) ||
!is_numeric($value[0]) ||
!is_numeric($value[1]) ||
$value[0] < 0 || $value[0] > 1 ||
$value[1] < 0 || $value[1] > 1
) {
throw new \InvalidArgumentException(
"The value passed to setGrayscaleLightness was invalid. ".
"Please check the documentation.");
}
$this->grayscaleLightness = array((float)$value[0], (float)$value[1]);
return $this;
}
/**
* Gets the default value of the BackgroundColor property. Resolves to transparent.
*
* @return \Jdenticon\Color
*/
public static function getDefaultBackgroundColor()
{
return Color::fromRgb(255, 255, 255, 255);
}
/**
* Gets the default value of the Padding property. Resolves to 0.08.
*
* @return float
*/
public static function getDefaultPadding()
{
return 0.08;
}
/**
* Gets the default value of the ColorSaturation property. Resolves to 0.5.
*
* @return float
*/
public static function getDefaultColorSaturation()
{
return 0.5;
}
/**
* Gets the default value of the GrayscaleSaturation property. Resolves to 0.
*
* @return float
*/
public static function getDefaultGrayscaleSaturation()
{
return 0;
}
/**
* Gets the default value of the ColorLightness property. Resolves to [0.4, 0.8].
*
* @return array
*/
public static function getDefaultColorLightness()
{
return array(0.4, 0.8);
}
/**
* Gets the default value of the GrayscaleLightness property. Resolves to [0.3, 0.9].
*
* @return array
*/
public static function getDefaultGrayscaleLightness()
{
return array(0.3, 0.9);
}
}

View file

@ -0,0 +1,209 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
/**
* Base class for rendering shapes in an identicon. Implement this class to e.g.
* support a new file format that is not natively supported by Jdenticon. To
* invoke the new Renderer, pass the renderer as an argument to the
* {@see \Jdenticon\Identicon::Draw} method.
*/
abstract class AbstractRenderer implements RendererInterface
{
private $transform;
protected $backgroundColor;
public function __construct()
{
$this->transform = Transform::getEmpty();
}
/**
* Sets the current transform that will be applied on all coordinates before
* being rendered to the target image.
*
* @param \Jdenticon\Rendering\Transform $transform The transform to set.
* If NULL is specified any existing transform is removed.
*/
public function setTransform(\Jdenticon\Rendering\Transform $transform)
{
$this->transform = $transform === null ?
Transform::getEmpty() : $transform;
}
/**
* Gets the current transform that will be applied on all coordinates before
* being rendered to the target image.
*
* @return \Jdenticon\Rendering\Transform
*/
public function getTransform()
{
return $this->transform;
}
/**
* Adds a polygon without translating its coordinates.
*
* @param array $points An array of the points that the polygon consists of.
*/
abstract protected function addPolygonNoTransform($points);
/**
* Adds a circle without translating its coordinates.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $counterClockwise If true the circle will be drawn
* counter clockwise.
*/
abstract protected function addCircleNoTransform($x, $y, $size, $counterClockwise);
/**
* Sets the background color of the image.
*
* @param \Jdenticon\Color $color The image background color.
*/
public function setBackgroundColor(\Jdenticon\Color $color)
{
$this->backgroundColor = $color;
}
/**
* Gets the background color of the image.
*
* @return \Jdenticon\Color
*/
public function getBackgroundColor()
{
return $this->backgroundColor;
}
private function addPolygonCore(array $points, $invert)
{
$transformedPoints = array();
foreach ($points as $point) {
$transformedPoints[] =
$this->transform->transformPoint($point->x, $point->y);
}
if ($invert) {
$transformedPoints = array_reverse($transformedPoints);
}
//var_dump($transformedPoints);
$this->addPolygonNoTransform($transformedPoints);
}
/**
* Adds a rectangle to the image.
*
* @param float $x The x-coordinate of the rectangle upper-left corner.
* @param float $y The y-coordinate of the rectangle upper-left corner.
* @param float $width The width of the rectangle.
* @param float $height The height of the rectangle.
* @param bool $invert If true the area of the rectangle will be removed
* from the filled area.
*/
public function addRectangle($x, $y, $width, $height, $invert = false)
{
$this->addPolygonCore(array(
new Point($x, $y),
new Point($x + $width, $y),
new Point($x + $width, $y + $height),
new Point($x, $y + $height),
), $invert);
}
/**
* Adds a circle to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $invert If true the area of the circle will be removed
* from the filled area.
*/
public function addCircle($x, $y, $size, $invert = false)
{
$northWest = $this->transform->transformPoint($x, $y, $size, $size);
$this->addCircleNoTransform($northWest->x, $northWest->y, $size, $invert);
}
/**
* Adds a polygon to the image.
*
* @param array $points Array of points that the polygon consists of.
* @param bool $invert If true the area of the polygon will be removed
* from the filled area.
*/
public function addPolygon($points, $invert = false)
{
$this->addPolygonCore($points, $invert);
}
/**
* Adds a triangle to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $width The width of the bounding rectangle.
* @param float $height The height of the bounding rectangle.
* @param float $direction The direction of the 90 degree corner of the
* triangle.
* @param bool $invert If true the area of the triangle will be removed
* from the filled area.
*/
public function addTriangle($x, $y, $width, $height, $direction, $invert = false)
{
$points = array(
new Point($x + $width, $y),
new Point($x + $width, $y + $height),
new Point($x, $y + $height),
new Point($x, $y)
);
array_splice($points, $direction, 1);
$this->addPolygonCore($points, $invert);
}
/**
* Adds a rhombus to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $width The width of the bounding rectangle.
* @param float $height The height of the bounding rectangle.
* @param bool $invert If true the area of the rhombus will be removed
* from the filled area.
*/
public function addRhombus($x, $y, $width, $height, $invert = false)
{
$this->addPolygonCore(array(
new Point($x + $width / 2, $y),
new Point($x + $width, $y + $height / 2),
new Point($x + $width / 2, $y + $height),
new Point($x, $y + $height / 2),
), $invert);
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
use Jdenticon\Color;
/**
* Specifies the colors to be used in an identicon.
*/
class ColorTheme
{
private $darkGray;
private $midColor;
private $lightGray;
private $lightColor;
private $darkColor;
/**
* Creates a new ColorTheme.
*
* @param float $hue The hue of the colored shapes in the range [0, 1].
* @param \Jdenticon\IdenticonStyle $style The style that specifies the
* lightness and saturation of the icon.
*/
public function __construct($hue, \Jdenticon\IdenticonStyle $style)
{
$grayscaleLightness = $style->getGrayscaleLightness();
$colorLightness = $style->getColorLightness();
$hues = $style->getHues();
if ($hues !== null) {
// $hue is in the range [0, 1]
// Multiply with 0.999 to change the range to [0, 1)
$hueIndex = (int)($hue * 0.999 * count($hues));
$hue = (float)$hues[$hueIndex] / 360;
}
$this->darkGray = Color::fromHslCompensated(
$hue, $style->getGrayscaleSaturation(), $grayscaleLightness[0]);
$this->midColor = Color::fromHslCompensated(
$hue, $style->getColorSaturation(), ($colorLightness[0] + $colorLightness[1]) / 2);
$this->lightGray = Color::fromHslCompensated(
$hue, $style->getGrayscaleSaturation(), $grayscaleLightness[1]);
$this->lightColor = Color::fromHslCompensated(
$hue, $style->getColorSaturation(), $colorLightness[1]);
$this->darkColor = Color::fromHslCompensated(
$hue, $style->getColorSaturation(), $colorLightness[0]);
}
/**
* Gets a color from this color theme by index.
*
* @param int $index Color index in the range [0, getCount()).
* @return Jdenticon\Color
*/
public function getByIndex($index)
{
if ($index === 0) return $this->darkGray;
if ($index === 1) return $this->midColor;
if ($index === 2) return $this->lightGray;
if ($index === 3) return $this->lightColor;
if ($index === 4) return $this->darkColor;
return null;
}
/**
* Gets the number of available colors in this theme.
*
* @return int
*/
public function getCount()
{
return 5;
}
}

View file

@ -0,0 +1,290 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
use Jdenticon\Shapes\Shape;
use Jdenticon\Shapes\ShapeCategory;
use Jdenticon\Shapes\ShapeDefinitions;
/**
* Generates identicons and render them to a
* {@link \Jdenticon\Rendering\RendererInterface}. This class dictates what
* shapes will be used in the generated icons. If you intend to customize the
* appearance of generated icons you probably wants to either subclass or modify
* this class.
*/
class IconGenerator
{
private $defaultShapes;
private static $instance;
protected function __construct()
{
$this->defaultShapes = array(
// Sides
new ShapeCategory(
/*$colorIndex=*/ 8,
/*$shapes=*/ ShapeDefinitions::getOuterShapes(),
/*$shapeIndex=*/ 2,
/*$rotationIndex=*/ 3,
/*$positions=*/ array(1,0, 2,0, 2,3, 1,3, 0,1, 3,1, 3,2, 0,2)
),
// Corners
new ShapeCategory(
/*$colorIndex=*/ 9,
/*$shapes=*/ ShapeDefinitions::getOuterShapes(),
/*$shapeIndex=*/ 4,
/*$rotationIndex=*/ 5,
/*$positions=*/ array(0,0, 3,0, 3,3, 0,3)
),
// Center
new ShapeCategory(
/*$colorIndex=*/ 10,
/*$shapes=*/ ShapeDefinitions::getCenterShapes(),
/*$shapeIndex=*/ 1,
/*$rotationIndex=*/ null,
/*$positions=*/ array(1,1, 2,1, 2,2, 1,2)
)
);
}
public static function getDefaultGenerator()
{
if (self::$instance === null) {
self::$instance = new IconGenerator();
}
return self::$instance;
}
/**
* Gets the number of cells in each direction of the icons generated by
* this IconGenerator.
*
* @return int
*/
public function getCellCount()
{
return 4;
}
/**
* Determines the hue to be used in an icon for the specified hash.
*
* @return float Hue in the range [0, 1].
*/
protected static function getHue($hash)
{
$value = hexdec(substr($hash, -7));
return $value / 0xfffffff;
}
/**
* Determines whether $newValue is duplicated in $source if all values
* in $duplicateValues are determined to be equal.
*
* @return bool
*/
private static function isDuplicate(
array $source, $newValue,
array $duplicateValues)
{
if (in_array($newValue, $duplicateValues, true)) {
foreach ($duplicateValues as $value) {
if (in_array($value, $source, true)) {
return true;
}
}
}
return false;
}
/**
* Gets the specified octet from a byte array.
*
* @param string $hash The hexstring from which the octet will be retrieved.
* @param int $index The zero-based index of the octet to be returned.
* @return int
*/
protected static function getOctet($hash, $index)
{
return hexdec($hash[$index]);
}
/**
* Gets an array of the shape categories to be rendered in icons generated
* by this IconGenerator.
*
* @return array
*/
protected function getCategories()
{
return $this->defaultShapes;
}
/**
* Gets an enumeration of individual shapes to be rendered in an icon for a
* specific hash.
*
* @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme
* specifying the colors to be used in the icon.
* @param string $hash The hash for which the shapes will be returned.
* @return array(Jdenticon\Shapes\Shape)
*/
protected function getShapes($colorTheme, $hash)
{
$usedColorThemeIndexes = array();
$categories = self::getCategories();
$shapes = array();
$colorCount = $colorTheme->getCount();
foreach ($categories as $category) {
$colorThemeIndex =
self::getOctet($hash, $category->colorIndex) % $colorCount;
if (self::isDuplicate(
// Disallow dark gray and dark color combo
$usedColorThemeIndexes, $colorThemeIndex, array(0, 4)) ||
self::isDuplicate(
// Disallow light gray and light color combo
$usedColorThemeIndexes, $colorThemeIndex, array(2, 3))
) {
$colorThemeIndex = 1;
}
$usedColorThemeIndexes[] = $colorThemeIndex;
$startRotationIndex = $category->rotationIndex === null ?
0 : self::getOctet($hash, $category->rotationIndex);
$shapeIndex =
self::getOctet($hash, $category->shapeIndex) %
count($category->shapes);
$shape = $category->shapes[$shapeIndex];
$shapes[] = new Shape(
/*$definition=*/ $shape,
/*$color=*/ $colorTheme->getByIndex($colorThemeIndex),
/*$positions=*/ $category->positions,
/*$startRotationIndex=*/ $startRotationIndex
);
}
return $shapes;
}
/**
* Creates a quadratic copy of the specified
* {@link \Jdenticon\Rendering\Rectangle} with a multiple of the cell count
* as size.
*
* @param \Jdenticon\Rendering\Rectangle $rect The rectangle to be
* normalized.
*/
protected function normalizeRectangle(\Jdenticon\Rendering\Rectangle $rect)
{
$size = (int)min($rect->width, $rect->height);
// Make size a multiple of the cell count
$size -= $size % $this->getCellCount();
return new Rectangle(
(int)($rect->x + ($rect->width - $size) / 2),
(int)($rect->y + ($rect->height - $size) / 2),
$size,
$size);
}
/**
* Renders the background of an icon.
*
* @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to
* be used for rendering the icon on the target surface.
* @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
* @param \Jdenticon\IdenticonStyle $style The style of the icon.
* @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme
* specifying the colors to be used in the icon.
* @param string $hash The hash to be used as basis for the generated icon.
*/
protected function renderBackground(
\Jdenticon\Rendering\RendererInterface $renderer,
\Jdenticon\Rendering\Rectangle $rect,
\Jdenticon\IdenticonStyle $style,
\Jdenticon\Rendering\ColorTheme $colorTheme,
$hash)
{
$renderer->setBackgroundColor($style->getBackgroundColor());
}
/**
* Renders the foreground of an icon.
*
* @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to
* be used for rendering the icon on the target surface.
* @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
* @param \Jdenticon\IdenticonStyle $style The style of the icon.
* @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme
* specifying the colors to be used in the icon.
* @param string $hash The hash to be used as basis for the generated icon.
*/
protected function renderForeground(
\Jdenticon\Rendering\RendererInterface $renderer,
\Jdenticon\Rendering\Rectangle $rect,
\Jdenticon\IdenticonStyle $style,
\Jdenticon\Rendering\ColorTheme $colorTheme,
$hash)
{
// Ensure rect is quadratic and a multiple of the cell count
$normalizedRect = $this->normalizeRectangle($rect);
$cellSize = $normalizedRect->width / $this->getCellCount();
foreach ($this->getShapes($colorTheme, $hash) as $shape) {
$rotation = $shape->startRotationIndex;
$renderer->beginShape($shape->color);
$positionCount = count($shape->positions);
for ($i = 0; $i + 1 < $positionCount; $i += 2) {
$renderer->setTransform(new Transform(
$normalizedRect->x + $shape->positions[$i + 0] * $cellSize,
$normalizedRect->y + $shape->positions[$i + 1] * $cellSize,
$cellSize, $rotation++ % 4));
$shape->definition->__invoke($renderer, $cellSize, $i / 2);
}
$renderer->endShape();
}
}
/**
* Generates an identicon for the specified hash.
*
* @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to
* be used for rendering the icon on the target surface.
* @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
* @param \Jdenticon\IdenticonStyle $style The style of the icon.
* @param string $hash The hash to be used as basis for the generated icon.
*/
public function generate(
\Jdenticon\Rendering\RendererInterface $renderer,
\Jdenticon\Rendering\Rectangle $rect,
\Jdenticon\IdenticonStyle $style,
$hash)
{
$hue = self::getHue($hash);
$colorTheme = new ColorTheme($hue, $style);
$this->renderBackground($renderer, $rect, $style, $colorTheme, $hash);
$this->renderForeground($renderer, $rect, $style, $colorTheme, $hash);
}
}

View file

@ -0,0 +1,296 @@
<?php
namespace Jdenticon\Rendering;
class ImagickRendererLine
{
/**
* Creates a new line from a vector.
*
* @param float $x0 Vector start x coordinate.
* @param float $y0 Vector start y coordinate.
* @param float $x1 Vector end x coordinate.
* @param float $y1 Vector end y coordinate.
*/
public static function fromVector($x0, $y0, $x1, $y1)
{
$line = new ImagickRendererLine();
$line->Px = $x0;
$line->Py = $y0;
$line->rx = $x1 - $x0;
$line->ry = $y1 - $y0;
return $line;
}
/**
* Moves the line to the right relative the direction vector.
*
* @param float $distance The number of pixels to move the line.
*/
public function moveRight($distance)
{
// Ortogonal direction vector
$rx = -$this->ry;
$ry = $this->rx;
$multiplier = $distance / sqrt($rx * $rx + $ry * $ry);
$this->Px += $rx * $multiplier;
$this->Py += $ry * $multiplier;
}
/**
* Computes the point at which two lines intersect.
*
* @return Point|null
*/
public static function intersection(
ImagickRendererLine $l1,
ImagickRendererLine $l2
) {
$rs = $l1->rx * $l2->ry - $l1->ry * $l2->rx;
if ($rs == 0) {
return null;
}
$u = (($l2->Px - $l1->Px) * $l1->ry - ($l2->Py - $l1->Py) * $l1->rx) / $rs;
return new Point(
$l2->Px + $u * $l2->rx,
$l2->Py + $u * $l2->ry
);
}
/**
* X coordiate of a point on the line.
* @var float
*/
public $Px;
/**
* Y coordiate of a point on the line.
* @var float
*/
public $Py;
/**
* X component of the direction vector.
* @var float
*/
public $rx;
/**
* Y component of the direction vector.
* @var float
*/
public $ry;
}
/**
* Renders icons as PNG using ImageMagick.
*
* Unfortunately the ImageMagick vector renderer has a lot of quirks that
* we need to accomodate. The most obvious is that the renderer always render
* all polygons 1/2 pixel too large. This behavior is documented here:
* http://www.imagemagick.org/Usage/draw/#bounds
*
* To prevent this we shrink all polygons with 1/2 pixels before passing them
* to ImageMagick.
*
* Another quirk is that if the polygon including the 1/2 pixel invisible border
* align perfectly to the pixel grid, white pixels will appear near edge
* intersections. Paths containing arcs will sometimes appear with horizontal
* lines drawn to the right side of the image.
*
* To prevent this (in most cases) we add 0.00013 to all coordinates.
*
*/
class ImagickRenderer extends AbstractRenderer
{
private $draw;
private $polygon;
private $width;
private $height;
/**
* This constant is added to all coordinates to avoid white pixels
* that sometimes appear near edge intersections when a polygon including
* its 1/2 invisible border is perfectly aligned to the pixel grid.
*/
const PREVENT_WHITE_PIXELS_OFFSET = -0.00013;
/**
* Creates an instance of the class ImagickRenderer.
*
* @param int $width The width of the icon in pixels.
* @param int $height The height of the icon in pixels.
*/
public function __construct($width, $height)
{
parent::__construct();
$this->draw = new \ImagickDraw();
$this->draw->setStrokeWidth(1);
$this->width = $width;
$this->height = $height;
}
/**
* Gets the MIME type of the renderer output.
*
* @return string
*/
public function getMimeType()
{
return 'image/png';
}
/**
* Adds a circle without translating its coordinates.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $counterClockwise If true the circle will be drawn
* counter clockwise.
*/
protected function addCircleNoTransform($x, $y, $size, $counterClockwise)
{
if ($counterClockwise) {
$x -= $size + 0.5;
$y -= 1;
} else {
$size -= 1;
$y -= 0.5;
}
$radius = $size / 2;
$this->draw->pathMoveToAbsolute(
$x + $size + self::PREVENT_WHITE_PIXELS_OFFSET,
$y + $radius + self::PREVENT_WHITE_PIXELS_OFFSET);
$this->draw->pathEllipticArcRelative($radius, $radius,
M_PI * 2, true, $counterClockwise, 0, 1);
$this->draw->pathClose();
}
/**
* Adds a polygon without translating its coordinates.
*
* @param array $points An array of the points that the polygon consists of.
*/
protected function addPolygonNoTransform($points)
{
$firstPoint = $points[0];
$lastPoint = end($points);
// Ensure polygon is closed
if ($firstPoint->x != $lastPoint->x ||
$firstPoint->y != $lastPoint->y
) {
$points[] = $firstPoint;
}
// Determine if polygon is an outer ring
// (source: https://stackoverflow.com/a/1165943)
$sum = 0;
$previousPoint = null;
foreach ($points as $point) {
if ($previousPoint !== null) {
$sum += ($point->x - $previousPoint->x) * ($point->y + $previousPoint->y);
}
$previousPoint = $point;
}
$isOuterRing = $sum < 0;
// ImageMagick draws all polygons 1 pixel too large. To prevent this,
// shrink polygons by 1 pixel.
$lines = array();
$previousPoint = null;
// Transform all edges to lines.
foreach ($points as $point) {
if ($previousPoint !== null) {
$lines[] = $line = ImagickRendererLine::fromVector(
$previousPoint->x, $previousPoint->y,
$point->x, $point->y
);
// ImageMagick draws a 1px border along the outer ring. To
// prevent the border overlaps inner rings too close to the
// outer ring, only inflate inner rings by 1/4 pixel.
$line->moveRight($isOuterRing ? 0.5 : 0.25);
}
$previousPoint = $point;
}
$first = true;
$previousLine = end($lines);
// Reconstruct point array from line intersections and draw the polygon
foreach ($lines as $line) {
$points[] = $point = ImagickRendererLine::intersection($previousLine, $line);
// Subtract 1/2 pixel to align the shapes to the pixel grid.
if ($first) {
$this->draw->pathMoveToAbsolute(
$point->x - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET,
$point->y - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET);
$first = false;
} else {
$this->draw->pathLineToAbsolute(
$point->x - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET,
$point->y - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET);
}
$previousLine = $line;
}
$this->draw->pathClose();
}
/**
* Begins a new shape. The shape should be ended with a call to endShape.
*
* @param \Jdenticon\Color $color The color of the shape.
*/
public function beginShape(\Jdenticon\Color $color)
{
$this->draw->setFillColor($color->__toString());
$this->draw->pathStart();
}
/**
* Ends the currently drawn shape.
*/
public function endShape()
{
$this->draw->pathFinish();
}
/**
* Renders this image as a PNG data stream.
*
* @return string
*/
public function getData()
{
$imagick = new \Imagick();
$imagick->newImage($this->width, $this->height, $this->backgroundColor->__toString());
$imagick->setImageFormat('png');
if (method_exists($imagick, 'setImageProperty')) {
$imagick->setImageProperty('Software', 'Jdenticon');
} else {
$imagick->setImageAttribute('Software', 'Jdenticon');
}
$imagick->drawImage($this->draw);
return $imagick->getImageBlob();
}
}

View file

@ -0,0 +1,123 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
use Jdenticon\Canvas\Canvas;
/**
* Renders icons as PNG using the internal vector rasterizer.
*/
class InternalPngRenderer extends AbstractRenderer
{
private $canvas;
private $ctx;
/**
* Creates an instance of the class ImagickRenderer.
*
* @param int $width The width of the icon in pixels.
* @param int $height The height of the icon in pixels.
*/
public function __construct($width, $height)
{
parent::__construct();
$this->canvas = new Canvas($width, $height);
$this->ctx = $this->canvas->getContext();
}
/**
* Gets the MIME type of the renderer output.
*
* @return string
*/
public function getMimeType()
{
return 'image/png';
}
/**
* Adds a circle without translating its coordinates.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $counterClockwise If true the circle will be drawn
* counter clockwise.
*/
protected function addCircleNoTransform($x, $y, $size, $counterClockwise)
{
$radius = $size / 2;
$this->ctx->moveTo($x + $size, $y + $radius);
$this->ctx->arc(
$x + $radius, $y + $radius,
$radius, 0, M_PI * 2,
$counterClockwise);
$this->ctx->closePath();
}
/**
* Adds a polygon without translating its coordinates.
*
* @param array $points An array of the points that the polygon consists of.
*/
protected function addPolygonNoTransform($points)
{
$pointCount = count($points);
$this->ctx->moveTo($points[0]->x, $points[0]->y);
for ($i = 1; $i < $pointCount; $i++) {
$this->ctx->lineTo($points[$i]->x, $points[$i]->y);
}
$this->ctx->closePath();
}
/**
* Sets the background color of the icon.
*
* @param \Jdenticon\Color $color The background color.
*/
public function setBackgroundColor(\Jdenticon\Color $color)
{
parent::setBackgroundColor($color);
$this->canvas->backColor = $this->backgroundColor->toRgba();
}
/**
* Begins a new shape. The shape should be ended with a call to endShape.
*
* @param \Jdenticon\Color $color The color of the shape.
*/
public function beginShape(\Jdenticon\Color $color)
{
$this->ctx->fillStyle = $color->toRgba();
$this->ctx->beginPath();
}
/**
* Ends the currently drawn shape.
*/
public function endShape()
{
$this->ctx->fill();
}
/**
* Gets the output from the renderer.
*
* @return string
*/
public function getData()
{
return $this->canvas->toPng(array('Software' => 'Jdenticon'));
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
/**
* A 2D coordinate.
*/
class Point
{
/**
* Creates a new Point.
*
* @param float $x X coordinate.
* @param float $y Y coordinate.
*/
public function __construct($x, $y)
{
$this->x = $x;
$this->y = $y;
}
/**
* The X coordinate of this point.
*
* @var float
*/
public $x;
/**
* The Y coordinate of this point.
*
* @var float
*/
public $y;
/**
* Gets a string representation of the point.
*
* @return string
*/
public function __toString()
{
return $this->x + ", " + $this->y;
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
/**
* Specifies the bounds of a 2D rectangle.
*/
class Rectangle
{
/**
* The X coordinate of the left side of the rectangle.
*
* @var float
*/
public $x;
/**
* The Y coordinate of the top side of the rectangle.
*
* @var float
*/
public $y;
/**
* The width of the rectangle.
* @var float
*/
public $width;
/**
* The height of the rectangle.
* @var float
*/
public $height;
/**
* Creates a new Rectangle.
*
* @param float $x The X coordinate of the left edge of the rectangle.
* @param float $y The Y coordinate of the top edge of the rectangle.
* @param float $width The width of the rectangle.
* @param float $height The height of the rectangle.
*/
public function __construct($x, $y, $width, $height)
{
$this->x = $x;
$this->y = $y;
$this->width = $width;
$this->height = $height;
}
}

View file

@ -0,0 +1,141 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
use Jdenticon\Color;
/**
* Interface for an identicon renderer.
*/
interface RendererInterface
{
/**
* Sets the current transform that will be applied on all coordinates before
* being rendered to the target image.
*
* @param \Jdenticon\Rendering\Transform $transform The transform to set.
* If NULL is specified any existing transform is removed.
*/
public function setTransform(\Jdenticon\Rendering\Transform $transform);
/**
* Gets the current transform that will be applied on all coordinates before
* being rendered to the target image.
*
* @return \Jdenticon\Rendering\Transform
*/
public function getTransform();
/**
* Sets the background color of the image.
*
* @param \Jdenticon\Color $color The image background color.
*/
public function setBackgroundColor(Color $color);
/**
* Gets the background color of the image.
*
* @return \Jdenticon\Color
*/
public function getBackgroundColor();
/**
* Gets the MIME type of the renderer output.
*
* @return string
*/
public function getMimeType();
/**
* Begins a new shape. The shape should be ended with a call to endShape.
*
* @param \Jdenticon\Color $color The color of the shape.
*/
public function beginShape(Color $color);
/**
* Ends the currently drawn shape.
*/
public function endShape();
/**
* Adds a rectangle to the image.
*
* @param float $x The x-coordinate of the rectangle upper-left corner.
* @param float $y The y-coordinate of the rectangle upper-left corner.
* @param float $width The width of the rectangle.
* @param float $height The height of the rectangle.
* @param bool $invert If true the area of the rectangle will be removed
* from the filled area.
*/
public function addRectangle($x, $y, $width, $height, $invert = false);
/**
* Adds a circle to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $invert If true the area of the circle will be removed from
* the filled area.
*/
public function addCircle($x, $y, $size, $invert = false);
/**
* Adds a polygon to the image.
*
* @param array $points Array of points that the polygon consists of.
* @param bool $invert If true the area of the polygon will be removed from
* the filled area.
*/
public function addPolygon($points, $invert = false);
/**
* Adds a triangle to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $width The width of the bounding rectangle.
* @param float $height The height of the bounding rectangle.
* @param float $direction The direction of the 90 degree corner of
* the triangle.
* @param bool $invert If true the area of the triangle will be removed
* from the filled area.
*/
public function addTriangle($x, $y, $width, $height, $direction, $invert = false);
/**
* Adds a rhombus to the image.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $width The width of the bounding rectangle.
* @param float $height The height of the bounding rectangle.
* @param bool $invert If true the area of the rhombus will be removed
* from the filled area.
*/
public function addRhombus($x, $y, $width, $height, $invert = false);
/**
* Gets the output from the renderer.
*
* @return string
*/
public function getData();
}

View file

@ -0,0 +1,83 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
/**
* Represents a SVG path element.
*/
class SvgPath
{
private $dataString;
public function __construct()
{
$this->dataString = '';
}
/**
* Adds a circle to the SVG.
*
* @param float $x X coordinate of the left side of the containing rectangle.
* @param float $y Y coordinate of the top side of the containing rectangle.
* @param float $size The diameter of the circle.
* @param bool $counterClockwise If true the circle will be drawn counter
* clockwise. This affects the rendering since the evenodd filling rule
* is used by Jdenticon.
*/
public function addCircle($x, $y, $size, $counterClockwise)
{
$sweepFlag = $counterClockwise ? '0' : '1';
$radiusAsString = number_format($size / 2, 2, '.', '');
$this->dataString .=
'M'. number_format($x, 2, '.', '') .' '.
number_format($y + $size / 2, 2, '.', '').
'a'. $radiusAsString .','. $radiusAsString .' 0 1,'.
$sweepFlag .' '. number_format($size, 2, '.', '') .',0'.
'a'. $radiusAsString .','. $radiusAsString .' 0 1,'.
$sweepFlag .' '. number_format(-$size, 2, '.', '') .',0';
}
/**
* Adds a polygon to the SVG.
*
* @param array(\Jdenticon\Rendering\Point) $points The corners of the
* polygon.
*/
public function addPolygon($points)
{
$pointCount = count($points);
$this->dataString .= 'M'.
number_format($points[0]->x, 2, '.', '') .' '.
number_format($points[0]->y, 2, '.', '');
for ($i = 1; $i < $pointCount; $i++) {
$this->dataString .= 'L'.
number_format($points[$i]->x, 2, '.', '') .' '.
number_format($points[$i]->y, 2, '.', '');
}
$this->dataString .= 'Z';
}
/**
* Gets the path as a SVG path string.
*
* @return string
*/
public function __toString()
{
return $this->dataString;
}
}

View file

@ -0,0 +1,133 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
/**
* Renders icons as SVG paths.
*/
class SvgRenderer extends AbstractRenderer
{
private $pathsByColor = array();
private $path;
private $width;
private $height;
/**
* Creates a new SvgRenderer.
*
* @param int $width The width of the icon in pixels.
* @param int $height The height of the icon in pixels.
*/
public function __construct($width, $height)
{
$this->width = $width;
$this->height = $height;
}
/**
* Gets the MIME type of the renderer output.
*
* @return string
*/
public function getMimeType()
{
return 'image/svg+xml';
}
/**
* Adds a circle without translating its coordinates.
*
* @param float $x The x-coordinate of the bounding rectangle
* upper-left corner.
* @param float $y The y-coordinate of the bounding rectangle
* upper-left corner.
* @param float $size The size of the bounding rectangle.
* @param bool $counterClockwise If true the circle will be drawn
* counter clockwise.
*/
protected function addCircleNoTransform($x, $y, $size, $counterClockwise)
{
$this->path->addCircle($x, $y, $size, $counterClockwise);
}
/**
* Adds a polygon without translating its coordinates.
*
* @param array $points An array of the points that the polygon consists of.
*/
protected function addPolygonNoTransform($points)
{
$this->path->addPolygon($points);
}
/**
* Begins a new shape. The shape should be ended with a call to endShape.
*
* @param \Jdenticon\Color $color The color of the shape.
*/
public function beginShape(\Jdenticon\Color $color)
{
$colorString = $color->toHexString(6);
if (isset($this->pathsByColor[$colorString])) {
$this->path = $this->pathsByColor[$colorString];
} else {
$this->path = new SvgPath();
$this->pathsByColor[$colorString] = $this->path;
}
}
/**
* Ends the currently drawn shape.
*/
public function endShape()
{
}
/**
* Generates an SVG string of the renderer output.
*
* @param bool $fragment If true an SVG string without the root svg element
* will be rendered.
*/
public function getData($fragment = false)
{
$svg = '';
$widthAsString = number_format($this->width, 0, '.', '');
$heightAsString = number_format($this->height, 0, '.', '');
if (!$fragment) {
$svg .= '<svg xmlns="http://www.w3.org/2000/svg" width="' .
$widthAsString .'" height="'. $heightAsString .'" viewBox="0 0 '.
$widthAsString .' '. $heightAsString .
'" preserveAspectRatio="xMidYMid meet">';
}
if ($this->backgroundColor->a > 0) {
$opacity = (float)$this->backgroundColor->a / 255;
$svg .= '<rect fill="'. $this->backgroundColor->toHexString(6) .
'" fill-opacity="'. number_format($opacity, 2, '.', '').
'" x="0" y="0" width="'. $widthAsString .'" height="'.
$heightAsString .'"/>';
}
foreach ($this->pathsByColor as $color => $path) {
$svg .= "<path fill=\"$color\" d=\"$path\"/>";
}
if (!$fragment) {
$svg .= '</svg>';
}
return $svg;
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
/*
* Translates and rotates a point before being rendered.
*/
class Transform
{
private $x;
private $y;
private $size;
private $rotation;
/**
* Creates a new Transform.
*
* @param float $x The x-coordinate of the upper left corner of the
* transformed rectangle.
* @param float $y The y-coordinate of the upper left corner of the
* transformed rectangle.
* @param float $size The size of the transformed rectangle.
* @param integer $rotation Rotation specified as
* 0 = 0 rad, 1 = 0.5π rad, 2 = π rad, 3 = 1.5π rad.
*/
public function __construct($x, $y, $size, $rotation)
{
$this->x = $x;
$this->y = $y;
$this->size = $size;
$this->rotation = $rotation;
}
/**
* Gets a noop transform.
*
* @return \Jdenticon\Rendering\Transform
*/
public static function getEmpty()
{
return new Transform(0, 0, 0, 0);
}
/**
* Transforms the specified point based on the translation and rotation
* specification for this Transform.
*
* @param float $x x-coordinate
* @param float $y y-coordinate
* @param float $width The width of the transformed rectangle. If greater
* than 0, this will ensure the returned point is of the upper left
* corner of the transformed rectangle.
* @param float $height The height of the transformed rectangle. If greater
* than 0, this will ensure the returned point is of the upper left
* corner of the transformed rectangle.
* @return \Jdenticon\Rendering\Point
*/
public function transformPoint($x, $y, $width = 0, $height = 0)
{
$right = $this->x + $this->size;
$bottom = $this->y + $this->size;
switch ($this->rotation) {
case 1: return new Point($right - $y - $height, $this->y + $x);
case 2: return new Point($right - $x - $width, $bottom - $y - $height);
case 3: return new Point($this->x + $y, $bottom - $x - $width);
default: return new Point($this->x + $x, $this->y + $y);
}
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Rendering;
/**
* Specifies in what direction the 90 degree angle of a triangle is pointing.
*/
class TriangleDirection
{
/**
* The 90 degree angle is pointing to South West.
*/
const SOUTH_WEST = 0;
/**
* The 90 degree angle is pointing to North West.
*/
const NORTH_WEST = 1;
/**
* The 90 degree angle is pointing to North East.
*/
const NORTH_EAST = 2;
/**
* The 90 degree angle is pointing to South East.
*/
const SOUTH_EAST = 3;
}

View file

@ -0,0 +1,62 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Shapes;
/**
* Represents a shape to be rendered in an icon. These instances are
* hash specific.
*/
class Shape
{
/**
* The shape definition to be used to render the shape.
*
* @var function(
* \Jdenticon\Rendering\RendererInterface $renderer,
* \Jdenticon\Shapes\ShapePosition $cell,
* int $index)
*/
public $definition;
/**
* The fill color of the shape.
*
* @var Jdenticon\Color
*/
public $color;
/**
* The positions in which the shape will be rendered.
*
* @var array(\Jdenticon\Shapes\ShapePosition)
*/
public $positions;
/**
* The rotation index of the icon in the first position.
*
* @var int
*/
public $startRotationIndex;
public function __construct(
$definition,
\Jdenticon\Color $color,
array $positions,
$startRotationIndex)
{
$this->definition = $definition;
$this->color = $color;
$this->positions = $positions;
$this->startRotationIndex = $startRotationIndex;
}
}

View file

@ -0,0 +1,71 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Shapes;
/**
* Represents a category of shapes that can be rendered in an icon. These
* instances are not hash specific.
*/
class ShapeCategory
{
/**
* The index of the hash octet determining the color of shapes in this
* category.
*
* @var int
*/
public $colorIndex;
/**
* A list of possible shape definitions in this category.
*
* @var array(function(
* \Jdenticon\Rendering\RendererInterface $renderer,
* \Jdenticon\Shapes\ShapePosition $cell,
* int $index))
*/
public $shapes;
/**
* The index of the hash octet determining which of the shape definitions
* that will be used for a particular hash.
*
* @var int
*/
public $shapeIndex;
/**
* The index of the hash octet determining the rotation index of the shape
* in the first position.
*
* @var int
*/
public $rotationIndex;
/**
* The positions in which the shapes of this category will be rendered.
*
* @var array(int)
*/
public $positions;
public function __construct(
$colorIndex, array $shapes, $shapeIndex, $rotationIndex, array $positions)
{
$this->colorIndex = $colorIndex;
$this->shapes = $shapes;
$this->shapeIndex = $shapeIndex;
$this->rotationIndex = $rotationIndex;
$this->positions = $positions;
}
}

View file

@ -0,0 +1,243 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Shapes;
use Jdenticon\Rendering\Point;
use Jdenticon\Rendering\TriangleDirection;
/**
* Provides definitions for the default shapes used in identicons.
*/
class ShapeDefinitions
{
private static $outerShapes;
private static $centerShapes;
/**
* Gets an array of all possible outer shapes. Do not modify the returned
* array.
*
* @return array(\Jdenticon\Rendering\Shape)
*/
public static function getOuterShapes()
{
if (self::$outerShapes === null) {
self::$outerShapes = self::createOuterShapes();
}
return self::$outerShapes;
}
/**
* Gets an array of all possible center shapes. Do not modify the returned
* array.
*
* @return array(\Jdenticon\Rendering\Shape)
*/
public static function getCenterShapes()
{
if (self::$centerShapes === null) {
self::$centerShapes = self::createCenterShapes();
}
return self::$centerShapes;
}
private static function createOuterShapes()
{
return array(
function ($renderer, $cell, $index)
{
$renderer->addTriangle(0, 0, $cell, $cell, 0);
},
function ($renderer, $cell, $index)
{
$renderer->addTriangle(0, $cell / 2, $cell, $cell / 2, 0);
},
function ($renderer, $cell, $index)
{
$renderer->addRhombus(0, 0, $cell, $cell);
},
function ($renderer, $cell, $index)
{
$m = $cell / 6;
$renderer->addCircle($m, $m, $cell - 2 * $m);
}
);
}
private static function createCenterShapes()
{
return array(
function ($renderer, $cell, $index)
{
$k = $cell * 0.42;
$renderer->addPolygon(array(
new Point(0, 0),
new Point($cell, 0),
new Point($cell, $cell - $k * 2),
new Point($cell - $k, $cell),
new Point(0, $cell)
));
},
function ($renderer, $cell, $index)
{
$w = (int)($cell * 0.5);
$h = (int)($cell * 0.8);
$renderer->addTriangle(
$cell - $w, 0, $w, $h,
TriangleDirection::NORTH_EAST);
},
function ($renderer, $cell, $index)
{
$s = (int)($cell / 3);
$renderer->addRectangle($s, $s, $cell - $s, $cell - $s);
},
function ($renderer, $cell, $index)
{
$tmp = $cell * 0.1;
if ($tmp > 1) {
// large icon => truncate decimals
$inner = (int)$tmp;
} elseif ($tmp > 0.5) {
// medium size icon => fixed width
$inner = 1;
} else {
// small icon => anti-aliased border
$inner = $tmp;
}
// Use fixed outer border widths in small icons to ensure
// the border is drawn
if ($cell < 6) {
$outer = 1;
} elseif ($cell < 8) {
$outer = 2;
} else {
$outer = (int)($cell / 4);
}
$renderer->addRectangle(
$outer, $outer,
$cell - $inner - $outer, $cell - $inner - $outer);
},
function ($renderer, $cell, $index)
{
$m = (int)($cell * 0.15);
$s = (int)($cell * 0.5);
$renderer->addCircle($cell - $s - $m, $cell - $s - $m, $s);
},
function ($renderer, $cell, $index)
{
$inner = $cell * 0.1;
$outer = $inner * 4;
// Align edge to nearest pixel in large icons
if ($outer > 3) {
$outer = (int)$outer;
}
$renderer->addRectangle(0, 0, $cell, $cell);
$renderer->addPolygon(array(
new Point($outer, $outer),
new Point($cell - $inner, $outer),
new Point($outer + ($cell - $outer - $inner) / 2,
$cell - $inner)
), true);
},
function ($renderer, $cell, $index)
{
$renderer->addPolygon(array(
new Point(0, 0),
new Point($cell, 0),
new Point($cell, $cell * 0.7),
new Point($cell * 0.4, $cell * 0.4),
new Point($cell * 0.7, $cell),
new Point(0, $cell)
));
},
function ($renderer, $cell, $index)
{
$renderer->addTriangle(
$cell / 2, $cell / 2, $cell / 2, $cell / 2,
TriangleDirection::SOUTH_EAST);
},
function ($renderer, $cell, $index)
{
$renderer->addPolygon(array(
new Point(0, 0),
new Point($cell, 0),
new Point($cell, $cell / 2),
new Point($cell / 2, $cell),
new Point(0, $cell)
));
},
function ($renderer, $cell, $index)
{
$tmp = $cell * 0.14;
if ($cell < 8) {
// small icon => anti-aliased border
$inner = $tmp;
} else {
// large icon => truncate decimals
$inner = (int)$tmp;
}
// Use fixed outer border widths in small icons to ensure
// the border is drawn
if ($cell < 4) {
$outer = 1;
} elseif ($cell < 6) {
$outer = 2;
} else {
$outer = (int)($cell * 0.35);
}
$renderer->addRectangle(0, 0, $cell, $cell);
$renderer->addRectangle(
$outer, $outer,
$cell - $outer - $inner, $cell - $outer - $inner, true);
},
function ($renderer, $cell, $index)
{
$inner = $cell * 0.12;
$outer = $inner * 3;
$renderer->addRectangle(0, 0, $cell, $cell);
$renderer->addCircle($outer, $outer, $cell - $inner - $outer,
true);
},
function ($renderer, $cell, $index)
{
$renderer->addTriangle(
$cell / 2, $cell / 2, $cell / 2, $cell / 2,
TriangleDirection::SOUTH_EAST);
},
function ($renderer, $cell, $index)
{
$m = $cell * 0.25;
$renderer->addRectangle(0, 0, $cell, $cell);
$renderer->addRhombus($m, $m, $cell - $m, $cell - $m, true);
},
function ($renderer, $cell, $index)
{
$m = $cell * 0.4;
$s = $cell * 1.2;
if ($index != 0) {
$renderer->addCircle($m, $m, $s);
}
}
);
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* This file is part of Jdenticon for PHP.
* https://github.com/dmester/jdenticon-php/
*
* Copyright (c) 2018 Daniel Mester Pirttijärvi
*
* For full license information, please see the LICENSE file that was
* distributed with this source code.
*/
namespace Jdenticon\Shapes;
/**
* Specifies in which cell a shape will be rendered.
*/
class ShapePosition
{
/**
* The horizontal cell index measured left to right.
*
* @var int
*/
public $x;
/**
* The vertical cell index measured from the top.
*
* @var int
*/
public $y;
/**
* Creates a new ShapePosition instance.
*
* @param int $x The x-coordinate of the containing cell.
* @param int $y The y-coordinate of the containing cell.
*/
public function __construct($x, $y)
{
$this->x = $x;
$this->y = $y;
}
}