IconBuilder.php 6.7 KB

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