aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming/lib/IconBuilder.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/theming/lib/IconBuilder.php')
-rw-r--r--apps/theming/lib/IconBuilder.php208
1 files changed, 208 insertions, 0 deletions
diff --git a/apps/theming/lib/IconBuilder.php b/apps/theming/lib/IconBuilder.php
new file mode 100644
index 00000000000..63f4559970d
--- /dev/null
+++ b/apps/theming/lib/IconBuilder.php
@@ -0,0 +1,208 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\Theming;
+
+use Imagick;
+use ImagickPixel;
+use OCP\Files\SimpleFS\ISimpleFile;
+
+class IconBuilder {
+ /**
+ * IconBuilder constructor.
+ *
+ * @param ThemingDefaults $themingDefaults
+ * @param Util $util
+ * @param ImageManager $imageManager
+ */
+ public function __construct(
+ private ThemingDefaults $themingDefaults,
+ private Util $util,
+ private ImageManager $imageManager,
+ ) {
+ }
+
+ /**
+ * @param $app string app name
+ * @return string|false image blob
+ */
+ public function getFavicon($app) {
+ if (!$this->imageManager->shouldReplaceIcons()) {
+ return false;
+ }
+ try {
+ $favicon = new Imagick();
+ $favicon->setFormat('ico');
+ $icon = $this->renderAppIcon($app, 128);
+ if ($icon === false) {
+ return false;
+ }
+ $icon->setImageFormat('png32');
+
+ $clone = clone $icon;
+ $clone->scaleImage(16, 0);
+ $favicon->addImage($clone);
+
+ $clone = clone $icon;
+ $clone->scaleImage(32, 0);
+ $favicon->addImage($clone);
+
+ $clone = clone $icon;
+ $clone->scaleImage(64, 0);
+ $favicon->addImage($clone);
+
+ $clone = clone $icon;
+ $clone->scaleImage(128, 0);
+ $favicon->addImage($clone);
+
+ $data = $favicon->getImagesBlob();
+ $favicon->destroy();
+ $icon->destroy();
+ $clone->destroy();
+ return $data;
+ } catch (\ImagickException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @param $app string app name
+ * @return string|false image blob
+ */
+ public function getTouchIcon($app) {
+ try {
+ $icon = $this->renderAppIcon($app, 512);
+ if ($icon === false) {
+ return false;
+ }
+ $icon->setImageFormat('png32');
+ $data = $icon->getImageBlob();
+ $icon->destroy();
+ return $data;
+ } catch (\ImagickException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Render app icon on themed background color
+ * fallback to logo
+ *
+ * @param string $app app name
+ * @param int $size size of the icon in px
+ * @return Imagick|false
+ */
+ public function renderAppIcon($app, $size) {
+ $appIcon = $this->util->getAppIcon($app);
+ if ($appIcon instanceof ISimpleFile) {
+ $appIconContent = $appIcon->getContent();
+ $mime = $appIcon->getMimeType();
+ } elseif (!file_exists($appIcon)) {
+ return false;
+ } else {
+ $appIconContent = file_get_contents($appIcon);
+ $mime = mime_content_type($appIcon);
+ }
+
+ if ($appIconContent === false || $appIconContent === '') {
+ return false;
+ }
+
+ $color = $this->themingDefaults->getColorPrimary();
+
+ // generate background image with rounded corners
+ $cornerRadius = 0.2 * $size;
+ $background = '<?xml version="1.0" encoding="UTF-8"?>'
+ . '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" width="' . $size . '" height="' . $size . '" xmlns:xlink="http://www.w3.org/1999/xlink">'
+ . '<rect x="0" y="0" rx="' . $cornerRadius . '" ry="' . $cornerRadius . '" width="' . $size . '" height="' . $size . '" style="fill:' . $color . ';" />'
+ . '</svg>';
+ // resize svg magic as this seems broken in Imagemagick
+ if ($mime === 'image/svg+xml' || substr($appIconContent, 0, 4) === '<svg') {
+ if (substr($appIconContent, 0, 5) !== '<?xml') {
+ $svg = '<?xml version="1.0"?>' . $appIconContent;
+ } else {
+ $svg = $appIconContent;
+ }
+ $tmp = new Imagick();
+ $tmp->setBackgroundColor(new ImagickPixel('transparent'));
+ $tmp->setResolution(72, 72);
+ $tmp->readImageBlob($svg);
+ $x = $tmp->getImageWidth();
+ $y = $tmp->getImageHeight();
+ $tmp->destroy();
+
+ // convert svg to resized image
+ $appIconFile = new Imagick();
+ $resX = (int)(72 * $size / $x);
+ $resY = (int)(72 * $size / $y);
+ $appIconFile->setResolution($resX, $resY);
+ $appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
+ $appIconFile->readImageBlob($svg);
+
+ /**
+ * invert app icons for bright primary colors
+ * the default nextcloud logo will not be inverted to black
+ */
+ if ($this->util->isBrightColor($color)
+ && !$appIcon instanceof ISimpleFile
+ && $app !== 'core'
+ ) {
+ $appIconFile->negateImage(false);
+ }
+ } else {
+ $appIconFile = new Imagick();
+ $appIconFile->setBackgroundColor(new ImagickPixel('transparent'));
+ $appIconFile->readImageBlob($appIconContent);
+ }
+ // offset for icon positioning
+ $padding = 0.15;
+ $border_w = (int)($appIconFile->getImageWidth() * $padding);
+ $border_h = (int)($appIconFile->getImageHeight() * $padding);
+ $innerWidth = ($appIconFile->getImageWidth() - $border_w * 2);
+ $innerHeight = ($appIconFile->getImageHeight() - $border_h * 2);
+ $appIconFile->adaptiveResizeImage($innerWidth, $innerHeight);
+ // center icon
+ $offset_w = (int)($size / 2 - $innerWidth / 2);
+ $offset_h = (int)($size / 2 - $innerHeight / 2);
+
+ $finalIconFile = new Imagick();
+ $finalIconFile->setBackgroundColor(new ImagickPixel('transparent'));
+ $finalIconFile->readImageBlob($background);
+ $finalIconFile->setImageVirtualPixelMethod(Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
+ $finalIconFile->setImageArtifact('compose:args', '1,0,-0.5,0.5');
+ $finalIconFile->compositeImage($appIconFile, Imagick::COMPOSITE_ATOP, $offset_w, $offset_h);
+ $finalIconFile->setImageFormat('png24');
+ if (defined('Imagick::INTERPOLATE_BICUBIC') === true) {
+ $filter = Imagick::INTERPOLATE_BICUBIC;
+ } else {
+ $filter = Imagick::FILTER_LANCZOS;
+ }
+ $finalIconFile->resizeImage($size, $size, $filter, 1, false);
+
+ $appIconFile->destroy();
+ return $finalIconFile;
+ }
+
+ /**
+ * @param string $app app name
+ * @param string $image relative path to svg file in app directory
+ * @return string|false content of a colorized svg file
+ */
+ public function colorSvg($app, $image) {
+ $imageFile = $this->util->getAppImage($app, $image);
+ if ($imageFile === false || $imageFile === '' || !file_exists($imageFile)) {
+ return false;
+ }
+ $svg = file_get_contents($imageFile);
+ if ($svg !== false && $svg !== '') {
+ $color = $this->util->elementColor($this->themingDefaults->getColorPrimary());
+ $svg = $this->util->colorizeSvg($svg, $color);
+ return $svg;
+ } else {
+ return false;
+ }
+ }
+}