ytkns/src/Effects/BackgroundImageEffect.php
2020-06-10 16:03:13 +00:00

402 lines
15 KiB
PHP

<?php
namespace YTKNS\Effects;
use Exception;
use YTKNS\Colour;
use YTKNS\Gradient;
use YTKNS\HtmlTag;
use YTKNS\PageBuilder;
use YTKNS\PageEffectInterface;
use YTKNS\PageEffectException;
use YTKNS\Upload;
use YTKNS\UploadNotFoundException;
class BackgroundImageEffect implements PageEffectInterface {
private const IMG_MIME = [
'image/png',
'image/jpeg',
'image/gif',
];
private const ATTACH_OPTS = [
'scroll' => 'Relative to browser',
'fixed' => 'Fixed to browser',
'local' => 'Local to browser',
];
private const POSITION_OPTS = [
'top left' => 'Top left',
'top center' => 'Top center',
'top right' => 'Top right',
'center left' => 'Center left',
'center center' => 'Center',
'center right' => 'Center right',
'bottom left' => 'Bottom left',
'bottom center' => 'Bottom center',
'bottom right' => 'Bottom right',
];
private const REPEAT_OPTS = [
'repeat' => 'Repeat',
'repeat-x' => 'Repeat horizontally',
'repeat-y' => 'Repeat vertically',
'space' => 'Repeat without clipping',
'round' => 'Repeat with stretching',
'no-repeat' => 'Don\'t repeat',
];
private const SIZE_OPTS = [
'auto auto' => 'Determine automatically',
'cover' => 'Cover entire screen',
'contain' => 'Contain within screen',
];
private const SLIDE_DIRS = [
'tl' => 'Top left',
'tc' => 'Top',
'tr' => 'Top right',
'cl' => 'Left',
'cr' => 'Right',
'bl' => 'Bottom left',
'bc' => 'Bottom',
'br' => 'Bottom right',
];
private const SLIDE_MIN = 0.01;
private const SLIDE_MAX = 1000;
private const SPIN_DIRS = [
'cw' => 'Clockwise',
'ccw' => 'Counterclockwise',
];
private const SPIN_MIN = 0.01;
private const SPIN_MAX = 1000;
private $imageUpload = null;
private $slide = false;
private $slideSpeed = 1;
private $slideDirection = 'br';
private $spin = false;
private $spinSpeed = 1;
private $spinDirection = 'cw';
private $attachment = null;
private $colour = null;
private $position = null;
private $repeat = null;
private $size = null;
private $syncWithAudio = false;
private $gradient = null;
private $gradientAnimate = false;
public function getEffectName(): string {
return 'Background Image';
}
public function getEffectProperties(): array {
return [
[
'name' => 'img',
'title' => 'Image',
'type' => [
'name' => 'upload',
'allowed' => self::IMG_MIME,
],
],
[
'name' => 'sld',
'title' => 'Slide Animation',
'default' => false,
'type' => [
'name' => 'bool',
],
],
[
'name' => 'sldspd',
'title' => 'Slide Animation Period',
'default' => 1,
'type' => [
'name' => 'float',
'min' => self::SLIDE_MIN,
'max' => self::SLIDE_MAX,
],
],
[
'name' => 'slddir',
'title' => 'Slide Animation Direction',
'default' => 'br',
'type' => [
'name' => 'select',
'options' => self::SLIDE_DIRS,
],
],
[
'name' => 'spin',
'title' => 'Spin Animation',
'default' => false,
'type' => [
'name' => 'bool',
],
],
[
'name' => 'spnspd',
'title' => 'Spin Animation Period',
'default' => 1,
'type' => [
'name' => 'float',
'min' => self::SPIN_MIN,
'max' => self::SPIN_MAX,
],
],
[
'name' => 'spndir',
'title' => 'Spin Animation Direction',
'default' => 'br',
'type' => [
'name' => 'select',
'options' => self::SPIN_DIRS,
],
],
[
'name' => 'attach',
'title' => 'Attachment',
'type' => [
'name' => 'select',
'options' => self::ATTACH_OPTS,
],
],
[
'name' => 'col',
'title' => 'Colour',
'type' => [
'name' => 'colour',
],
],
[
'name' => 'pos',
'title' => 'Position',
'type' => [
'name' => 'select',
'options' => self::POSITION_OPTS,
],
],
[
'name' => 'rep',
'title' => 'Repeat',
'type' => [
'name' => 'select',
'options' => self::REPEAT_OPTS,
],
],
[
'name' => 'size',
'title' => 'Size',
'type' => [
'name' => 'select',
'options' => self::SIZE_OPTS,
],
],
[
'name' => 'sync',
'title' => 'Synchronise with Background Audio',
'default' => false,
'type' => [
'name' => 'bool',
],
],
[
'name' => 'grad',
'title' => 'Gradient',
'default' => [],
'type' => [
'name' => 'gradient',
],
],
/*[
'name' => 'grdanm',
'title' => 'Apply animations to gradient',
'default' => false,
'type' => [
'name' => 'bool',
],
],*/
];
}
public function setEffectParams(array $vars, bool $quiet = false): void {
try {
if(isset($vars['img']) && is_string($vars['img'])) {
try {
$this->imageUpload = Upload::byId($vars['img']);
} catch(Exception $ex) {
if(!$quiet)
throw $ex;
}
if(!$quiet && !in_array($this->imageUpload->getType(), self::IMG_MIME))
throw new PageEffectException('Image upload was of invalid type.');
}
} catch(UploadNotFoundException $ex) {
$this->imageUpload = null;
}
if(isset($vars['sld']))
$this->slide = is_bool($vars['sld']) ? $vars['sld'] : (is_string($vars['sld']) ? boolval($vars['sld']) : false);
if(isset($vars['spin']))
$this->spin = is_bool($vars['spin']) ? $vars['spin'] : (is_string($vars['spin']) ? boolval($vars['spin']) : false);
if(isset($vars['sync']))
$this->syncWithAudio = is_bool($vars['sync']) ? $vars['sync'] : (is_string($vars['sync']) ? boolval($vars['sync']) : false);
if(isset($vars['grdanm']))
$this->gradientAnimate = is_bool($vars['grdanm']) ? $vars['grdanm'] : (is_string($vars['grdanm']) ? boolval($vars['grdanm']) : false);
if(isset($vars['sldspd'])) {
$this->slideSpeed = is_int($vars['sldspd']) || is_float($vars['sldspd']) ? $vars['sldspd'] : (is_string($vars['sldspd']) ? floatval($vars['sldspd']) : 1);
if(!$quiet && ($this->slideSpeed < self::SLIDE_MIN || $this->slideSpeed > self::SLIDE_MAX))
throw new PageEffectException(sprintf('Slide speed may not be less than %d or more than %d', self::SLIDE_MIN, self::SLIDE_MAX));
}
if(isset($vars['spnspd'])) {
$this->spinSpeed = is_int($vars['spnspd']) || is_float($vars['spnspd']) ? $vars['spnspd'] : (is_string($vars['spnspd']) ? floatval($vars['spnspd']) : 1);
if(!$quiet && ($this->spinSpeed < self::SPIN_MIN || $this->spinSpeed > self::SPIN_MAX))
throw new PageEffectException(sprintf('Spin speed may not be less than %d or more than %d', self::SPIN_MIN, self::SPIN_MAX));
}
if(isset($vars['slddir']) && is_string($vars['slddir']) && array_key_exists($vars['slddir'], self::SLIDE_DIRS))
$this->slideDirection = $vars['slddir'];
if(isset($vars['spndir']) && is_string($vars['spndir']) && array_key_exists($vars['spndir'], self::SPIN_DIRS))
$this->spinDirection = $vars['spndir'];
if(isset($vars['col']))
$this->colour = Colour::create($vars['col']);
try {
if(isset($vars['grad']))
$this->gradient = Gradient::fromArray($vars['grad']);
} catch(Exception $ex) {
if(!$quiet)
throw $ex;
}
if(isset($vars['attach']) && is_string($vars['attach']) && array_key_exists($vars['attach'], self::ATTACH_OPTS))
$this->attachment = $vars['attach'];
if(isset($vars['pos']) && is_string($vars['pos']) && array_key_exists($vars['pos'], self::POSITION_OPTS))
$this->position = $vars['pos'];
if(isset($vars['rep']) && is_string($vars['rep']) && array_key_exists($vars['rep'], self::REPEAT_OPTS))
$this->repeat = $vars['rep'];
if(isset($vars['size']) && is_string($vars['size']) && array_key_exists($vars['size'], self::SIZE_OPTS))
$this->size = $vars['size'];
}
public function getEffectParams(): array {
return [
'img' => empty($this->imageUpload) ? null : $this->imageUpload->getId(),
'sld' => $this->slide,
'sldspd' => $this->slideSpeed,
'slddir' => $this->slideDirection,
'spin' => $this->spin,
'spnspd' => $this->spinSpeed,
'spndir' => $this->spinDirection,
'attach' => $this->attachment,
'col' => empty($this->colour) ? 0 : $this->colour->getRaw(),
'pos' => $this->position,
'rep' => $this->repeat,
'size' => $this->size,
'sync' => $this->syncWithAudio,
'grad' => empty($this->gradient) ? null : $this->gradient->getArray(),
'grdanm' => $this->gradientAnimate,
];
}
public function applyEffect(PageBuilder $builder): void {
$head = $builder->getHead();
$body = $builder->getContainer();
$bgTarget = $body;
$styleText = '';
if(empty($_GET['preview']) && !$this->gradientAnimate) {
$bgTargetClasses = ['BackgroundImage'];
if($this->spin && $this->slide)
$bgTargetClasses[] = 'Cover';
$bgTarget = $body->appendChild(new HtmlTag('div', ['class' => implode(' ', $bgTargetClasses), 'id' => 'BackgroundImage']));
}
if(empty($_GET['preview']) && !empty($this->imageUpload)) {
if($this->slide) {
$imageSize = getimagesize($this->imageUpload->getPath());
$imageWidth = $imageSize[0];
$imageHeight = $imageSize[1];
$animSX = $this->slideDirection[1] === 'l' ? $imageWidth : 0;
$animSY = $this->slideDirection[0] === 't' ? $imageHeight : 0;
$animEX = $this->slideDirection[1] === 'r' ? $imageWidth : 0;
$animEY = $this->slideDirection[0] === 'b' ? $imageHeight : 0;
if($imageSize !== false) {
$slideAnim = '_' . bin2hex(random_bytes(8));
$styleText .= sprintf(
'@keyframes %s { 0%% { background-position: %dpx %dpx; } 100%% { background-position: %dpx %dpx; } } ',
$slideAnim, $animSX, $animSY, $animEX, $animEY
);
}
}
}
$styleBgColour = isset($this->colour)
? sprintf(' background-color: #%s;', $this->colour->getHex())
: ' background-color: #000;';
$animation = [];
$backgroundImage = [];
if($this->spin)
$animation[] = sprintf('SharedAnimation_Spin360 infinite linear %s %Fs', $this->spinDirection === 'cw' ? 'normal' : 'reverse', $this->spinSpeed);
if(isset($slideAnim))
$animation[] = sprintf('%s infinite linear %Fs', $slideAnim, $this->slideSpeed);
$syncWithAudio = empty($_GET['preview']) && $this->syncWithAudio;
if(!empty($this->imageUpload) && !$syncWithAudio)
$backgroundImage[] = sprintf('url(\'%s\')', $this->imageUpload->getUrl());
if($bgTarget !== $body) {
$styleText .= '#container {';
if(!empty($this->gradient))
$styleText .= 'background-image: ' . $this->gradient->getCSS() . ';';
$styleText .= $styleBgColour;
$styleText .= '}';
}
$styleText .= '#' . ($bgTarget->getAttribute('id')) . ' {';
if($bgTarget === $body) {
if(!empty($this->gradient))
$backgroundImage[] = $this->gradient->getCSS();
$styleText .= $styleBgColour;
}
if(!empty($backgroundImage))
$styleText .= sprintf(' background-image: %s;', implode(', ', $backgroundImage));
if(!empty($this->attachment))
$styleText .= sprintf(' background-attachment: %s;', $this->attachment);
if(!empty($this->position))
$styleText .= sprintf(' background-position: %s;', $this->position);
if(!empty($this->repeat))
$styleText .= sprintf(' background-repeat: %s;', $this->repeat);
if(!empty($this->size))
$styleText .= sprintf(' background-size: %s;', $this->size);
if(!empty($animation))
$styleText .= sprintf(' animation: %s;', implode(', ', $animation));
$styleText .= ' }';
$styleTag = new HtmlTag('style', ['type' => 'text/css']);
$styleTag->setTextContent($styleText);
$head->appendChild($styleTag);
if(!empty($this->imageUpload) && $syncWithAudio) {
$scriptText = 'window.addEventListener(\'DOMContentLoaded\', function() {';
$scriptText .= 'synchroniseBackgroundWithAudio(\'' . $this->imageUpload->getUrl() . '\');';
$scriptText .= '});';
$scriptTag = new HtmlTag('script', ['type' => 'text/javascript']);
$scriptTag->setTextContent($scriptText);
$head->appendChild($scriptTag);
}
}
}