|
1
|
|
|
<?php |
|
2
|
|
|
namespace nstdio\svg\filter; |
|
3
|
|
|
|
|
4
|
|
|
use nstdio\svg\container\ContainerInterface; |
|
5
|
|
|
use nstdio\svg\traits\ElementTrait; |
|
6
|
|
|
use nstdio\svg\util\KeyValueWriter; |
|
7
|
|
|
|
|
8
|
|
|
/** |
|
9
|
|
|
* Class ComponentTransfer |
|
10
|
|
|
* This filter primitive performs component-wise remapping of data as follows: |
|
11
|
|
|
* |
|
12
|
|
|
* R' = feFuncR( R ) |
|
13
|
|
|
* G' = feFuncG( G ) |
|
14
|
|
|
* B' = feFuncB( B ) |
|
15
|
|
|
* A' = feFuncA( A ) |
|
16
|
|
|
* for every pixel. It allows operations like brightness adjustment, contrast adjustment, color balance or |
|
17
|
|
|
* thresholding. |
|
18
|
|
|
* |
|
19
|
|
|
* The calculations are performed on non-premultiplied color values. If the input graphics consists of premultiplied |
|
20
|
|
|
* color values, those values are automatically converted into non-premultiplied color values for this operation. (Note |
|
21
|
|
|
* that the undoing and redoing of the premultiplication can be avoided if feFuncA is the identity transform and all |
|
22
|
|
|
* alpha values on the source graphic are set to 1.) |
|
23
|
|
|
* |
|
24
|
|
|
* @link https://www.w3.org/TR/SVG/filters.html#feComponentTransferElement |
|
25
|
|
|
* @property string type = "identity | table | discrete | linear | gamma" Indicates the type of component |
|
26
|
|
|
* transfer function. The type of function determines the applicability of the other attributes. |
|
27
|
|
|
* @property string tableValues = "(list of <number>s)" When type="table", the list of <number>s v0,v1,...vn, separated |
|
28
|
|
|
* by white space and/or a comma, which define the lookup table. An empty list results in an identity |
|
29
|
|
|
* transfer function. If the attribute is not specified, then the effect is as if an empty list were |
|
30
|
|
|
* provided. In the following, C is the initial component (e.g., 'feFuncR'), C' is the remapped component; |
|
31
|
|
|
* both in the closed interval [0,1]. |
|
32
|
|
|
* @property float slope = "<number>" When type="linear", the slope of the linear function. If the attribute is |
|
33
|
|
|
* not specified, then the effect is as if a value of 1 were specified. |
|
34
|
|
|
* @property float intercept = "<number>" When type="linear", the intercept of the linear function. If the attribute |
|
35
|
|
|
* is not specified, then the effect is as if a value of 0 were specified. |
|
36
|
|
|
* @property float amplitude = "<number>" When type="gamma", the amplitude of the gamma function. If the attribute |
|
37
|
|
|
* is not specified, then the effect is as if a value of 1 were specified. |
|
38
|
|
|
* @property float exponent = "<number>" When type="gamma", the exponent of the gamma function. If the attribute is |
|
39
|
|
|
* not specified, then the effect is as if a value of 1 were specified. |
|
40
|
|
|
* @property float offset = "<number>" When type="gamma", the offset of the gamma function. If the attribute is |
|
41
|
|
|
* not specified, then the effect is as if a value of 0 were specified. |
|
42
|
|
|
* @package nstdio\svg\filter |
|
43
|
|
|
* @author Edgar Asatryan <[email protected]> |
|
44
|
|
|
*/ |
|
45
|
|
|
class ComponentTransfer extends BaseFilter implements ContainerInterface |
|
46
|
|
|
{ |
|
47
|
|
|
use ElementTrait; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* @inheritdoc |
|
51
|
|
|
*/ |
|
52
|
12 |
|
public function getName() |
|
53
|
|
|
{ |
|
54
|
12 |
|
return "feComponentTransfer"; |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
1 |
|
public static function identity(ContainerInterface $container, $filterId = null) |
|
58
|
|
|
{ |
|
59
|
1 |
|
return self::createAndAppend($container, [ |
|
60
|
1 |
|
'id' => $filterId, |
|
61
|
1 |
|
'type' => __FUNCTION__, |
|
62
|
1 |
|
], true); |
|
63
|
|
|
|
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* @param ContainerInterface $container |
|
68
|
|
|
* @param $config |
|
69
|
|
|
* |
|
70
|
|
|
* @param bool $alpha |
|
71
|
|
|
* |
|
72
|
|
|
* @return Filter |
|
73
|
|
|
*/ |
|
74
|
11 |
|
private static function createAndAppend(ContainerInterface $container, $config, $alpha = false) |
|
75
|
|
|
{ |
|
76
|
11 |
|
$id = $config['id']; |
|
77
|
11 |
|
$type = $config['type']; |
|
78
|
11 |
|
unset($config['id'], $config['type']); |
|
79
|
|
|
|
|
80
|
11 |
|
$filter = self::defaultFilter($container, $id); |
|
81
|
11 |
|
$componentTransfer = new ComponentTransfer($filter); |
|
82
|
11 |
|
$componentTransfer->id = null; |
|
83
|
|
|
|
|
84
|
11 |
|
self::funcFactory($componentTransfer, $type, $config, $alpha); |
|
85
|
|
|
|
|
86
|
11 |
|
return $filter; |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
11 |
|
private static function funcFactory(ContainerInterface $container, $type, array $config, $alpha = false) |
|
90
|
|
|
{ |
|
91
|
|
|
/** @var Func[] $ret */ |
|
92
|
11 |
|
$ret = [new FuncR($container, $type), new FuncG($container, $type), new FuncB($container, $type)]; |
|
93
|
|
|
|
|
94
|
11 |
|
if ($alpha === true) { |
|
95
|
1 |
|
$ret[] = new FuncA($container, $type); |
|
96
|
1 |
|
} |
|
97
|
11 |
|
foreach ($config as $key => $value) { |
|
98
|
10 |
|
KeyValueWriter::apply($ret[$key]->getElement(), $value); |
|
|
|
|
|
|
99
|
11 |
|
} |
|
100
|
|
|
|
|
101
|
11 |
|
return $ret; |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
5 |
|
public static function table(ContainerInterface $container, array $table, $filterId = null) |
|
105
|
|
|
{ |
|
106
|
5 |
|
$table = self::padAttribute($table); |
|
107
|
5 |
|
$config = ['id' => $filterId, 'type' => __FUNCTION__]; |
|
108
|
5 |
|
for ($i = 0; $i < 3; $i++) { |
|
109
|
5 |
|
$config[$i]['tableValues'] = implode(' ', $table[$i]); |
|
110
|
5 |
|
} |
|
111
|
|
|
|
|
112
|
5 |
|
return self::createAndAppend($container, $config); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
7 |
|
private static function padAttribute(array $attributeValue, $max = 3) |
|
116
|
|
|
{ |
|
117
|
7 |
|
$val = array_values(array_slice($attributeValue, 0, $max)); |
|
118
|
7 |
|
$cnt = count($val); |
|
119
|
7 |
|
if ($cnt < $max) { |
|
120
|
5 |
|
$repeatValue = $val[$cnt - 1]; |
|
121
|
5 |
|
for ($i = $cnt - 1; $i < $max; $i++) { |
|
122
|
5 |
|
$val[$i] = $repeatValue; |
|
123
|
5 |
|
} |
|
124
|
5 |
|
} |
|
125
|
|
|
|
|
126
|
7 |
|
return $val; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
2 |
View Code Duplication |
public static function linear(ContainerInterface $container, $slope, $intercept, $filterId = null) |
|
|
|
|
|
|
130
|
|
|
{ |
|
131
|
2 |
|
$config = ['id' => $filterId, 'type' => __FUNCTION__]; |
|
132
|
|
|
|
|
133
|
2 |
|
self::buildConfig($config, [ |
|
134
|
2 |
|
'slope' => $slope, |
|
135
|
2 |
|
'intercept' => $intercept, |
|
136
|
2 |
|
]); |
|
137
|
|
|
|
|
138
|
2 |
|
return self::createAndAppend($container, $config); |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
5 |
|
private static function buildConfig(array &$config, array $attributes) |
|
142
|
|
|
{ |
|
143
|
5 |
|
foreach ($attributes as $key => $value) { |
|
144
|
5 |
|
if (is_array($value)) { |
|
145
|
2 |
|
$aligned = self::padAttribute($value); |
|
146
|
2 |
|
for ($i = 0; $i < 3; $i++) { |
|
147
|
2 |
|
$config[$i][$key] = $aligned[$i]; |
|
148
|
2 |
|
} |
|
149
|
2 |
|
} else { |
|
150
|
4 |
|
for ($i = 0; $i < 3; $i++) { |
|
151
|
4 |
|
$config[$i][$key] = $value; |
|
152
|
4 |
|
} |
|
153
|
|
|
} |
|
154
|
5 |
|
} |
|
155
|
5 |
|
} |
|
156
|
|
|
|
|
157
|
1 |
View Code Duplication |
public static function gamma(ContainerInterface $container, $amplitude, $exponent, $offset = 0, $filterId = null) |
|
|
|
|
|
|
158
|
|
|
{ |
|
159
|
1 |
|
$config = ['id' => $filterId, 'type' => __FUNCTION__]; |
|
160
|
1 |
|
self::buildConfig($config, [ |
|
161
|
1 |
|
'amplitude' => $amplitude, |
|
162
|
1 |
|
'exponent' => $exponent, |
|
163
|
1 |
|
'offset' => $offset, |
|
164
|
1 |
|
]); |
|
165
|
|
|
|
|
166
|
1 |
|
return self::createAndAppend($container, $config); |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
1 |
View Code Duplication |
public static function brightness(ContainerInterface $container, $amount, $filterId = null) |
|
|
|
|
|
|
170
|
|
|
{ |
|
171
|
1 |
|
$config = ['id' => $filterId, 'type' => 'linear']; |
|
172
|
1 |
|
self::buildConfig($config, [ |
|
173
|
1 |
|
'slope' => $amount, |
|
174
|
1 |
|
]); |
|
175
|
|
|
|
|
176
|
1 |
|
return self::createAndAppend($container, $config); |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
1 |
View Code Duplication |
public static function contrast(ContainerInterface $container, $amount, $filterId = null) |
|
|
|
|
|
|
180
|
|
|
{ |
|
181
|
1 |
|
$config = ['id' => $filterId, 'type' => 'linear']; |
|
182
|
|
|
|
|
183
|
1 |
|
$intercept = 0.5 - (0.5 * $amount); |
|
184
|
1 |
|
self::buildConfig($config, [ |
|
185
|
1 |
|
'slope' => $amount, |
|
186
|
1 |
|
'intercept' => $intercept, |
|
187
|
1 |
|
]); |
|
188
|
|
|
|
|
189
|
1 |
|
return self::createAndAppend($container, $config); |
|
190
|
|
|
} |
|
191
|
|
|
} |
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.