Passed
Push — master ( 8d9c41...e5a195 )
by Pauli
10:20
created

Color::red()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 * @copyright 2018 John Molakvoæ <[email protected]>
8
 *
9
 * @author Christopher Schäpers <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Jan-Christoph Borchardt <[email protected]>
12
 * @author Joas Schilling <[email protected]>
13
 * @author John Molakvoæ <[email protected]>
14
 * @author Julius Härtl <[email protected]>
15
 * @author Michael Weimann <[email protected]>
16
 * @author Morris Jobke <[email protected]>
17
 * @author Robin Appelman <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Sergey Shliakhov <[email protected]>
20
 * @author Thomas Müller <[email protected]>
21
 * @author Pauli Järvinen <[email protected]>
22
 *
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program. If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
namespace OCA\Music\Utility;
39
40
use OCA\Music\Utility\PlaceholderImage\Color;
41
42
/**
43
 * Create placeholder images. This is a modified and stripped-down copy of https://github.com/nextcloud/server/blob/master/lib/private/Avatar/Avatar.php.
44
 */
45
class PlaceholderImage {
46
47
	public static function generate(string $name, string $seed, int $size): string {
48
		$text = self::getText($name);
49
		$backgroundColor = self::getBackgroundColor($seed);
50
51
		$im = \imagecreatetruecolor($size, $size);
52
		$background = \imagecolorallocate(
53
			$im,
54
			$backgroundColor->red(),
55
			$backgroundColor->green(),
56
			$backgroundColor->blue()
57
		);
58
		$white = \imagecolorallocate($im, 255, 255, 255);
59
		\imagefilledrectangle($im, 0, 0, $size, $size, $background);
60
61
		$font = self::findFontPath();
62
63
		$fontSize = $size * 0.4;
64
		[$x, $y] = self::imageTtfCenter($im, $text, $font, (int)$fontSize);
0 ignored issues
show
Bug introduced by
It seems like $im can also be of type GdImage; however, parameter $image of OCA\Music\Utility\Placeh...Image::imageTtfCenter() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

64
		[$x, $y] = self::imageTtfCenter(/** @scrutinizer ignore-type */ $im, $text, $font, (int)$fontSize);
Loading history...
65
66
		\imagettftext($im, $fontSize, 0, $x, $y, $white, $font, $text);
67
68
		\ob_start();
69
		\imagepng($im);
70
		$data = \ob_get_contents();
71
		\ob_end_clean();
72
73
		return $data;
74
	}
75
76
	private static function findFontPath() : string {
77
		// try to find the font from the hosting cloud instance first
78
		$files = \glob(__DIR__ . '/../../../../core/fonts/*Regular.ttf');
79
80
		// If the suitable font was not found from the core, then fall back to our local font.
81
		// The drawback is that this font contains only the Latin alphabet.
82
		return $files[0] ?? __DIR__ . '/../../fonts/OpenSans-Regular.woff';
83
	}
84
85
	/**
86
	 * Returns the first letter of the display name, or "?" if no name given.
87
	 */
88
	private static function getText(string $displayName): string {
89
		if (empty($displayName) === true) {
90
			return '?';
91
		}
92
		$firstTwoLetters = array_map(function ($namePart) {
93
			return \mb_strtoupper(mb_substr($namePart, 0, 1), 'UTF-8');
94
		}, explode(' ', $displayName, 2));
95
			return \implode('', $firstTwoLetters);
96
	}
97
98
	/**
99
	 * Calculate real image ttf center
100
	 *
101
	 * @param resource $image
102
	 * @param string $text text string
103
	 * @param string $font font path
104
	 * @param int $size font size
105
	 * @param int $angle
106
	 * @return int[]
107
	 */
108
	private static function imageTtfCenter($image, string $text, string $font, int $size, int $angle = 0): array {
109
		// Image width & height
110
		$xi = imagesx($image);
111
		$yi = imagesy($image);
112
113
		// bounding box
114
		$box = \imagettfbbox($size, $angle, $font, $text);
115
116
		// imagettfbbox can return negative int
117
		$xr = \abs(\max($box[2], $box[4]));
118
		$yr = \abs(\max($box[5], $box[7]));
119
120
		// calculate bottom left placement
121
		$x = \intval(($xi - $xr) / 2);
122
		$y = \intval(($yi + $yr) / 2);
123
124
		return [$x, $y];
125
	}
126
127
128
	/**
129
	 * Convert a string to an integer evenly
130
	 * @param string $hash the text to parse
131
	 * @param int $maximum the maximum range
132
	 * @return int between 0 and $maximum
133
	 */
134
	private static function hashToInt(string $hash, int $maximum): int {
135
		$final = 0;
136
		$result = [];
137
138
		// Splitting evenly the string
139
		for ($i = 0; $i < \strlen($hash); $i++) {
140
			// chars in md5 goes up to f, hex:16
141
			$result[] = \intval(\substr($hash, $i, 1), 16) % 16;
142
		}
143
		// Adds up all results
144
		foreach ($result as $value) {
145
			$final += $value;
146
		}
147
		// chars in md5 goes up to f, hex:16
148
		return \intval($final % $maximum);
149
	}
150
151
	/**
152
	 * @return Color Object containing r g b int in the range [0, 255]
153
	 */
154
	private static function getBackgroundColor(string $hash): Color {
155
		// Normalize hash
156
		$hash = \strtolower($hash);
157
158
		// Already a md5 hash?
159
		if (preg_match('/^([0-9a-f]{4}-?){8}$/', $hash, $matches) !== 1) {
160
			$hash = \md5($hash);
161
		}
162
163
		// Remove unwanted char
164
		$hash = \preg_replace('/[^0-9a-f]+/', '', $hash);
165
166
		$red = new Color(182, 70, 157);
167
		$yellow = new Color(221, 203, 85);
168
		$blue = new Color(0, 130, 201); // Nextcloud blue
169
170
		// Number of steps to go from a color to another
171
		// 3 colors * 6 will result in 18 generated colors
172
		$steps = 6;
173
174
		$palette1 = Color::mixPalette($steps, $red, $yellow);
175
		$palette2 = Color::mixPalette($steps, $yellow, $blue);
176
		$palette3 = Color::mixPalette($steps, $blue, $red);
177
178
		$finalPalette = \array_merge($palette1, $palette2, $palette3);
179
180
		return $finalPalette[self::hashToInt($hash, $steps * 3)];
181
	}
182
}
183
184
185
namespace OCA\Music\Utility\PlaceholderImage;
186
187
/**
188
 * A strripped-down copy of https://github.com/nextcloud/server/blob/master/lib/public/Color.php
189
 */
190
class Color {
191
	private $r;
192
	private $g;
193
	private $b;
194
195
	public function __construct(int $r, int $g, int $b) {
196
		$this->r = $r;
197
		$this->g = $g;
198
		$this->b = $b;
199
	}
200
201
	public function red(): int {
202
		return $this->r;
203
	}
204
205
	public function green(): int {
206
		return $this->g;
207
	}
208
209
	public function blue(): int {
210
		return $this->b;
211
	}
212
213
	/**
214
	 * Mix two colors
215
	 *
216
	 * @param int $steps the number of intermediate colors that should be generated for the palette
217
	 * @param Color $color1 the first color
218
	 * @param Color $color2 the second color
219
	 * @return Color[]
220
	 */
221
	public static function mixPalette(int $steps, Color $color1, Color $color2): array {
222
		$palette = [$color1];
223
		$step = self::stepCalc($steps, [$color1, $color2]);
224
		for ($i = 1; $i < $steps; $i++) {
225
			$r = intval($color1->red() + ($step[0] * $i));
226
			$g = intval($color1->green() + ($step[1] * $i));
227
			$b = intval($color1->blue() + ($step[2] * $i));
228
			$palette[] = new Color($r, $g, $b);
229
		}
230
		return $palette;
231
	}
232
233
	/**
234
	 * Calculate steps between two Colors
235
	 * @param int $steps start color
236
	 * @param Color[] $ends end color
237
	 * @return array{0: int, 1: int, 2: int} [r,g,b] steps for each color to go from $steps to $ends
238
	 */
239
	private static function stepCalc(int $steps, array $ends): array {
240
		return [
241
			($ends[1]->red() - $ends[0]->red()) / $steps,
242
			($ends[1]->green() - $ends[0]->green()) / $steps,
243
			($ends[1]->blue() - $ends[0]->blue()) / $steps
244
		];
245
	}
246
}