Signed-off-by: Julius Haertl <jus@bitgrid.net>pull/840/head
parent
b466628bfd
commit
af8976ab03
@ -0,0 +1,140 @@ |
||||
<?php |
||||
/** |
||||
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net> |
||||
* |
||||
* @author Julius Härtl <jus@bitgrid.net> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
|
||||
namespace OCA\Theming; |
||||
|
||||
use Imagick; |
||||
use ImagickPixel; |
||||
|
||||
class IconBuilder { |
||||
/** @var ThemingDefaults */ |
||||
private $themingDefaults; |
||||
/** @var Util */ |
||||
private $util; |
||||
|
||||
/** |
||||
* IconBuilder constructor. |
||||
* |
||||
* @param ThemingDefaults $themingDefaults |
||||
* @param Util $util |
||||
*/ |
||||
public function __construct( |
||||
ThemingDefaults $themingDefaults, |
||||
Util $util |
||||
) { |
||||
$this->themingDefaults = $themingDefaults; |
||||
$this->util = $util; |
||||
} |
||||
|
||||
/** |
||||
* @param $app app name |
||||
* @return string image blob |
||||
*/ |
||||
public function getFavicon($app) { |
||||
$icon = $this->renderAppIcon($app); |
||||
$icon->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1); |
||||
$icon->setImageFormat("png24"); |
||||
$data = $icon->getImageBlob(); |
||||
$icon->destroy(); |
||||
return $data; |
||||
} |
||||
|
||||
/** |
||||
* @param $app app name |
||||
* @return string image blob |
||||
*/ |
||||
public function getTouchIcon($app) { |
||||
$icon = $this->renderAppIcon($app); |
||||
$icon->setImageFormat("png24"); |
||||
$data = $icon->getImageBlob(); |
||||
$icon->destroy(); |
||||
return $data; |
||||
} |
||||
|
||||
/** |
||||
* Render app icon on themed background color |
||||
* fallback to logo |
||||
* |
||||
* @param $app app name |
||||
* @return Imagick |
||||
*/ |
||||
public function renderAppIcon($app) { |
||||
$appIcon = $this->util->getAppIcon($app); |
||||
|
||||
$color = $this->themingDefaults->getMailHeaderColor(); |
||||
$mime = mime_content_type($appIcon); |
||||
// generate background image with rounded corners |
||||
$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="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink">' . |
||||
'<rect x="0" y="0" rx="75" ry="75" width="512" height="512" style="fill:' . $color . ';" />' . |
||||
'</svg>'; |
||||
|
||||
// resize svg magic as this seems broken in Imagemagick |
||||
if($mime === "image/svg+xml") { |
||||
$svg = file_get_contents($appIcon); |
||||
|
||||
$tmp = new Imagick(); |
||||
$tmp->readImageBlob($svg); |
||||
$x = $tmp->getImageWidth(); |
||||
$y = $tmp->getImageHeight(); |
||||
$res = $tmp->getImageResolution(); |
||||
$tmp->destroy(); |
||||
|
||||
// convert svg to resized image |
||||
$appIconFile = new Imagick(); |
||||
$resX = (int)(512 * $res['x'] / $x * 2.53); |
||||
$resY = (int)(512 * $res['y'] / $y * 2.53); |
||||
$appIconFile->setResolution($resX, $resY); |
||||
$appIconFile->setBackgroundColor(new ImagickPixel('transparent')); |
||||
$appIconFile->readImageBlob($svg); |
||||
} else { |
||||
$appIconFile = new Imagick(); |
||||
$appIconFile->setBackgroundColor(new ImagickPixel('transparent')); |
||||
$appIconFile->readImageBlob(file_get_contents($appIcon)); |
||||
$appIconFile->scaleImage(512, 512, true); |
||||
} |
||||
|
||||
// offset for icon positioning |
||||
$border_w = (int)($appIconFile->getImageWidth() * 0.05); |
||||
$border_h = (int)($appIconFile->getImageHeight() * 0.05); |
||||
$innerWidth = (int)($appIconFile->getImageWidth() - $border_w * 2); |
||||
$innerHeight = (int)($appIconFile->getImageHeight() - $border_h * 2); |
||||
$appIconFile->adaptiveResizeImage($innerWidth, $innerHeight); |
||||
// center icon |
||||
$offset_w = 512 / 2 - $innerWidth / 2; |
||||
$offset_h = 512 / 2 - $innerHeight / 2; |
||||
|
||||
$appIconFile->setImageFormat("png24"); |
||||
|
||||
$finalIconFile = new Imagick(); |
||||
$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->resizeImage(512, 512, Imagick::FILTER_LANCZOS, 1); |
||||
|
||||
$appIconFile->destroy(); |
||||
return $finalIconFile; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,150 @@ |
||||
<?php |
||||
/** |
||||
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net> |
||||
* |
||||
* @author Julius Härtl <jus@bitgrid.net> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
namespace OCA\Theming\Tests; |
||||
|
||||
use OCA\Theming\IconBuilder; |
||||
use OCA\Theming\ThemingDefaults; |
||||
use OCA\Theming\Util; |
||||
use OCP\Files\IRootFolder; |
||||
use OCP\IConfig; |
||||
use Test\TestCase; |
||||
|
||||
class IconBuilderTest extends TestCase { |
||||
|
||||
/** @var IConfig */ |
||||
protected $config; |
||||
/** @var IRootFolder */ |
||||
protected $rootFolder; |
||||
/** @var ThemingDefaults */ |
||||
protected $themingDefaults; |
||||
/** @var Util */ |
||||
protected $util; |
||||
/** @var IconBuilder */ |
||||
protected $iconBuilder; |
||||
|
||||
protected function setUp() { |
||||
parent::setUp(); |
||||
|
||||
if(!extension_loaded('imagick')) { |
||||
$this->markTestSkipped('Imagemagick is required for dynamic icon generation.'); |
||||
} |
||||
$checkImagick = new \Imagick(); |
||||
if (count($checkImagick->queryFormats('SVG')) < 1) { |
||||
$this->markTestSkipped('No SVG provider present.'); |
||||
} |
||||
|
||||
$this->config = $this->getMockBuilder('\OCP\IConfig')->getMock(); |
||||
$this->rootFolder = $this->getMockBuilder('OCP\Files\IRootFolder')->getMock(); |
||||
$this->themingDefaults = $this->getMockBuilder('OCA\Theming\ThemingDefaults') |
||||
->disableOriginalConstructor()->getMock(); |
||||
$this->util = new Util($this->config, $this->rootFolder); |
||||
$this->iconBuilder = new IconBuilder($this->themingDefaults, $this->util); |
||||
} |
||||
|
||||
public function dataRenderAppIcon() { |
||||
return [ |
||||
['core', '#0082c9', 'touch-original.png'], |
||||
['core', '#FF0000', 'touch-core-red.png'], |
||||
['testing', '#FF0000', 'touch-testing-red.png'], |
||||
['comments', '#0082c9', 'touch-comments.png'], |
||||
['core', '#0082c9', 'touch-original-png.png'], |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataRenderAppIcon |
||||
* @param $app |
||||
* @param $color |
||||
* @param $file |
||||
*/ |
||||
public function testRenderAppIcon($app, $color, $file) { |
||||
|
||||
$this->themingDefaults->expects($this->once()) |
||||
->method('getMailHeaderColor') |
||||
->willReturn($color); |
||||
|
||||
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file); |
||||
$icon = $this->iconBuilder->renderAppIcon($app); |
||||
|
||||
$this->assertEquals(true, $icon->valid()); |
||||
$this->assertEquals(512, $icon->getImageWidth()); |
||||
$this->assertEquals(512, $icon->getImageHeight()); |
||||
$this->assertEquals($icon, $expectedIcon); |
||||
$icon->destroy(); |
||||
$expectedIcon->destroy(); |
||||
//$this->assertLessThan(0.0005, $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1]); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataRenderAppIcon |
||||
* @param $app |
||||
* @param $color |
||||
* @param $file |
||||
*/ |
||||
public function testGetTouchIcon($app, $color, $file) { |
||||
|
||||
$this->themingDefaults->expects($this->once()) |
||||
->method('getMailHeaderColor') |
||||
->willReturn($color); |
||||
|
||||
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file); |
||||
$icon = new \Imagick(); |
||||
$icon->readImageBlob($this->iconBuilder->getTouchIcon($app)); |
||||
|
||||
$this->assertEquals(true, $icon->valid()); |
||||
$this->assertEquals(512, $icon->getImageWidth()); |
||||
$this->assertEquals(512, $icon->getImageHeight()); |
||||
$this->assertEquals($icon, $expectedIcon); |
||||
$icon->destroy(); |
||||
$expectedIcon->destroy(); |
||||
//$this->assertLessThan(0.0005, $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1]); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataRenderAppIcon |
||||
* @param $app |
||||
* @param $color |
||||
* @param $file |
||||
*/ |
||||
public function testGetFavicon($app, $color, $file) { |
||||
|
||||
$this->themingDefaults->expects($this->once()) |
||||
->method('getMailHeaderColor') |
||||
->willReturn($color); |
||||
|
||||
$expectedIcon = new \Imagick(realpath(dirname(__FILE__)). "/data/" . $file); |
||||
$icon = new \Imagick(); |
||||
$icon->readImageBlob($this->iconBuilder->getFavicon($app)); |
||||
|
||||
$this->assertEquals(true, $icon->valid()); |
||||
$this->assertEquals(32, $icon->getImageWidth()); |
||||
$this->assertEquals(32, $icon->getImageHeight()); |
||||
$icon->destroy(); |
||||
$expectedIcon->destroy(); |
||||
//$this->assertLessThan(0.0005, $expectedIcon->compareImages($icon, Imagick::METRIC_MEANABSOLUTEERROR)[1]); |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue