1use super::{Hsla, Hsva, PreMulRgba, Rgba, clamp_normal};
2use zng_layout::unit::Factor;
3
4use pastey::*;
5
6pub type RenderMixBlendMode = zng_view_api::MixBlendMode;
8
9macro_rules! impl_mix {
10 (
11 separable {$(
12 $(#[$meta:meta])*
13 $Mode:ident => |$fg:ident, $bg:ident, $ca0:ident, $ca1:ident| $mix:expr,
14 )+}
15
16 non_separable {$(
17 $(#[$ns_meta:meta])*
18 $NsMode:ident => |[$fgh:ident, $fgs:ident, $fgl:ident], [$bgh:ident, $bgs:ident, $bgl:ident]| $ns_mix:expr,
19 )+}
20 ) => {
21 #[repr(u8)]
23 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24 pub enum MixBlendMode {
25 $(
26 $(#[$meta])*
27 $Mode,
28 )+
29 $(
30 $(#[$ns_meta])*
31 $NsMode,
32 )+
33 }
34
35 impl From<MixBlendMode> for RenderMixBlendMode {
36 fn from(mode: MixBlendMode) -> Self {
37 match mode {
38 $(MixBlendMode::$Mode => RenderMixBlendMode::$Mode,)+
39 $(MixBlendMode::$NsMode => RenderMixBlendMode::$NsMode,)+
40 }
41 }
42 }
43
44 pub trait MixAdjust {
46 fn mix(self, mode: MixBlendMode, background: Self) -> Self where Self:Sized {
48 match mode {
49 $(MixBlendMode::$Mode => paste!(self.[<mix_ $Mode:lower>](background)),)+
50 $(MixBlendMode::$NsMode => paste!(self.[<mix_ $NsMode:lower>](background)),)+
51 }
52 }
53
54 $(
55 paste! {
56 #[doc = "MixAdjust `background` over `self` using the [`MixBlendMode::" $Mode "`]."]
57 $(#[$meta])*
59 fn [<mix_ $Mode:lower>](self, background: Self) -> Self;
60 }
61 )+
62
63 $(
64 paste! {
65 #[doc = "MixAdjust `self` over `background` using the [`MixBlendMode::`" $NsMode "`]."]
66 $(#[$ns_meta])*
68 fn [<mix_ $NsMode:lower>](self, background: Self) -> Self;
71 }
72 )+
73
74 fn lighten<A: Into<Factor>>(self, amount: A) -> Self;
87
88 fn darken<A: Into<Factor>>(self, amount: A) -> Self;
100
101 fn desaturate<A: Into<Factor>>(self, amount: A) -> Self;
113
114 fn with_lightness<L: Into<Factor>>(self, lightness: L) -> Self;
116 }
117
118 impl PreMulRgba {
119 $(
120 paste! {
121 fn [<mix_ $Mode:lower _impl>](self, background: PreMulRgba) -> PreMulRgba {
122 PreMulRgba {
123 red: {
124 let $fg = self.red;
125 let $bg = background.red;
126 let $ca0 = self.alpha;
127 let $ca1 = background.alpha;
128 $mix
129 },
130 green: {
131 let $fg = self.green;
132 let $bg = background.green;
133 let $ca0 = self.alpha;
134 let $ca1 = background.alpha;
135 $mix
136 },
137 blue: {
138 let $fg = self.blue;
139 let $bg = background.blue;
140 let $ca0 = self.alpha;
141 let $ca1 = background.alpha;
142 $mix
143 },
144 alpha: {
145 let fga = self.alpha;
146 let bga = background.alpha;
147 (fga + bga - fga * bga).max(0.0).min(1.0)
148 },
149 }
150 }
151 }
152 )+
153 }
154
155 impl Hsla {
156 $(
157 paste! {
158 fn [<mix_ $NsMode:lower _impl>](self, background: Hsla) -> Hsla {
159 let $fgh = self.hue;
160 let $fgs = self.saturation;
161 let $fgl = self.lightness;
162
163 let $bgh = background.hue;
164 let $bgs = background.saturation;
165 let $bgl = background.lightness;
166
167 let [h, s, l] = { $ns_mix };
168
169 Hsla {
170 hue: h,
171 saturation: s,
172 lightness: l,
173 alpha: {
174 let fga = self.alpha;
175 let bga = background.alpha;
176 (fga + bga - fga * bga).max(0.0).min(1.0)
177 }
178 }
179 }
180 }
181 )+
182 }
183
184 impl MixAdjust for PreMulRgba {
185 $(
186 paste! {
187 fn [<mix_ $Mode:lower>](self, background: Self) -> Self {
188 self.[<mix_ $Mode:lower _impl>](background)
189 }
190 }
191 )+
192 $(
193 paste! {
194 fn [<mix_ $NsMode:lower>](self, background: Self) -> Self {
195 Hsla::from(self).[<mix_ $NsMode:lower>](Hsla::from(background)).into()
196 }
197 }
198 )+
199
200 fn lighten<A: Into<Factor>>(self, amount: A) -> Self {
201 Hsla::from(self).lighten(amount.into()).into()
202 }
203
204 fn darken<A: Into<Factor>>(self, amount: A) -> Self {
205 Hsla::from(self).darken(amount.into()).into()
206 }
207
208 fn desaturate<A: Into<Factor>>(self, amount: A) -> Self {
209 Hsla::from(self).desaturate(amount).into()
210 }
211
212 fn with_lightness<L: Into<Factor>>(self, lightness: L) -> Self {
213 Hsla::from(self).with_lightness(lightness).into()
214 }
215 }
216
217 impl MixAdjust for Hsla {
218 $(
219 paste! {
220 fn [<mix_ $Mode:lower>](self, background: Self) -> Self {
221 PreMulRgba::from(self).[<mix_ $Mode:lower>](PreMulRgba::from(background)).into()
222 }
223 }
224 )+
225
226 $(
227 paste! {
228 fn [<mix_ $NsMode:lower>](self, background: Self) -> Self {
229 self.[<mix_ $NsMode:lower _impl>](background)
230 }
231 }
232 )+
233
234 fn lighten<A: Into<Factor>>(self, amount: A) -> Self {
235 let mut lighter = self;
236 lighter.lightness = clamp_normal(lighter.lightness + (lighter.lightness * amount.into().0));
237 lighter
238 }
239
240 fn darken<A: Into<Factor>>(self, amount: A) -> Self {
241 let mut darker = self;
242 darker.lightness = clamp_normal(darker.lightness - (darker.lightness * amount.into().0));
243 darker
244 }
245
246 fn desaturate<A: Into<Factor>>(self, amount: A) -> Self {
247 let mut d = self;
248 d.saturation = clamp_normal(d.saturation - (d.saturation * amount.into().0));
249 d
250 }
251
252 fn with_lightness<L: Into<Factor>>(mut self, lightness: L) -> Self {
253 self.set_lightness(lightness);
254 self
255 }
256 }
257
258 impl MixAdjust for Rgba {
259 $(
260 paste! {
261 fn [<mix_ $Mode:lower>](self, background: Self) -> Self {
262 PreMulRgba::from(self).[<mix_ $Mode:lower>](PreMulRgba::from(background)).into()
263 }
264 }
265 )+
266 $(
267 paste! {
268 fn [<mix_ $NsMode:lower>](self, background: Self) -> Self {
269 Hsla::from(self).[<mix_ $NsMode:lower>](Hsla::from(background)).into()
270 }
271 }
272 )+
273
274 fn lighten<A: Into<Factor>>(self, amount: A) -> Self {
275 Hsla::from(self).lighten(amount.into()).into()
276 }
277
278 fn darken<A: Into<Factor>>(self, amount: A) -> Self {
279 Hsla::from(self).darken(amount.into()).into()
280 }
281
282 fn desaturate<A: Into<Factor>>(self, amount: A) -> Self {
283 Hsla::from(self).desaturate(amount).into()
284 }
285
286 fn with_lightness<L: Into<Factor>>(self, lightness: L) -> Self {
287 Hsla::from(self).with_lightness(lightness).into()
288 }
289 }
290
291 impl MixAdjust for Hsva {
292 $(
293 paste! {
294 fn [<mix_ $Mode:lower>](self, background: Self) -> Self {
295 PreMulRgba::from(self).[<mix_ $Mode:lower>](PreMulRgba::from(background)).into()
296 }
297 }
298 )+
299 $(
300 paste! {
301 fn [<mix_ $NsMode:lower>](self, background: Self) -> Self {
302 Hsla::from(self).[<mix_ $NsMode:lower>](Hsla::from(background)).into()
303 }
304 }
305 )+
306
307 fn lighten<A: Into<Factor>>(self, amount: A) -> Self {
308 Hsla::from(self).lighten(amount.into()).into()
309 }
310
311 fn darken<A: Into<Factor>>(self, amount: A) -> Self {
312 Hsla::from(self).darken(amount.into()).into()
313 }
314
315 fn desaturate<A: Into<Factor>>(self, amount: A) -> Self {
316 Hsla::from(self).desaturate(amount).into()
317 }
318
319 fn with_lightness<L: Into<Factor>>(self, lightness: L) -> Self {
320 Hsla::from(self).with_lightness(lightness).into()
321 }
322 }
323 };
324}
325
326impl_mix! {
327 separable {
328 Normal => |fg, bg, fga, _bga| fg + bg * (1.0 - fga),
330
331 Multiply => |fg, bg, fga, bga| fg * bg + fg * (1.0 - bga) + bg * (1.0 - fga),
337
338 Screen => |fg, bg, _fga, _bga| fg + bg - fg * bg,
344
345 Overlay => |fg, bg, fga, bga| if bg * 2.0 <= bga {
353 2.0 * fg * bg + fg * (1.0 - bga) + bg * (1.0 - fga)
354 } else {
355 fg * (1.0 + bga) + bg * (1.0 + fga) - 2.0 * fg * bg - fga * bga
356 },
357
358 Darken => |fg, bg, fga, bga| (fg * bga).min(bg * fga) + fg * (1.0 - bga) + bg * (1.0 - fga),
362
363 Lighten => |fg, bg, fga, bga| (fg * bga).max(bg * fga) + fg * (1.0 - bga) + bg * (1.0 - fga),
367
368 ColorDodge => |fg, bg, fga, bga| if fg == fga {
370 fga * bga + fg * (1.0 - bga) + bg * (1.0 - fga)
371 } else {
372 fga * bga * 1.0_f32.min((bg / bga) * fga / (fga - fg)) + fg * (1.0 - bga) + bg * (1.0 - fga)
373 },
374
375 ColorBurn => |fg, bg, fga, bga| fga * bga * (1.0 - 1.0_f32.min((1.0 - bg / bga) * fga / fg)) + fg * (1.0 - bga) + bg * (1.0 - fga),
377
378 HardLight => |fg, bg, fga, bga| if fg * 2.0 <= fga {
382 2.0 * fg * bg + fg * (1.0 - bga) + bg * (1.0 - fga)
383 } else {
384 fg * (1.0 + bga) + bg * (1.0 + fga) - 2.0 * fg * bg - fga * bga
385 },
386
387 SoftLight => |fg, bg, fga, bga| {
391 let m = bg / bga;
392
393 if fg * 2.0 <= fga {
394 bg * (fga + (2.0 * fg - fga) * (1.0 - m)) + fg * (1.0 - bga) + bg * (1.0 - fga)
395 } else if bg * 4.0 <= bga {
396 let m2 = m * m;
397 let m3 = m2 * m;
398 bga * (2.0 * fg - fga) * (m3 * 16.0 - m2 * 12.0 - m * 3.0) + fg - fg * bga + bg
399 } else {
400 bga * (2.0 * fg - fga) * (m.sqrt() - m) + fg - fg * bga + bg
401 }
402 },
403
404 Difference => |fg, bg, fga, bga| fg + bg - 2.0 * (fg * bga).min(bg * fga),
408
409 Exclusion => |fg, bg, _fga, _bga| fg + bg - 2.0 * fg * bg,
413 }
414
415 non_separable {
416 Hue => |[fgh, _fgs, _fgl], [_bgh, bgs, bgl]| [fgh, bgs, bgl],
418
419 Saturation => |[_fgh, fgs, _fgl], [bgh, _bgs, bgl]| [bgh, fgs, bgl],
421
422 Color => |[fgh, fgs, _fgl], [_bgh, _bgs, bgl]| [fgh, fgs, bgl],
424
425 Luminosity => |[fgh, _fgs, _fbl], [bgh, bgs, _bgl]| [bgh, bgs, fgh],
427 }
428}
429#[expect(clippy::derivable_impls)] impl Default for MixBlendMode {
431 fn default() -> Self {
432 MixBlendMode::Normal
433 }
434}