diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 76bc79498e..bb08685634 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3044,7 +3044,6 @@ phutil_register_library_map(array( 'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php', 'PhabricatorFilesBuiltinFile' => 'applications/files/builtin/PhabricatorFilesBuiltinFile.php', 'PhabricatorFilesComposeAvatarBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php', - 'PhabricatorFilesComposeAvatarExample' => 'applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php', 'PhabricatorFilesComposeIconBuiltinFile' => 'applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php', 'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php', 'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php', @@ -8628,7 +8627,6 @@ phutil_register_library_map(array( 'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel', 'PhabricatorFilesBuiltinFile' => 'Phobject', 'PhabricatorFilesComposeAvatarBuiltinFile' => 'PhabricatorFilesBuiltinFile', - 'PhabricatorFilesComposeAvatarExample' => 'PhabricatorUIExample', 'PhabricatorFilesComposeIconBuiltinFile' => 'PhabricatorFilesBuiltinFile', 'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow', diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php index df4243cfdd..f29ce97835 100644 --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -333,8 +333,12 @@ final class PhabricatorImageTransformer extends Phobject { return null; } + // NOTE: Empirically, the highest compression level (9) seems to take + // up to twice as long as the default compression level (6) but produce + // only slightly smaller files (10% on avatars, 3% on screenshots). + ob_start(); - $result = imagepng($image, null, 9); + $result = imagepng($image, null, 6); $output = ob_get_clean(); if (!$result) { diff --git a/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php index fcd6f97601..4f827f7603 100644 --- a/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php +++ b/src/applications/files/builtin/PhabricatorFilesComposeAvatarBuiltinFile.php @@ -7,8 +7,79 @@ final class PhabricatorFilesComposeAvatarBuiltinFile private $color; private $border; + private $maps = array(); + 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) { $this->icon = $icon; return $this; @@ -46,15 +117,22 @@ final class PhabricatorFilesComposeAvatarBuiltinFile } public function getBuiltinDisplayName() { - $icon = $this->getIcon(); - $color = $this->getColor(); - $border = implode(',', $this->getBorder()); + return $this->getImageDisplayName( + $this->getIcon(), + $this->getColor(), + $this->getBorder()); + } + + private function getImageDisplayName($icon, $color, $border) { + $border = implode(',', $border); return "{$icon}-{$color}-{$border}.png"; } public function loadBuiltinFileData() { return $this->composeImage( - $this->getColor(), $this->getIcon(), $this->getBorder()); + $this->getColor(), + $this->getIcon(), + $this->getBorder()); } private function composeImage($color, $image, $border) { @@ -68,7 +146,7 @@ final class PhabricatorFilesComposeAvatarBuiltinFile $color_const = hexdec(trim($color, '#')); $true_border = self::rgba2gd($border); - $image_map = self::getImageMap(); + $image_map = $this->getMap('image'); $data = Filesystem::readFile($image_map[$image]); $img = imagecreatefromstring($data); @@ -111,10 +189,10 @@ final class PhabricatorFilesComposeAvatarBuiltinFile $b = $rgba[2]; $a = $rgba[3]; $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 = $root.'/resources/builtin/alphanumeric/'; @@ -131,64 +209,7 @@ final class PhabricatorFilesComposeAvatarBuiltinFile return $map; } - public function getUniqueProfileImage($username) { - $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() { + private function newPackMap() { $root = dirname(phutil_get_library_root('phabricator')); $root = $root.'/resources/builtin/alphanumeric/'; @@ -196,28 +217,24 @@ final class PhabricatorFilesComposeAvatarBuiltinFile ->withType('d') ->withFollowSymlinks(false) ->find(); + $map = array_values($map); - return array_values($map); + return $map; } - public static function getBorderMap() { - - $map = array( + private function newBorderMap() { + return array( array(0, 0, 0, 0), array(0, 0, 0, 0.3), array(255, 255, 255, 0.4), array(255, 255, 255, 0.7), ); - - return $map; } - public static function getColorMap() { - // - // Generated Colors - // http://tools.medialab.sciences-po.fr/iwanthue/ - // - $map = array( + private function newColorMap() { + // Via: http://tools.medialab.sciences-po.fr/iwanthue/ + + return array( '#335862', '#2d5192', '#3c5da0', @@ -447,7 +464,6 @@ final class PhabricatorFilesComposeAvatarBuiltinFile '#335862', '#335862', ); - return $map; } } diff --git a/src/applications/people/cache/PhabricatorUserProfileImageCacheType.php b/src/applications/people/cache/PhabricatorUserProfileImageCacheType.php index e2a535cdd4..8babff859f 100644 --- a/src/applications/people/cache/PhabricatorUserProfileImageCacheType.php +++ b/src/applications/people/cache/PhabricatorUserProfileImageCacheType.php @@ -45,21 +45,10 @@ final class PhabricatorUserProfileImageCacheType $generate_users[] = $user; } - // Generate Files for anyone without a default - foreach ($generate_users as $generate_user) { - $generate_user_phid = $generate_user->getPHID(); - $generate_username = $generate_user->getUsername(); - $generate_version = PhabricatorFilesComposeAvatarBuiltinFile::VERSION; - $generate_file = id(new PhabricatorFilesComposeAvatarBuiltinFile()) - ->getUserProfileImageFile($generate_username); - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - $generate_user->setDefaultProfileImagePHID($generate_file->getPHID()); - $generate_user->setDefaultProfileImageVersion($generate_version); - $generate_user->save(); - unset($unguarded); - - $file_phids[$generate_user_phid] = $generate_file->getPHID(); + $generator = new PhabricatorFilesComposeAvatarBuiltinFile(); + foreach ($generate_users as $user) { + $file = $generator->updateUser($user); + $file_phids[$user->getPHID()] = $file->getPHID(); } if ($file_phids) { diff --git a/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php b/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php index 358756cecd..8bf3c8e118 100644 --- a/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php +++ b/src/applications/people/management/PhabricatorPeopleProfileImageWorkflow.php @@ -51,6 +51,7 @@ final class PhabricatorPeopleProfileImageWorkflow } $version = PhabricatorFilesComposeAvatarBuiltinFile::VERSION; + $generator = new PhabricatorFilesComposeAvatarBuiltinFile(); foreach ($iterator as $user) { $username = $user->getUsername(); @@ -63,16 +64,13 @@ final class PhabricatorPeopleProfileImageWorkflow } if ($default_phid == null || $is_force || $generate) { - $file = id(new PhabricatorFilesComposeAvatarBuiltinFile()) - ->getUserProfileImageFile($username); - $user->setDefaultProfileImagePHID($file->getPHID()); - $user->setDefaultProfileImageVersion($version); - $user->save(); $console->writeOut( "%s\n", pht( 'Generating profile image for "%s".', $username)); + + $generator->updateUser($user); } else { $console->writeOut( "%s\n", diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php index 6c8b7ac6b5..ba8b1cf292 100644 --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -267,6 +267,10 @@ final class PhabricatorUser return !($this->getPHID() === null); } + public function saveWithoutIndex() { + return parent::save(); + } + public function save() { if (!$this->getConduitCertificate()) { $this->setConduitCertificate($this->generateConduitCertificate()); @@ -276,7 +280,7 @@ final class PhabricatorUser $this->setAccountSecret(Filesystem::readRandomCharacters(64)); } - $result = parent::save(); + $result = $this->saveWithoutIndex(); if ($this->profile) { $this->profile->save(); diff --git a/src/applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php b/src/applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php deleted file mode 100644 index 57a65863db..0000000000 --- a/src/applications/uiexample/examples/PhabricatorFilesComposeAvatarExample.php +++ /dev/null @@ -1,95 +0,0 @@ -getRequest(); - $viewer = $request->getUser(); - - $colors = PhabricatorFilesComposeAvatarBuiltinFile::getColorMap(); - $packs = PhabricatorFilesComposeAvatarBuiltinFile::getImagePackMap(); - $builtins = PhabricatorFilesComposeAvatarBuiltinFile::getImageMap(); - $borders = PhabricatorFilesComposeAvatarBuiltinFile::getBorderMap(); - - $images = array(); - foreach ($builtins as $builtin => $raw_file) { - $file = PhabricatorFile::loadBuiltin($viewer, $builtin); - $images[] = $file->getBestURI(); - } - - $content = array(); - shuffle($colors); - foreach ($colors as $color) { - shuffle($borders); - $color_const = hexdec(trim($color, '#')); - $border = head($borders); - $border_color = implode(', ', $border); - - $styles = array(); - $styles[] = 'background-color: '.$color.';'; - $styles[] = 'display: inline-block;'; - $styles[] = 'height: 42px;'; - $styles[] = 'width: 42px;'; - $styles[] = 'border-radius: 3px;'; - $styles[] = 'border: 4px solid rgba('.$border_color.');'; - - shuffle($images); - $png = head($images); - - $image = phutil_tag( - 'img', - array( - 'src' => $png, - 'height' => 42, - 'width' => 42, - )); - - $tag = phutil_tag( - 'div', - array( - 'style' => implode(' ', $styles), - ), - $image); - - $content[] = phutil_tag( - 'div', - array( - 'class' => 'mlr mlb', - 'style' => 'float: left;', - ), - $tag); - } - - $count = new PhutilNumber( - count($colors) * count($builtins) * count($borders)); - - $infoview = id(new PHUIInfoView()) - ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) - ->appendChild(pht('This installation can generate %s unique '. - 'avatars. You can add additional image packs in '. - 'resources/builtins/alphanumeric/.', $count)); - - $info = phutil_tag_div('pmb', $infoview); - $view = phutil_tag_div('ml', $content); - - return phutil_tag( - 'div', - array(), - array( - $info, - $view, - )); - } -}