Completed
Push — master ( 5ce3c7...241579 )
by Morris
21:41
created

IconBuilder::colorSvg()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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