Differential D19168 Diff 45914 src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php
| <?php | <?php | ||||
| final class PhabricatorFilesComposeAvatarBuiltinFile | final class PhabricatorFilesComposeAvatarBuiltinFile | ||||
| extends PhabricatorFilesBuiltinFile { | extends PhabricatorFilesBuiltinFile { | ||||
| private $icon; | private $icon; | ||||
| private $color; | private $color; | ||||
| private $border; | private $border; | ||||
| private $maps = array(); | |||||
| const VERSION = 'v1'; | const VERSION = 'v1'; | ||||
| public function updateUser(PhabricatorUser $user) { | |||||
| $username = $user->getUsername(); | |||||
| $image_map = $this->getMap('image'); | |||||
| $initial = phutil_utf8_strtoupper(substr($username, 0, 1)); | |||||
| $pack = $this->pickMap('pack', $username); | |||||
| $icon = "alphanumeric/{$pack}/{$initial}.png"; | |||||
| if (!isset($image_map[$icon])) { | |||||
| $icon = "alphanumeric/{$pack}/_default.png"; | |||||
| } | |||||
| $border = $this->pickMap('border', $username); | |||||
| $color = $this->pickMap('color', $username); | |||||
| $data = $this->composeImage($color, $icon, $border); | |||||
| $name = $this->getImageDisplayName($color, $icon, $border); | |||||
| $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | |||||
| $file = PhabricatorFile::newFromFileData( | |||||
| $data, | |||||
| array( | |||||
| 'name' => $name, | |||||
| 'profile' => true, | |||||
| 'canCDN' => true, | |||||
| )); | |||||
| $user | |||||
| ->setDefaultProfileImagePHID($file->getPHID()) | |||||
| ->setDefaultProfileImageVersion(self::VERSION) | |||||
| ->saveWithoutIndex(); | |||||
| unset($unguarded); | |||||
| return $file; | |||||
| } | |||||
| private function getMap($map_key) { | |||||
| if (!isset($this->maps[$map_key])) { | |||||
| switch ($map_key) { | |||||
| case 'pack': | |||||
| $map = $this->newPackMap(); | |||||
| break; | |||||
| case 'image': | |||||
| $map = $this->newImageMap(); | |||||
| break; | |||||
| case 'color': | |||||
| $map = $this->newColorMap(); | |||||
| break; | |||||
| case 'border': | |||||
| $map = $this->newBorderMap(); | |||||
| break; | |||||
| default: | |||||
| throw new Exception(pht('Unknown map "%s".', $map_key)); | |||||
| } | |||||
| $this->maps[$map_key] = $map; | |||||
| } | |||||
| return $this->maps[$map_key]; | |||||
| } | |||||
| private function pickMap($map_key, $username) { | |||||
| $map = $this->getMap($map_key); | |||||
| $seed = $username.'_'.$map_key; | |||||
| $key = PhabricatorHash::digestToRange($seed, 0, count($map) - 1); | |||||
| return $map[$key]; | |||||
| } | |||||
| public function setIcon($icon) { | public function setIcon($icon) { | ||||
| $this->icon = $icon; | $this->icon = $icon; | ||||
| return $this; | return $this; | ||||
| } | } | ||||
| public function getIcon() { | public function getIcon() { | ||||
| return $this->icon; | return $this->icon; | ||||
| } | } | ||||
| Show All 21 Lines | public function getBuiltinFileKey() { | ||||
| $color = $this->getColor(); | $color = $this->getColor(); | ||||
| $border = implode(',', $this->getBorder()); | $border = implode(',', $this->getBorder()); | ||||
| $desc = "compose(icon={$icon}, color={$color}, border={$border}"; | $desc = "compose(icon={$icon}, color={$color}, border={$border}"; | ||||
| $hash = PhabricatorHash::digestToLength($desc, 40); | $hash = PhabricatorHash::digestToLength($desc, 40); | ||||
| return "builtin:{$hash}"; | return "builtin:{$hash}"; | ||||
| } | } | ||||
| public function getBuiltinDisplayName() { | public function getBuiltinDisplayName() { | ||||
| $icon = $this->getIcon(); | return $this->getImageDisplayName( | ||||
| $color = $this->getColor(); | $this->getIcon(), | ||||
| $border = implode(',', $this->getBorder()); | $this->getColor(), | ||||
| $this->getBorder()); | |||||
| } | |||||
| private function getImageDisplayName($icon, $color, $border) { | |||||
| $border = implode(',', $border); | |||||
| return "{$icon}-{$color}-{$border}.png"; | return "{$icon}-{$color}-{$border}.png"; | ||||
| } | } | ||||
| public function loadBuiltinFileData() { | public function loadBuiltinFileData() { | ||||
| return $this->composeImage( | return $this->composeImage( | ||||
| $this->getColor(), $this->getIcon(), $this->getBorder()); | $this->getColor(), | ||||
| $this->getIcon(), | |||||
| $this->getBorder()); | |||||
| } | } | ||||
| private function composeImage($color, $image, $border) { | private function composeImage($color, $image, $border) { | ||||
| // If we don't have the GD extension installed, just return a static | // If we don't have the GD extension installed, just return a static | ||||
| // default profile image rather than trying to compose a dynamic one. | // default profile image rather than trying to compose a dynamic one. | ||||
| if (!function_exists('imagecreatefromstring')) { | if (!function_exists('imagecreatefromstring')) { | ||||
| $root = dirname(phutil_get_library_root('phabricator')); | $root = dirname(phutil_get_library_root('phabricator')); | ||||
| $default_path = $root.'/resources/builtin/profile.png'; | $default_path = $root.'/resources/builtin/profile.png'; | ||||
| return Filesystem::readFile($default_path); | return Filesystem::readFile($default_path); | ||||
| } | } | ||||
| $color_const = hexdec(trim($color, '#')); | $color_const = hexdec(trim($color, '#')); | ||||
| $true_border = self::rgba2gd($border); | $true_border = self::rgba2gd($border); | ||||
| $image_map = self::getImageMap(); | $image_map = $this->getMap('image'); | ||||
| $data = Filesystem::readFile($image_map[$image]); | $data = Filesystem::readFile($image_map[$image]); | ||||
| $img = imagecreatefromstring($data); | $img = imagecreatefromstring($data); | ||||
| // 4 pixel border at 50x50, 32 pixel border at 400x400 | // 4 pixel border at 50x50, 32 pixel border at 400x400 | ||||
| $canvas = imagecreatetruecolor(400, 400); | $canvas = imagecreatetruecolor(400, 400); | ||||
| $image_fill = imagefill($canvas, 0, 0, $color_const); | $image_fill = imagefill($canvas, 0, 0, $color_const); | ||||
| Show All 26 Lines | final class PhabricatorFilesComposeAvatarBuiltinFile | ||||
| } | } | ||||
| private static function rgba2gd($rgba) { | private static function rgba2gd($rgba) { | ||||
| $r = $rgba[0]; | $r = $rgba[0]; | ||||
| $g = $rgba[1]; | $g = $rgba[1]; | ||||
| $b = $rgba[2]; | $b = $rgba[2]; | ||||
| $a = $rgba[3]; | $a = $rgba[3]; | ||||
| $a = (1 - $a) * 255; | $a = (1 - $a) * 255; | ||||
| return ($a << 24) | ($r << 16) | ($g << 8) | $b; | return ($a << 24) | ($r << 16) | ($g << 8) | $b; | ||||
| } | } | ||||
| public static function getImageMap() { | private function newImageMap() { | ||||
| $root = dirname(phutil_get_library_root('phabricator')); | $root = dirname(phutil_get_library_root('phabricator')); | ||||
| $root = $root.'/resources/builtin/alphanumeric/'; | $root = $root.'/resources/builtin/alphanumeric/'; | ||||
| $map = array(); | $map = array(); | ||||
| $list = id(new FileFinder($root)) | $list = id(new FileFinder($root)) | ||||
| ->withType('f') | ->withType('f') | ||||
| ->withFollowSymlinks(true) | ->withFollowSymlinks(true) | ||||
| ->find(); | ->find(); | ||||
| foreach ($list as $file) { | foreach ($list as $file) { | ||||
| $map['alphanumeric/'.$file] = $root.$file; | $map['alphanumeric/'.$file] = $root.$file; | ||||
| } | } | ||||
| return $map; | return $map; | ||||
| } | } | ||||
| public function getUniqueProfileImage($username) { | private function newPackMap() { | ||||
| $pack_map = $this->getImagePackMap(); | |||||
| $image_map = $this->getImageMap(); | |||||
| $color_map = $this->getColorMap(); | |||||
| $border_map = $this->getBorderMap(); | |||||
| $file = phutil_utf8_strtoupper(substr($username, 0, 1)); | |||||
| $pack_count = count($pack_map); | |||||
| $color_count = count($color_map); | |||||
| $border_count = count($border_map); | |||||
| $pack_seed = $username.'_pack'; | |||||
| $color_seed = $username.'_color'; | |||||
| $border_seed = $username.'_border'; | |||||
| $pack_key = | |||||
| PhabricatorHash::digestToRange($pack_seed, 0, $pack_count - 1); | |||||
| $color_key = | |||||
| PhabricatorHash::digestToRange($color_seed, 0, $color_count - 1); | |||||
| $border_key = | |||||
| PhabricatorHash::digestToRange($border_seed, 0, $border_count - 1); | |||||
| $pack = $pack_map[$pack_key]; | |||||
| $icon = 'alphanumeric/'.$pack.'/'.$file.'.png'; | |||||
| $color = $color_map[$color_key]; | |||||
| $border = $border_map[$border_key]; | |||||
| if (!isset($image_map[$icon])) { | |||||
| $icon = 'alphanumeric/'.$pack.'/_default.png'; | |||||
| } | |||||
| return array('color' => $color, 'icon' => $icon, 'border' => $border); | |||||
| } | |||||
| public function getUserProfileImageFile($username) { | |||||
| $unique = $this->getUniqueProfileImage($username); | |||||
| $composer = id(new self()) | |||||
| ->setIcon($unique['icon']) | |||||
| ->setColor($unique['color']) | |||||
| ->setBorder($unique['border']); | |||||
| $data = $composer->loadBuiltinFileData(); | |||||
| $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | |||||
| $file = PhabricatorFile::newFromFileData( | |||||
| $data, | |||||
| array( | |||||
| 'name' => $composer->getBuiltinDisplayName(), | |||||
| 'profile' => true, | |||||
| 'canCDN' => true, | |||||
| )); | |||||
| unset($unguarded); | |||||
| return $file; | |||||
| } | |||||
| public static function getImagePackMap() { | |||||
| $root = dirname(phutil_get_library_root('phabricator')); | $root = dirname(phutil_get_library_root('phabricator')); | ||||
| $root = $root.'/resources/builtin/alphanumeric/'; | $root = $root.'/resources/builtin/alphanumeric/'; | ||||
| $map = id(new FileFinder($root)) | $map = id(new FileFinder($root)) | ||||
| ->withType('d') | ->withType('d') | ||||
| ->withFollowSymlinks(false) | ->withFollowSymlinks(false) | ||||
| ->find(); | ->find(); | ||||
| $map = array_values($map); | |||||
| return array_values($map); | return $map; | ||||
| } | } | ||||
| public static function getBorderMap() { | private function newBorderMap() { | ||||
| return array( | |||||
| $map = array( | |||||
| array(0, 0, 0, 0), | array(0, 0, 0, 0), | ||||
| array(0, 0, 0, 0.3), | array(0, 0, 0, 0.3), | ||||
| array(255, 255, 255, 0.4), | array(255, 255, 255, 0.4), | ||||
| array(255, 255, 255, 0.7), | array(255, 255, 255, 0.7), | ||||
| ); | ); | ||||
| return $map; | |||||
| } | } | ||||
| public static function getColorMap() { | private function newColorMap() { | ||||
| // | // Via: http://tools.medialab.sciences-po.fr/iwanthue/ | ||||
| // Generated Colors | |||||
| // http://tools.medialab.sciences-po.fr/iwanthue/ | return array( | ||||
| // | |||||
| $map = array( | |||||
| '#335862', | '#335862', | ||||
| '#2d5192', | '#2d5192', | ||||
| '#3c5da0', | '#3c5da0', | ||||
| '#99cd86', | '#99cd86', | ||||
| '#704889', | '#704889', | ||||
| '#5ac59e', | '#5ac59e', | ||||
| '#984060', | '#984060', | ||||
| '#33d4d1', | '#33d4d1', | ||||
| ▲ Show 20 Lines • Show All 213 Lines • ▼ Show 20 Lines | return array( | ||||
| '#335862', | '#335862', | ||||
| '#5c8596', | '#5c8596', | ||||
| '#335862', | '#335862', | ||||
| '#456a74', | '#456a74', | ||||
| '#335862', | '#335862', | ||||
| '#335862', | '#335862', | ||||
| '#335862', | '#335862', | ||||
| ); | ); | ||||
| return $map; | |||||
| } | } | ||||
| } | } | ||||