Completed
Push — master ( ed37dc...26bcf4 )
by Morris
19:46
created

IconBuilder   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 3
dl 0
loc 204
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B getFavicon() 0 38 4
A getTouchIcon() 0 14 3
D renderAppIcon() 0 96 13
A colorSvg() 0 15 4
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Julius Härtl <[email protected]>
4
 *
5
 * @author Jan-Christoph Borchardt <[email protected]>
6
 * @author Julius Haertl <[email protected]>
7
 * @author Julius Härtl <[email protected]>
8
 *
9
 * @license GNU AGPL version 3 or any later version
10
 *
11
 * This program is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License as
13
 * published by the Free Software Foundation, either version 3 of the
14
 * License, or (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23
 *
24
 */
25
26
namespace OCA\Theming;
27
28
use Imagick;
29
use ImagickPixel;
30
use OCP\App\AppPathNotFoundException;
31
use OCP\Files\SimpleFS\ISimpleFile;
32
33
class IconBuilder {
34
	/** @var ThemingDefaults */
35
	private $themingDefaults;
36
	/** @var Util */
37
	private $util;
38
39
	/**
40
	 * IconBuilder constructor.
41
	 *
42
	 * @param ThemingDefaults $themingDefaults
43
	 * @param Util $util
44
	 */
45
	public function __construct(
46
		ThemingDefaults $themingDefaults,
47
		Util $util
48
	) {
49
		$this->themingDefaults = $themingDefaults;
50
		$this->util = $util;
51
	}
52
53
	/**
54
	 * @param $app string app name
55
	 * @return string|false image blob
56
	 */
57
	public function getFavicon($app) {
58
		if (!$this->themingDefaults->shouldReplaceIcons()) {
59
			return false;
60
		}
61
		try {
62
			$favicon = new Imagick();
63
			$favicon->setFormat("ico");
64
			$icon = $this->renderAppIcon($app, 128);
65
			if ($icon === false) {
66
				return false;
67
			}
68
			$icon->setImageFormat("png32");
69
70
			$clone = clone $icon;
71
			$clone->scaleImage(16,0);
72
			$favicon->addImage($clone);
73
74
			$clone = clone $icon;
75
			$clone->scaleImage(32,0);
76
			$favicon->addImage($clone);
77
78
			$clone = clone $icon;
79
			$clone->scaleImage(64,0);
80
			$favicon->addImage($clone);
81
82
			$clone = clone $icon;
83
			$clone->scaleImage(128,0);
84
			$favicon->addImage($clone);
85
86
			$data = $favicon->getImagesBlob();
87
			$favicon->destroy();
88
			$icon->destroy();
89
			$clone->destroy();
90
			return $data;
91
		} catch (\ImagickException $e) {
92
			return false;
93
		}
94
	}
95
96
	/**
97
	 * @param $app string app name
98
	 * @return string|false image blob
99
	 */
100
	public function getTouchIcon($app) {
101
		try {
102
			$icon = $this->renderAppIcon($app, 512);
103
			if ($icon === false) {
104
				return false;
105
			}
106
			$icon->setImageFormat("png32");
107
			$data = $icon->getImageBlob();
108
			$icon->destroy();
109
			return $data;
110
		} catch (\ImagickException $e) {
111
			return false;
112
		}
113
	}
114
115
	/**
116
	 * Render app icon on themed background color
117
	 * fallback to logo
118
	 *
119
	 * @param $app string app name
120
	 * @param $size int size of the icon in px
121
	 * @return Imagick|false
122
	 */
123
	public function renderAppIcon($app, $size) {
124
		$appIcon = $this->util->getAppIcon($app);
125
		if($appIcon === false) {
126
			return false;
127
		}
128
		if ($appIcon instanceof ISimpleFile) {
129
			$appIconContent = $appIcon->getContent();
130
			$mime = $appIcon->getMimeType();
131
		} else {
132
			$appIconContent = file_get_contents($appIcon);
133
			$mime = mime_content_type($appIcon);
134
		}
135
136
		if($appIconContent === false || $appIconContent === "") {
137
			return false;
138
		}
139
140
		$color = $this->themingDefaults->getColorPrimary();
141
142
		// generate background image with rounded corners
143
		$background = '<?xml version="1.0" encoding="UTF-8"?>' .
144
			'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink">' .
145
			'<rect x="0" y="0" rx="100" ry="100" width="512" height="512" style="fill:' . $color . ';" />' .
146
			'</svg>';
147
		// resize svg magic as this seems broken in Imagemagick
148
		if($mime === "image/svg+xml" || substr($appIconContent, 0, 4) === "<svg") {
149
			if(substr($appIconContent, 0, 5) !== "<?xml") {
150
				$svg = "<?xml version=\"1.0\"?>".$appIconContent;
151
			} else {
152
				$svg = $appIconContent;
153
			}
154
			$tmp = new Imagick();
155
			$tmp->readImageBlob($svg);
156
			$x = $tmp->getImageWidth();
157
			$y = $tmp->getImageHeight();
158
			$res = $tmp->getImageResolution();
159
			$tmp->destroy();
160
161
			if($x>$y) {
162
				$max = $x;
163
			} else {
164
				$max = $y;
165
			}
166
167
			// convert svg to resized image
168
			$appIconFile = new Imagick();
169
			$resX = (int)(512 * $res['x'] / $max * 2.53);
170
			$resY = (int)(512 * $res['y'] / $max * 2.53);
171
			$appIconFile->setResolution($resX, $resY);
172
			$appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
173
			$appIconFile->readImageBlob($svg);
174
175
			/**
176
			 * invert app icons for bright primary colors
177
			 * the default nextcloud logo will not be inverted to black
178
			 */
179
			if ($this->util->invertTextColor($color)
180
				&& !$appIcon instanceof ISimpleFile
181
				&& $app !== "core"
182
			) {
183
				$appIconFile->negateImage(false);
184
			}
185
			$appIconFile->scaleImage(512, 512, true);
186
		} else {
187
			$appIconFile = new Imagick();
188
			$appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
189
			$appIconFile->readImageBlob($appIconContent);
190
			$appIconFile->scaleImage(512, 512, true);
191
		}
192
		// offset for icon positioning
193
		$border_w = (int)($appIconFile->getImageWidth() * 0.05);
194
		$border_h = (int)($appIconFile->getImageHeight() * 0.05);
195
		$innerWidth = (int)($appIconFile->getImageWidth() - $border_w * 2);
196
		$innerHeight = (int)($appIconFile->getImageHeight() - $border_h * 2);
197
		$appIconFile->adaptiveResizeImage($innerWidth, $innerHeight);
198
		// center icon
199
		$offset_w = 512 / 2 - $innerWidth / 2;
200
		$offset_h = 512 / 2 - $innerHeight / 2;
201
202
		$finalIconFile = new Imagick();
203
		$finalIconFile->setBackgroundColor(new ImagickPixel('transparent'));
204
		$finalIconFile->readImageBlob($background);
205
		$finalIconFile->setImageVirtualPixelMethod(Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
206
		$finalIconFile->setImageArtifact('compose:args', "1,0,-0.5,0.5");
207
		$finalIconFile->compositeImage($appIconFile, Imagick::COMPOSITE_ATOP, $offset_w, $offset_h);
208
		$finalIconFile->setImageFormat('png24');
209
		if (defined("Imagick::INTERPOLATE_BICUBIC") === true) {
210
			$filter = Imagick::INTERPOLATE_BICUBIC;
211
		} else {
212
			$filter = Imagick::FILTER_LANCZOS;
213
		}
214
		$finalIconFile->resizeImage($size, $size, $filter, 1, false);
215
216
		$appIconFile->destroy();
217
		return $finalIconFile;
218
	}
219
220
	public function colorSvg($app, $image) {
221
		try {
222
			$imageFile = $this->util->getAppImage($app, $image);
223
		} catch (AppPathNotFoundException $e) {
224
			return false;
225
		}
226
		$svg = file_get_contents($imageFile);
227
		if ($svg !== false && $svg !== "") {
228
			$color = $this->util->elementColor($this->themingDefaults->getColorPrimary());
229
			$svg = $this->util->colorizeSvg($svg, $color);
230
			return $svg;
231
		} else {
232
			return false;
233
		}
234
	}
235
236
}
237