IconBuilder.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
  4. *
  5. * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
  6. * @author Julius Haertl <jus@bitgrid.net>
  7. * @author Julius Härtl <jus@bitgrid.net>
  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. namespace OCA\Theming;
  26. use Imagick;
  27. use ImagickPixel;
  28. use OCP\App\AppPathNotFoundException;
  29. use OCP\Files\SimpleFS\ISimpleFile;
  30. class IconBuilder {
  31. /** @var ThemingDefaults */
  32. private $themingDefaults;
  33. /** @var Util */
  34. private $util;
  35. /** @var ImageManager */
  36. private $imageManager;
  37. /**
  38. * IconBuilder constructor.
  39. *
  40. * @param ThemingDefaults $themingDefaults
  41. * @param Util $util
  42. * @param ImageManager $imageManager
  43. */
  44. public function __construct(
  45. ThemingDefaults $themingDefaults,
  46. Util $util,
  47. ImageManager $imageManager
  48. ) {
  49. $this->themingDefaults = $themingDefaults;
  50. $this->util = $util;
  51. $this->imageManager = $imageManager;
  52. }
  53. /**
  54. * @param $app string app name
  55. * @return string|false image blob
  56. */
  57. public function getFavicon($app) {
  58. if (!$this->imageManager->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. $clone = clone $icon;
  70. $clone->scaleImage(16,0);
  71. $favicon->addImage($clone);
  72. $clone = clone $icon;
  73. $clone->scaleImage(32,0);
  74. $favicon->addImage($clone);
  75. $clone = clone $icon;
  76. $clone->scaleImage(64,0);
  77. $favicon->addImage($clone);
  78. $clone = clone $icon;
  79. $clone->scaleImage(128,0);
  80. $favicon->addImage($clone);
  81. $data = $favicon->getImagesBlob();
  82. $favicon->destroy();
  83. $icon->destroy();
  84. $clone->destroy();
  85. return $data;
  86. } catch (\ImagickException $e) {
  87. return false;
  88. }
  89. }
  90. /**
  91. * @param $app string app name
  92. * @return string|false image blob
  93. */
  94. public function getTouchIcon($app) {
  95. try {
  96. $icon = $this->renderAppIcon($app, 512);
  97. if ($icon === false) {
  98. return false;
  99. }
  100. $icon->setImageFormat("png32");
  101. $data = $icon->getImageBlob();
  102. $icon->destroy();
  103. return $data;
  104. } catch (\ImagickException $e) {
  105. return false;
  106. }
  107. }
  108. /**
  109. * Render app icon on themed background color
  110. * fallback to logo
  111. *
  112. * @param $app string app name
  113. * @param $size int size of the icon in px
  114. * @return Imagick|false
  115. */
  116. public function renderAppIcon($app, $size) {
  117. $appIcon = $this->util->getAppIcon($app);
  118. if($appIcon === false) {
  119. return false;
  120. }
  121. if ($appIcon instanceof ISimpleFile) {
  122. $appIconContent = $appIcon->getContent();
  123. $mime = $appIcon->getMimeType();
  124. } else {
  125. $appIconContent = file_get_contents($appIcon);
  126. $mime = mime_content_type($appIcon);
  127. }
  128. if($appIconContent === false || $appIconContent === "") {
  129. return false;
  130. }
  131. $color = $this->themingDefaults->getColorPrimary();
  132. // generate background image with rounded corners
  133. $background = '<?xml version="1.0" encoding="UTF-8"?>' .
  134. '<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">' .
  135. '<rect x="0" y="0" rx="100" ry="100" width="512" height="512" style="fill:' . $color . ';" />' .
  136. '</svg>';
  137. // resize svg magic as this seems broken in Imagemagick
  138. if($mime === "image/svg+xml" || substr($appIconContent, 0, 4) === "<svg") {
  139. if(substr($appIconContent, 0, 5) !== "<?xml") {
  140. $svg = "<?xml version=\"1.0\"?>".$appIconContent;
  141. } else {
  142. $svg = $appIconContent;
  143. }
  144. $tmp = new Imagick();
  145. $tmp->readImageBlob($svg);
  146. $x = $tmp->getImageWidth();
  147. $y = $tmp->getImageHeight();
  148. $res = $tmp->getImageResolution();
  149. $tmp->destroy();
  150. if($x>$y) {
  151. $max = $x;
  152. } else {
  153. $max = $y;
  154. }
  155. // convert svg to resized image
  156. $appIconFile = new Imagick();
  157. $resX = (int)(512 * $res['x'] / $max * 2.53);
  158. $resY = (int)(512 * $res['y'] / $max * 2.53);
  159. $appIconFile->setResolution($resX, $resY);
  160. $appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
  161. $appIconFile->readImageBlob($svg);
  162. /**
  163. * invert app icons for bright primary colors
  164. * the default nextcloud logo will not be inverted to black
  165. */
  166. if ($this->util->invertTextColor($color)
  167. && !$appIcon instanceof ISimpleFile
  168. && $app !== "core"
  169. ) {
  170. $appIconFile->negateImage(false);
  171. }
  172. $appIconFile->scaleImage(512, 512, true);
  173. } else {
  174. $appIconFile = new Imagick();
  175. $appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
  176. $appIconFile->readImageBlob($appIconContent);
  177. $appIconFile->scaleImage(512, 512, true);
  178. }
  179. // offset for icon positioning
  180. $border_w = (int)($appIconFile->getImageWidth() * 0.05);
  181. $border_h = (int)($appIconFile->getImageHeight() * 0.05);
  182. $innerWidth = (int)($appIconFile->getImageWidth() - $border_w * 2);
  183. $innerHeight = (int)($appIconFile->getImageHeight() - $border_h * 2);
  184. $appIconFile->adaptiveResizeImage($innerWidth, $innerHeight);
  185. // center icon
  186. $offset_w = 512 / 2 - $innerWidth / 2;
  187. $offset_h = 512 / 2 - $innerHeight / 2;
  188. $finalIconFile = new Imagick();
  189. $finalIconFile->setBackgroundColor(new ImagickPixel('transparent'));
  190. $finalIconFile->readImageBlob($background);
  191. $finalIconFile->setImageVirtualPixelMethod(Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
  192. $finalIconFile->setImageArtifact('compose:args', "1,0,-0.5,0.5");
  193. $finalIconFile->compositeImage($appIconFile, Imagick::COMPOSITE_ATOP, $offset_w, $offset_h);
  194. $finalIconFile->setImageFormat('png24');
  195. if (defined("Imagick::INTERPOLATE_BICUBIC") === true) {
  196. $filter = Imagick::INTERPOLATE_BICUBIC;
  197. } else {
  198. $filter = Imagick::FILTER_LANCZOS;
  199. }
  200. $finalIconFile->resizeImage($size, $size, $filter, 1, false);
  201. $appIconFile->destroy();
  202. return $finalIconFile;
  203. }
  204. public function colorSvg($app, $image) {
  205. try {
  206. $imageFile = $this->util->getAppImage($app, $image);
  207. } catch (AppPathNotFoundException $e) {
  208. return false;
  209. }
  210. $svg = file_get_contents($imageFile);
  211. if ($svg !== false && $svg !== "") {
  212. $color = $this->util->elementColor($this->themingDefaults->getColorPrimary());
  213. $svg = $this->util->colorizeSvg($svg, $color);
  214. return $svg;
  215. } else {
  216. return false;
  217. }
  218. }
  219. }