Passed
Push — master ( 1a3471...4a3469 )
by Kanstantsin
04:57
created

Watermark::getMarkFilepath()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 0
dl 0
loc 13
ccs 0
cts 12
cp 0
crap 20
rs 9.2
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace tkanstantsin\fileupload\formatter\adapter;
5
6
use Imagine\Image\BoxInterface;
7
use Imagine\Image\ImageInterface;
8
use Imagine\Image\ImagineInterface;
9
use Imagine\Image\Point;
10
use tkanstantsin\fileupload\config\InvalidConfigException;
11
use tkanstantsin\fileupload\formatter\Image;
12
use tkanstantsin\fileupload\formatter\ImagineFactory;
13
use tkanstantsin\fileupload\model\IFile;
14
15
/**
16
 * Class Watermark
17
 */
18
class Watermark extends AbstractImageAdapter
19
{
20
    public const POSITION_CENTER_CENTER = 'center_center';
21
    public const POSITION_CENTER_LEFT = 'center_left';
22
    public const POSITION_CENTER_RIGHT = 'center_right';
23
    public const POSITION_TOP_CENTER = 'top_center';
24
    public const POSITION_TOP_LEFT = 'top_left';
25
    public const POSITION_TOP_RIGHT = 'top_right';
26
    public const POSITION_BOTTOM_CENTER = 'bottom_center';
27
    public const POSITION_BOTTOM_LEFT = 'bottom_left';
28
    public const POSITION_BOTTOM_RIGHT = 'bottom_right';
29
30
    public const SIZE_COVER = 'cover';
31
    public const SIZE_CONTAIN = 'contain';
32
    public const SIZE_STRETCH = 'stretch';
33
34
    /**
35
     * Absolute path to image file
36
     * @var callable|string|null
37
     */
38
    public $markFilepath;
39
    /**
40
     * Water mark image binary
41
     * @var string|resource|null
42
     */
43
    public $markContent;
44
45
    /**
46
     * Opacity for watermark image
47
     * @var int
48
     */
49
    public $opacity = 100;
50
    /**
51
     * TODO: implement position.
52
     * @var string
53
     */
54
    public $position = self::POSITION_CENTER_CENTER;
55
    /**
56
     * TODO: implement size.
57
     * @var string
58
     */
59
    public $size = self::SIZE_CONTAIN;
60
    /**
61
     * Watermark size relative to image size
62
     * @var float
63
     */
64
    public $scale = 0.9;
65
66
    /**
67
     * @inheritdoc
68
     * @throws \tkanstantsin\fileupload\config\InvalidConfigException
69
     * @throws \ErrorException
70
     */
71
    public function init(): void
72
    {
73
        parent::init();
74
75
        if ($this->scale > 1 || $this->scale <= 0) {
76
            throw new InvalidConfigException('Scale must be greater than 0 and lower or equal than 1.');
77
        }
78
        if ($this->markFilepath === null && $this->markContent === null) {
79
            throw new InvalidConfigException('Either watermarkPath or watermarkImage must be defined.');
80
        }
81
        if ($this->getMarkFilepath() !== null && !file_exists($this->getMarkFilepath())) {
82
            throw new InvalidConfigException(sprintf('Watermark by path `%s` not found.', $this->getMarkFilepath()));
83
        }
84
    }
85
86
    /**
87
     * Applies filters or something to content and return it
88
     *
89
     * @param IFile $file
90
     * @param       $content
91
     *
92
     * @return mixed
93
     * @throws \Imagine\Exception\OutOfBoundsException
94
     * @throws \UnexpectedValueException
95
     * @throws \Imagine\Exception\InvalidArgumentException
96
     * @throws \Imagine\Exception\RuntimeException
97
     * @throws \ImageOptimizer\Exception\Exception
98
     * @throws \ErrorException
99
     */
100
    public function exec(IFile $file, $content)
101
    {
102
        $image = \is_resource($content)
103
            ? $this->imagine->read($content)
104
            : $this->imagine->load($content);
105
        $imageSize = $image->getSize();
106
107
        $watermark = $this->getWatermark($this->imagine);
108
        $watermarkSize = $watermark->getSize();
109
110
        // NOTE: only for position: center
111
        $watermarkSize = $this->getWatermarkBox($imageSize, $watermarkSize);
112
113
        /* @see http://urmaul.com/blog/imagick-filters-comparison */
114
        $filter = $this->driver === ImagineFactory::IMAGICK ? ImageInterface::FILTER_SINC : ImageInterface::FILTER_UNDEFINED;
115
        $watermark = $watermark->resize($watermarkSize, $filter);
116
        $watermarkSize = $watermark->getSize();
117
118
        $image = $image->paste($watermark, $this->getPositionPoint($imageSize, $watermarkSize));
119
120
        return $image->get($file->getExtension() ?? Image::DEFAULT_EXTENSION);
121
    }
122
123
    /**
124
     * Get watermark image
125
     * @param ImagineInterface $imagine
126
     * @return ImageInterface
127
     * @throws \Imagine\Exception\RuntimeException
128
     * @throws \UnexpectedValueException
129
     * @throws \ErrorException
130
     */
131
    private function getWatermark(ImagineInterface $imagine): ImageInterface
132
    {
133
        $resource = null;
134
        if ($this->getMarkFilepath() !== null) {
135
            $resource = fopen($this->getMarkFilepath(), 'rb');
136
        } elseif (\is_string($this->markContent)) {
137
            $resource = 'a'; // TODO: create resource.
138
        } elseif (\is_resource($this->markContent)) {
139
            $resource = $this->markContent;
140
        }
141
142
        if ($resource === null) {
143
            throw new \UnexpectedValueException('Water mark image invalid format');
144
        }
145
146
        return $imagine->read($resource);
0 ignored issues
show
Bug introduced by
It seems like $resource can also be of type false and string; however, parameter $resource of Imagine\Image\ImagineInterface::read() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

146
        return $imagine->read(/** @scrutinizer ignore-type */ $resource);
Loading history...
147
    }
148
149
    /**
150
     * @param BoxInterface $imageSize
151
     * @param BoxInterface $watermarkSize
152
     *
153
     * @return BoxInterface
154
     */
155
    private function getWatermarkBox(BoxInterface $imageSize, BoxInterface $watermarkSize): BoxInterface
156
    {
157
        // ensure that watermark smaller than image
158
        $watermarkSize = $watermarkSize->widen((int) ($imageSize->getWidth() * $this->scale));
159
        $watermarkSize = $watermarkSize->heighten(min($watermarkSize->getHeight(), $imageSize->getHeight()));
160
161
        return $watermarkSize;
162
    }
163
164
    /**
165
     * @param BoxInterface $imageSize
166
     * @param BoxInterface $watermarkSize
167
     *
168
     * @return Point
169
     * @throws \Imagine\Exception\InvalidArgumentException
170
     */
171
    private function getPositionPoint(BoxInterface $imageSize, BoxInterface $watermarkSize): Point
172
    {
173
        return new Point(
174
            (int) (($imageSize->getWidth() - $watermarkSize->getWidth()) / 2),
175
            (int) (($imageSize->getHeight() - $watermarkSize->getHeight()) / 2)
176
        );
177
    }
178
179
    /**
180
     * @return null|string
181
     * @throws \ErrorException
182
     */
183
    private function getMarkFilepath(): ?string
184
    {
185
        if ($this->markFilepath === null) {
186
            return null;
187
        }
188
        if (\is_string($this->markFilepath)) {
189
            return $this->markFilepath;
190
        }
191
        if (\is_callable($this->markFilepath)) {
192
            return \call_user_func($this->markFilepath, $this);
193
        }
194
195
        throw new \ErrorException('Mark file path has invalid format. Callable, string or null expected.');
196
    }
197
}