Remove all "FileHasObject" edge reads and writes
[phabricator.git] / src / applications / files / builtin / PhabricatorFilesComposeAvatarBuiltinFile.php
blob4f827f76032a46e634e31195787e9faac6ef3faf
1 <?php
3 final class PhabricatorFilesComposeAvatarBuiltinFile
4 extends PhabricatorFilesBuiltinFile {
6 private $icon;
7 private $color;
8 private $border;
10 private $maps = array();
12 const VERSION = 'v1';
14 public function updateUser(PhabricatorUser $user) {
15 $username = $user->getUsername();
17 $image_map = $this->getMap('image');
18 $initial = phutil_utf8_strtoupper(substr($username, 0, 1));
19 $pack = $this->pickMap('pack', $username);
20 $icon = "alphanumeric/{$pack}/{$initial}.png";
21 if (!isset($image_map[$icon])) {
22 $icon = "alphanumeric/{$pack}/_default.png";
25 $border = $this->pickMap('border', $username);
26 $color = $this->pickMap('color', $username);
28 $data = $this->composeImage($color, $icon, $border);
29 $name = $this->getImageDisplayName($color, $icon, $border);
31 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
33 $file = PhabricatorFile::newFromFileData(
34 $data,
35 array(
36 'name' => $name,
37 'profile' => true,
38 'canCDN' => true,
39 ));
41 $user
42 ->setDefaultProfileImagePHID($file->getPHID())
43 ->setDefaultProfileImageVersion(self::VERSION)
44 ->saveWithoutIndex();
46 unset($unguarded);
48 return $file;
51 private function getMap($map_key) {
52 if (!isset($this->maps[$map_key])) {
53 switch ($map_key) {
54 case 'pack':
55 $map = $this->newPackMap();
56 break;
57 case 'image':
58 $map = $this->newImageMap();
59 break;
60 case 'color':
61 $map = $this->newColorMap();
62 break;
63 case 'border':
64 $map = $this->newBorderMap();
65 break;
66 default:
67 throw new Exception(pht('Unknown map "%s".', $map_key));
69 $this->maps[$map_key] = $map;
72 return $this->maps[$map_key];
75 private function pickMap($map_key, $username) {
76 $map = $this->getMap($map_key);
77 $seed = $username.'_'.$map_key;
78 $key = PhabricatorHash::digestToRange($seed, 0, count($map) - 1);
79 return $map[$key];
83 public function setIcon($icon) {
84 $this->icon = $icon;
85 return $this;
88 public function getIcon() {
89 return $this->icon;
92 public function setColor($color) {
93 $this->color = $color;
94 return $this;
97 public function getColor() {
98 return $this->color;
101 public function setBorder($border) {
102 $this->border = $border;
103 return $this;
106 public function getBorder() {
107 return $this->border;
110 public function getBuiltinFileKey() {
111 $icon = $this->getIcon();
112 $color = $this->getColor();
113 $border = implode(',', $this->getBorder());
114 $desc = "compose(icon={$icon}, color={$color}, border={$border}";
115 $hash = PhabricatorHash::digestToLength($desc, 40);
116 return "builtin:{$hash}";
119 public function getBuiltinDisplayName() {
120 return $this->getImageDisplayName(
121 $this->getIcon(),
122 $this->getColor(),
123 $this->getBorder());
126 private function getImageDisplayName($icon, $color, $border) {
127 $border = implode(',', $border);
128 return "{$icon}-{$color}-{$border}.png";
131 public function loadBuiltinFileData() {
132 return $this->composeImage(
133 $this->getColor(),
134 $this->getIcon(),
135 $this->getBorder());
138 private function composeImage($color, $image, $border) {
139 // If we don't have the GD extension installed, just return a static
140 // default profile image rather than trying to compose a dynamic one.
141 if (!function_exists('imagecreatefromstring')) {
142 $root = dirname(phutil_get_library_root('phabricator'));
143 $default_path = $root.'/resources/builtin/profile.png';
144 return Filesystem::readFile($default_path);
147 $color_const = hexdec(trim($color, '#'));
148 $true_border = self::rgba2gd($border);
149 $image_map = $this->getMap('image');
150 $data = Filesystem::readFile($image_map[$image]);
152 $img = imagecreatefromstring($data);
154 // 4 pixel border at 50x50, 32 pixel border at 400x400
155 $canvas = imagecreatetruecolor(400, 400);
157 $image_fill = imagefill($canvas, 0, 0, $color_const);
158 if (!$image_fill) {
159 throw new Exception(
160 pht('Failed to save builtin avatar image data (imagefill).'));
163 $border_thickness = imagesetthickness($canvas, 64);
164 if (!$border_thickness) {
165 throw new Exception(
166 pht('Failed to save builtin avatar image data (imagesetthickness).'));
169 $image_rectangle = imagerectangle($canvas, 0, 0, 400, 400, $true_border);
170 if (!$image_rectangle) {
171 throw new Exception(
172 pht('Failed to save builtin avatar image data (imagerectangle).'));
175 $image_copy = imagecopy($canvas, $img, 0, 0, 0, 0, 400, 400);
176 if (!$image_copy) {
177 throw new Exception(
178 pht('Failed to save builtin avatar image data (imagecopy).'));
181 return PhabricatorImageTransformer::saveImageDataInAnyFormat(
182 $canvas,
183 'image/png');
186 private static function rgba2gd($rgba) {
187 $r = $rgba[0];
188 $g = $rgba[1];
189 $b = $rgba[2];
190 $a = $rgba[3];
191 $a = (1 - $a) * 255;
192 return ($a << 24) | ($r << 16) | ($g << 8) | $b;
195 private function newImageMap() {
196 $root = dirname(phutil_get_library_root('phabricator'));
197 $root = $root.'/resources/builtin/alphanumeric/';
199 $map = array();
200 $list = id(new FileFinder($root))
201 ->withType('f')
202 ->withFollowSymlinks(true)
203 ->find();
205 foreach ($list as $file) {
206 $map['alphanumeric/'.$file] = $root.$file;
209 return $map;
212 private function newPackMap() {
213 $root = dirname(phutil_get_library_root('phabricator'));
214 $root = $root.'/resources/builtin/alphanumeric/';
216 $map = id(new FileFinder($root))
217 ->withType('d')
218 ->withFollowSymlinks(false)
219 ->find();
220 $map = array_values($map);
222 return $map;
225 private function newBorderMap() {
226 return array(
227 array(0, 0, 0, 0),
228 array(0, 0, 0, 0.3),
229 array(255, 255, 255, 0.4),
230 array(255, 255, 255, 0.7),
234 private function newColorMap() {
235 // Via: http://tools.medialab.sciences-po.fr/iwanthue/
237 return array(
238 '#335862',
239 '#2d5192',
240 '#3c5da0',
241 '#99cd86',
242 '#704889',
243 '#5ac59e',
244 '#984060',
245 '#33d4d1',
246 '#9c4050',
247 '#20d8fd',
248 '#944937',
249 '#4bd0e3',
250 '#a25542',
251 '#4eb4f3',
252 '#6da8ec',
253 '#545608',
254 '#829ce5',
255 '#68681d',
256 '#607bc2',
257 '#4b69ad',
258 '#236ead',
259 '#31a0de',
260 '#4f8ed0',
261 '#846f2a',
262 '#bdb0f0',
263 '#518342',
264 '#9166aa',
265 '#5e904e',
266 '#f79dcc',
267 '#158e6b',
268 '#e189b7',
269 '#3ba984',
270 '#a85582',
271 '#4cccb7',
272 '#863d67',
273 '#84c08c',
274 '#7f4c7f',
275 '#a1bb7a',
276 '#65558f',
277 '#445082',
278 '#c9ca8e',
279 '#265582',
280 '#f4b189',
281 '#265582',
282 '#40b8e1',
283 '#814a28',
284 '#80c8f6',
285 '#cf7b5d',
286 '#1db5c7',
287 '#c0606e',
288 '#299a89',
289 '#ef8ead',
290 '#296437',
291 '#d39edb',
292 '#507436',
293 '#b888c9',
294 '#476025',
295 '#9987c5',
296 '#7867a3',
297 '#769b5a',
298 '#c46e9d',
299 '#437d4e',
300 '#d17492',
301 '#115e41',
302 '#ec8794',
303 '#297153',
304 '#d67381',
305 '#57c2c3',
306 '#bc607f',
307 '#86ceac',
308 '#7e3e53',
309 '#72c8b8',
310 '#884349',
311 '#45a998',
312 '#faa38c',
313 '#265582',
314 '#265582',
315 '#e4b788',
316 '#265582',
317 '#bbbc81',
318 '#265582',
319 '#ccb781',
320 '#265582',
321 '#eb957f',
322 '#15729c',
323 '#cf996f',
324 '#369bc5',
325 '#b6685d',
326 '#2da0a1',
327 '#d38275',
328 '#217e70',
329 '#ec9da1',
330 '#146268',
331 '#e8aa95',
332 '#3c6796',
333 '#8da667',
334 '#935f93',
335 '#69a573',
336 '#ae78ad',
337 '#569160',
338 '#d898be',
339 '#8eb4e8',
340 '#5e622c',
341 '#929ad3',
342 '#6c8548',
343 '#576196',
344 '#aed0a0',
345 '#694e79',
346 '#9abb8d',
347 '#8c5175',
348 '#6bb391',
349 '#8b4a5f',
350 '#519878',
351 '#ae7196',
352 '#3d8465',
353 '#e69eb3',
354 '#48663d',
355 '#cdaede',
356 '#71743d',
357 '#63acda',
358 '#7b5d30',
359 '#66bed6',
360 '#3585b0',
361 '#5880b0',
362 '#739acc',
363 '#48a3ba',
364 '#9d565b',
365 '#7fc4ca',
366 '#99566b',
367 '#94cabf',
368 '#7b4b49',
369 '#b1c8eb',
370 '#4e5632',
371 '#ecb2c3',
372 '#2d6158',
373 '#cf8287',
374 '#25889f',
375 '#b2696d',
376 '#6bafb6',
377 '#8c5744',
378 '#84b9d6',
379 '#9db3d6',
380 '#777cad',
381 '#826693',
382 '#86a779',
383 '#9d7fad',
384 '#b193c2',
385 '#547348',
386 '#d5adcb',
387 '#3f674d',
388 '#c98398',
389 '#66865a',
390 '#b2add6',
391 '#5a623d',
392 '#9793bb',
393 '#3c5472',
394 '#d5c5a1',
395 '#5e5a7f',
396 '#2c647e',
397 '#d8b194',
398 '#49607f',
399 '#c7b794',
400 '#335862',
401 '#e3aba7',
402 '#335862',
403 '#d9b9ad',
404 '#335862',
405 '#c48975',
406 '#347b81',
407 '#ad697e',
408 '#799a6d',
409 '#916b88',
410 '#69536b',
411 '#b4c4ad',
412 '#845865',
413 '#96b89d',
414 '#706d92',
415 '#9aa27a',
416 '#5b7292',
417 '#bc967b',
418 '#417792',
419 '#ce9793',
420 '#335862',
421 '#c898a5',
422 '#527a5f',
423 '#b38ba9',
424 '#648d72',
425 '#986b78',
426 '#79afa4',
427 '#966461',
428 '#50959b',
429 '#b27d7a',
430 '#335862',
431 '#335862',
432 '#bcadc4',
433 '#706343',
434 '#749ebc',
435 '#8c6a50',
436 '#92b8c4',
437 '#758cad',
438 '#868e67',
439 '#335862',
440 '#335862',
441 '#335862',
442 '#ac7e8b',
443 '#77a185',
444 '#807288',
445 '#636f51',
446 '#a192a9',
447 '#467a70',
448 '#9b7d73',
449 '#335862',
450 '#335862',
451 '#8c9c85',
452 '#335862',
453 '#81645a',
454 '#5f9489',
455 '#335862',
456 '#789da8',
457 '#335862',
458 '#72826c',
459 '#335862',
460 '#5c8596',
461 '#335862',
462 '#456a74',
463 '#335862',
464 '#335862',
465 '#335862',