@@ -124,20 +124,27 @@ public function set ($data) {
124124 $ type = 'jpg ' ;
125125 }
126126 if ($ type !== 'jpg ' && $ type !== 'png ' ) {
127- throw new \Exception ($ this ->l ->t (" Unknown filetype " ));
127+ throw new \Exception ($ this ->l ->t (' Unknown filetype ' ));
128128 }
129129
130130 if (!$ img ->valid ()) {
131- throw new \Exception ($ this ->l ->t (" Invalid image " ));
131+ throw new \Exception ($ this ->l ->t (' Invalid image ' ));
132132 }
133133
134134 if (!($ img ->height () === $ img ->width ())) {
135- throw new NotSquareException ($ this ->l ->t (" Avatar image is not square " ));
135+ throw new NotSquareException ($ this ->l ->t (' Avatar image is not square ' ));
136136 }
137137
138138 $ this ->remove ();
139139 $ file = $ this ->folder ->newFile ('avatar. ' .$ type );
140140 $ file ->putContent ($ data );
141+
142+ try {
143+ $ generated = $ this ->folder ->getFile ('generated ' );
144+ $ generated ->delete ();
145+ } catch (NotFoundException $ e ) {
146+ //
147+ }
141148 $ this ->user ->triggerChange ('avatar ' , $ file );
142149 }
143150
@@ -146,16 +153,13 @@ public function set ($data) {
146153 * @return void
147154 */
148155 public function remove () {
149- $ regex = '/^avatar\.([0-9]+\.)?(jpg|png)$/ ' ;
150156 $ avatars = $ this ->folder ->getDirectoryListing ();
151157
152158 $ this ->config ->setUserValue ($ this ->user ->getUID (), 'avatar ' , 'version ' ,
153159 (int )$ this ->config ->getUserValue ($ this ->user ->getUID (), 'avatar ' , 'version ' , 0 ) + 1 );
154160
155161 foreach ($ avatars as $ avatar ) {
156- if (preg_match ($ regex , $ avatar ->getName ())) {
157- $ avatar ->delete ();
158- }
162+ $ avatar ->delete ();
159163 }
160164 $ this ->user ->triggerChange ('avatar ' , '' );
161165 }
@@ -164,7 +168,16 @@ public function remove () {
164168 * @inheritdoc
165169 */
166170 public function getFile ($ size ) {
167- $ ext = $ this ->getExtension ();
171+ try {
172+ $ ext = $ this ->getExtension ();
173+ } catch (NotFoundException $ e ) {
174+ $ data = $ this ->generateAvatar ($ this ->user ->getDisplayName (), 1024 );
175+ $ avatar = $ this ->folder ->newFile ('avatar.png ' );
176+ $ avatar ->putContent ($ data );
177+ $ ext = 'png ' ;
178+
179+ $ this ->folder ->newFile ('generated ' );
180+ }
168181
169182 if ($ size === -1 ) {
170183 $ path = 'avatar. ' . $ ext ;
@@ -179,19 +192,26 @@ public function getFile($size) {
179192 throw new NotFoundException ;
180193 }
181194
182- $ avatar = new OC_Image ();
183- /** @var ISimpleFile $file */
184- $ file = $ this ->folder ->getFile ('avatar. ' . $ ext );
185- $ avatar ->loadFromData ($ file ->getContent ());
186- if ($ size !== -1 ) {
195+ if ($ this ->folder ->fileExists ('generated ' )) {
196+ $ data = $ this ->generateAvatar ($ this ->user ->getDisplayName (), $ size );
197+
198+ } else {
199+ $ avatar = new OC_Image ();
200+ /** @var ISimpleFile $file */
201+ $ file = $ this ->folder ->getFile ('avatar. ' . $ ext );
202+ $ avatar ->loadFromData ($ file ->getContent ());
187203 $ avatar ->resize ($ size );
204+ $ data = $ avatar ->data ();
188205 }
206+
189207 try {
190208 $ file = $ this ->folder ->newFile ($ path );
191- $ file ->putContent ($ avatar -> data () );
209+ $ file ->putContent ($ data );
192210 } catch (NotPermittedException $ e ) {
193211 $ this ->logger ->error ('Failed to save avatar for ' . $ this ->user ->getUID ());
212+ throw new NotFoundException ();
194213 }
214+
195215 }
196216
197217 return $ file ;
@@ -211,4 +231,166 @@ private function getExtension() {
211231 }
212232 throw new NotFoundException ;
213233 }
234+
235+ /**
236+ * @param string $userDisplayName
237+ * @param int $size
238+ * @return string
239+ */
240+ private function generateAvatar ($ userDisplayName , $ size ) {
241+ $ text = strtoupper (substr ($ userDisplayName , 0 , 1 ));
242+ $ backgroundColor = $ this ->avatarBackgroundColor ($ userDisplayName );
243+
244+ $ im = imagecreatetruecolor ($ size , $ size );
245+ $ background = imagecolorallocate ($ im , $ backgroundColor [0 ], $ backgroundColor [1 ], $ backgroundColor [2 ]);
246+ $ white = imagecolorallocate ($ im , 255 , 255 , 255 );
247+ imagefilledrectangle ($ im , 0 , 0 , $ size , $ size , $ background );
248+
249+ $ font = __DIR__ . '/../../core/fonts/OpenSans-Semibold.woff ' ;
250+
251+ $ fontSize = $ size * 0.4 ;
252+ $ box = imagettfbbox ($ fontSize , 0 , $ font , $ text );
253+
254+ $ x = ($ size - ($ box [2 ] - $ box [0 ])) / 2 ;
255+ $ y = ($ size - ($ box [1 ] - $ box [7 ])) / 2 ;
256+ $ x += 1 ;
257+ $ y -= $ box [7 ];
258+ imagettftext ($ im , $ fontSize , 0 , $ x , $ y , $ white , $ font , $ text );
259+
260+ ob_start ();
261+ imagepng ($ im );
262+ $ data = ob_get_contents ();
263+ ob_end_clean ();
264+
265+ return $ data ;
266+ }
267+
268+ /**
269+ * @param int $r
270+ * @param int $g
271+ * @param int $b
272+ * @return double[] Array containing h s l in [0, 1] range
273+ */
274+ private function rgbToHsl ($ r , $ g , $ b ) {
275+ $ r /= 255.0 ;
276+ $ g /= 255.0 ;
277+ $ b /= 255.0 ;
278+
279+ $ max = max ($ r , $ g , $ b );
280+ $ min = min ($ r , $ g , $ b );
281+
282+
283+ $ h = ($ max + $ min ) / 2.0 ;
284+ $ l = ($ max + $ min ) / 2.0 ;
285+
286+ if ($ max === $ min ) {
287+ $ h = $ s = 0 ; // Achromatic
288+ } else {
289+ $ d = $ max - $ min ;
290+ $ s = $ l > 0.5 ? $ d / (2 - $ max - $ min ) : $ d / ($ max + $ min );
291+ switch ($ max ) {
292+ case $ r :
293+ $ h = ($ g - $ b ) / $ d + ($ g < $ b ? 6 : 0 );
294+ break ;
295+ case $ g :
296+ $ h = ($ b - $ r ) / $ d + 2.0 ;
297+ break ;
298+ case $ b :
299+ $ h = ($ r - $ g ) / $ d + 4.0 ;
300+ break ;
301+ }
302+ $ h /= 6.0 ;
303+ }
304+ return [$ h , $ s , $ l ];
305+
306+ }
307+
308+ /**
309+ * @param string $text
310+ * @return int[] Array containting r g b in the range [0, 255]
311+ */
312+ private function avatarBackgroundColor ($ text ) {
313+ $ hash = preg_replace ('/[^0-9a-f]+/ ' , '' , $ text );
314+
315+ $ hash = md5 ($ hash );
316+ $ hashChars = str_split ($ hash );
317+
318+
319+ // Init vars
320+ $ result = ['0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' , '0 ' ];
321+ $ rgb = [0 , 0 , 0 ];
322+ $ sat = 0.70 ;
323+ $ lum = 0.68 ;
324+ $ modulo = 16 ;
325+
326+
327+ // Splitting evenly the string
328+ foreach ($ hashChars as $ i => $ char ) {
329+ $ result [$ i % $ modulo ] .= intval ($ char , 16 );
330+ }
331+
332+ // Converting our data into a usable rgb format
333+ // Start at 1 because 16%3=1 but 15%3=0 and makes the repartition even
334+ for ($ count = 1 ; $ count < $ modulo ; $ count ++) {
335+ $ rgb [$ count %3 ] += (int )$ result [$ count ];
336+ }
337+
338+ // Reduce values bigger than rgb requirements
339+ $ rgb [0 ] %= 255 ;
340+ $ rgb [1 ] %= 255 ;
341+ $ rgb [2 ] %= 255 ;
342+
343+ $ hsl = $ this ->rgbToHsl ($ rgb [0 ], $ rgb [1 ], $ rgb [2 ]);
344+
345+ // Classic formula to check the brightness for our eye
346+ // If too bright, lower the sat
347+ $ bright = sqrt (0.299 * ($ rgb [0 ] ** 2 ) + 0.587 * ($ rgb [1 ] ** 2 ) + 0.114 * ($ rgb [2 ] ** 2 ));
348+ if ($ bright >= 200 ) {
349+ $ sat = 0.60 ;
350+ }
351+
352+ return $ this ->hslToRgb ($ hsl [0 ], $ sat , $ lum );
353+ }
354+
355+ /**
356+ * @param double $h Hue in range [0, 1]
357+ * @param double $s Saturation in range [0, 1]
358+ * @param double $l Lightness in range [0, 1]
359+ * @return int[] Array containing r g b in the range [0, 255]
360+ */
361+ private function hslToRgb ($ h , $ s , $ l ){
362+ $ hue2rgb = function ($ p , $ q , $ t ){
363+ if ($ t < 0 ) {
364+ $ t += 1 ;
365+ }
366+ if ($ t > 1 ) {
367+ $ t -= 1 ;
368+ }
369+ if ($ t < 1 /6 ) {
370+ return $ p + ($ q - $ p ) * 6 * $ t ;
371+ }
372+ if ($ t < 1 /2 ) {
373+ return $ q ;
374+ }
375+ if ($ t < 2 /3 ) {
376+ return $ p + ($ q - $ p ) * (2 /3 - $ t ) * 6 ;
377+ }
378+ return $ p ;
379+ };
380+
381+ if ($ s === 0 ){
382+ $ r = $ l ;
383+ $ g = $ l ;
384+ $ b = $ l ; // achromatic
385+ }else {
386+ $ q = $ l < 0.5 ? $ l * (1 + $ s ) : $ l + $ s - $ l * $ s ;
387+ $ p = 2 * $ l - $ q ;
388+ $ r = $ hue2rgb ($ p , $ q , $ h + 1 /3 );
389+ $ g = $ hue2rgb ($ p , $ q , $ h );
390+ $ b = $ hue2rgb ($ p , $ q , $ h - 1 /3 );
391+ }
392+
393+ return array (round ($ r * 255 ), round ($ g * 255 ), round ($ b * 255 ));
394+ }
395+
214396}
0 commit comments