palette/convert/
from_into_color_unclamped_mut.rs

1use core::{
2    marker::PhantomData,
3    ops::{Deref, DerefMut},
4};
5
6use crate::cast::{self, ArrayCast};
7
8use super::{FromColorMut, FromColorMutGuard, FromColorUnclamped, IntoColorUnclamped};
9
10/// Temporarily convert colors in place, without clamping.
11///
12/// It allows colors to be converted without using more additional memory than
13/// what is necessary for the conversion, itself. The conversion will however
14/// have to be reverted at some point, since the memory space is borrowed and
15/// has to be restored to its original format. This is enforced by a scope guard
16/// that does the opposite conversion when it's dropped.
17///
18/// See also [`IntoColorUnclampedMut`] and [`FromColorMut`].
19///
20/// ```
21/// use palette::{convert::FromColorUnclampedMut, ShiftHueAssign, Srgb, Hsv};
22///
23/// let mut rgb = [
24///     Srgb::new(1.0, 0.0, 0.0),
25///     Srgb::new(0.0, 1.0, 0.0),
26///     Srgb::new(0.0, 0.0, 1.0),
27/// ];
28///
29/// {
30///     let mut hsv = <[Hsv]>::from_color_unclamped_mut(&mut rgb);
31///
32///     // All of the colors in `rgb` have been converted to `Hsv`:
33///     assert_eq!(
34///         *hsv,
35///         [
36///             Hsv::new(0.0, 1.0, 1.0),
37///             Hsv::new(120.0, 1.0, 1.0),
38///             Hsv::new(240.0, 1.0, 1.0),
39///         ]
40///     );
41///
42///     hsv.shift_hue_assign(60.0);
43///
44/// } // The guard is dropped here and the colors are restored to `Srgb`.
45///
46/// // Notice how the colors in `rgb` have changed:
47/// assert_eq!(
48///     rgb,
49///     [
50///         Srgb::new(1.0, 1.0, 0.0),
51///         Srgb::new(0.0, 1.0, 1.0),
52///         Srgb::new(1.0, 0.0, 1.0),
53///     ]
54/// );
55/// ```
56///
57/// The scope guard, [`FromColorUnclampedMutGuard`], has a few extra methods
58/// that can make multiple conversion steps more efficient. One of those is
59/// [`FromColorUnclampedMutGuard::then_into_color_unclamped_mut`], which works
60/// like [`IntoColorUnclampedMut::into_color_unclamped_mut`], but does not add
61/// an extra step when restoring to the original color type. This example will
62/// convert `Srgb → Hsv → Hsl → Srgb` instead of `Srgb → Hsv → Hsl → Hsv →
63/// Srgb`:
64///
65/// ```
66/// use palette::{convert::FromColorUnclampedMut, ShiftHueAssign, LightenAssign, Srgb, Hsv, Hsl};
67///
68/// let mut rgb = [
69///     Srgb::new(1.0, 0.0, 0.0),
70///     Srgb::new(0.0, 1.0, 0.0),
71///     Srgb::new(0.0, 0.0, 1.0),
72/// ];
73///
74/// {
75///     let mut hsv = <[Hsv]>::from_color_unclamped_mut(&mut rgb);
76///     hsv.shift_hue_assign(60.0);
77///
78///     let mut hsl = hsv.then_into_color_unclamped_mut::<[Hsl]>();
79///     hsl.lighten_assign(0.5);
80///
81/// } // `then_into_color_unclamped_mut` makes the guard restore directly to `Srgb` here.
82///
83/// // Notice how the colors in `rgb` have changed:
84/// assert_eq!(
85///     rgb,
86///     [
87///         Srgb::new(1.0, 1.0, 0.5),
88///         Srgb::new(0.5, 1.0, 1.0),
89///         Srgb::new(1.0, 0.5, 1.0),
90///     ]
91/// );
92/// ```
93///
94/// # Note
95///
96/// The reused memory space could end up with unexpected values if the
97/// conversion panics or if the scope guard's `drop` function doesn't run. The
98/// default implementations of `FromColorUnclampedMut` uses [`ArrayCast`], which
99/// is only implemented for color types that can safely accept and recover from
100/// any value. Other color types will have to provide their own implementations
101/// that can handle this case.
102pub trait FromColorUnclampedMut<T>
103where
104    T: ?Sized + FromColorUnclampedMut<Self>,
105{
106    /// Temporarily convert from another color type in place, without clamping.
107    ///
108    /// This reuses the memory space, and the returned scope guard will restore
109    /// the converted colors to their original type when it's dropped.
110    #[must_use]
111    fn from_color_unclamped_mut(color: &mut T) -> FromColorUnclampedMutGuard<Self, T>;
112}
113
114impl<T, U> FromColorUnclampedMut<U> for T
115where
116    T: FromColorUnclamped<U> + ArrayCast + Clone,
117    U: FromColorUnclamped<T> + ArrayCast<Array = T::Array> + Clone,
118{
119    #[inline]
120    fn from_color_unclamped_mut(color: &mut U) -> FromColorUnclampedMutGuard<Self, U> {
121        let color_clone = color.clone();
122
123        let result: &mut Self = cast::from_array_mut(cast::into_array_mut(color));
124
125        *result = color_clone.into_color_unclamped();
126
127        FromColorUnclampedMutGuard {
128            current: Some(result),
129            original: PhantomData,
130        }
131    }
132}
133
134impl<T, U> FromColorUnclampedMut<[U]> for [T]
135where
136    T: FromColorUnclampedMut<U> + ArrayCast + ?Sized,
137    U: FromColorUnclampedMut<T> + ArrayCast<Array = T::Array> + ?Sized,
138{
139    #[inline]
140    fn from_color_unclamped_mut(colors: &mut [U]) -> FromColorUnclampedMutGuard<Self, [U]> {
141        for color in &mut *colors {
142            // Forgetting the guard leaves the colors in the converted state.
143            core::mem::forget(T::from_color_unclamped_mut(color));
144        }
145
146        FromColorUnclampedMutGuard {
147            current: Some(cast::from_array_slice_mut(cast::into_array_slice_mut(
148                colors,
149            ))),
150            original: PhantomData,
151        }
152    }
153}
154
155/// Temporarily convert colors in place. The `Into` counterpart to
156/// [`FromColorUnclampedMut`].
157///
158/// See [`FromColorUnclampedMut`] for more details and examples.
159///
160/// ```
161/// use palette::{convert::IntoColorUnclampedMut, ShiftHueAssign, Srgb, Hsv};
162///
163/// let mut rgb = [
164///     Srgb::new(1.0, 0.0, 0.0),
165///     Srgb::new(0.0, 1.0, 0.0),
166///     Srgb::new(0.0, 0.0, 1.0),
167/// ];
168///
169/// {
170///     let hsv: &mut [Hsv] = &mut rgb.into_color_unclamped_mut(); // The guard is coerced into a slice.
171///
172///     // All of the colors in `rgb` have been converted to `Hsv`:
173///     assert_eq!(
174///         hsv,
175///         [
176///             Hsv::new(0.0, 1.0, 1.0),
177///             Hsv::new(120.0, 1.0, 1.0),
178///             Hsv::new(240.0, 1.0, 1.0),
179///         ]
180///     );
181///
182///     hsv.shift_hue_assign(60.0);
183///
184/// } // The guard is dropped here and the colors are restored to `Srgb`.
185///
186/// // Notice how the colors in `rgb` have changed:
187/// assert_eq!(
188///     rgb,
189///     [
190///         Srgb::new(1.0, 1.0, 0.0),
191///         Srgb::new(0.0, 1.0, 1.0),
192///         Srgb::new(1.0, 0.0, 1.0),
193///     ]
194/// );
195/// ```
196pub trait IntoColorUnclampedMut<T>: FromColorUnclampedMut<T>
197where
198    T: ?Sized + FromColorUnclampedMut<Self>,
199{
200    /// Temporarily convert to another color type in place, without clamping.
201    ///
202    /// This reuses the memory space, and the returned scope guard will restore
203    /// the converted colors to their original type when it's dropped.
204    #[allow(clippy::wrong_self_convention)]
205    #[must_use]
206    fn into_color_unclamped_mut(&mut self) -> FromColorUnclampedMutGuard<T, Self>;
207}
208
209impl<T, U> IntoColorUnclampedMut<T> for U
210where
211    T: FromColorUnclampedMut<U> + ?Sized,
212    U: FromColorUnclampedMut<T> + ?Sized,
213{
214    #[inline]
215    fn into_color_unclamped_mut(&mut self) -> FromColorUnclampedMutGuard<T, Self> {
216        T::from_color_unclamped_mut(self)
217    }
218}
219
220/// A scope guard that restores the guarded colors to their original type,
221/// without clamping, when dropped.
222#[repr(transparent)]
223pub struct FromColorUnclampedMutGuard<'a, T, U>
224where
225    T: FromColorUnclampedMut<U> + ?Sized,
226    U: FromColorUnclampedMut<T> + ?Sized,
227{
228    // `Option` lets us move out without triggering `Drop`.
229    pub(super) current: Option<&'a mut T>,
230    pub(super) original: PhantomData<&'a mut U>,
231}
232
233impl<'a, T, U> FromColorUnclampedMutGuard<'a, T, U>
234where
235    T: FromColorUnclampedMut<U> + ?Sized,
236    U: FromColorUnclampedMut<T> + ?Sized,
237{
238    /// Convert the colors to another type and replace this guard.
239    ///
240    /// The colors will not be converted back to the current color type before
241    /// being restored, as opposed to when `into_color_mut` is called. Instead,
242    /// they are restored directly to their original type.
243    #[must_use]
244    #[inline]
245    pub fn then_into_color_mut<C>(mut self) -> FromColorMutGuard<'a, C, U>
246    where
247        T: FromColorMut<C>,
248        C: FromColorMut<U> + FromColorMut<T> + ?Sized,
249        U: FromColorMut<C>,
250    {
251        FromColorMutGuard {
252            current: self
253                .current
254                .take()
255                .map(C::from_color_mut)
256                .and_then(|mut guard| guard.current.take()),
257            original: PhantomData,
258        }
259    }
260
261    /// Convert the colors to another type, without clamping, and replace this
262    /// guard.
263    ///
264    /// The colors will not be converted back to the current color type before
265    /// being restored, as opposed to when `into_color_unclamped_mut` is called.
266    /// Instead, they are restored directly to their original type.
267    #[must_use]
268    #[inline]
269    pub fn then_into_color_unclamped_mut<C>(mut self) -> FromColorUnclampedMutGuard<'a, C, U>
270    where
271        T: FromColorUnclampedMut<C>,
272        C: FromColorUnclampedMut<U> + FromColorUnclampedMut<T> + ?Sized,
273        U: FromColorUnclampedMut<C>,
274    {
275        FromColorUnclampedMutGuard {
276            current: self
277                .current
278                .take()
279                .map(C::from_color_unclamped_mut)
280                .and_then(|mut guard| guard.current.take()),
281            original: PhantomData,
282        }
283    }
284
285    /// Replace this guard with a guard that clamps the colors after restoring.
286    #[must_use]
287    #[inline]
288    pub fn into_clamped_guard(mut self) -> FromColorMutGuard<'a, T, U>
289    where
290        T: FromColorMut<U>,
291        U: FromColorMut<T>,
292    {
293        FromColorMutGuard {
294            current: self.current.take(),
295            original: PhantomData,
296        }
297    }
298
299    /// Immediately restore the colors to their original type.
300    ///
301    /// This happens automatically when the guard is dropped, but there may be
302    /// situations where it's better or more convenient to call `restore`
303    /// directly.
304    #[inline]
305    pub fn restore(mut self) -> &'a mut U {
306        let restored = self
307            .current
308            .take()
309            .map(U::from_color_unclamped_mut)
310            .and_then(|mut guard| guard.current.take());
311
312        if let Some(restored) = restored {
313            restored
314        } else {
315            unreachable!()
316        }
317    }
318}
319
320impl<'a, T, U> Deref for FromColorUnclampedMutGuard<'a, T, U>
321where
322    T: FromColorUnclampedMut<U> + ?Sized,
323    U: FromColorUnclampedMut<T> + ?Sized,
324{
325    type Target = T;
326
327    #[inline]
328    fn deref(&self) -> &Self::Target {
329        if let Some(current) = self.current.as_ref() {
330            current
331        } else {
332            unreachable!()
333        }
334    }
335}
336
337impl<'a, T, U> DerefMut for FromColorUnclampedMutGuard<'a, T, U>
338where
339    T: FromColorUnclampedMut<U> + ?Sized,
340    U: FromColorUnclampedMut<T> + ?Sized,
341{
342    #[inline]
343    fn deref_mut(&mut self) -> &mut Self::Target {
344        if let Some(current) = self.current.as_mut() {
345            current
346        } else {
347            unreachable!()
348        }
349    }
350}
351
352impl<'a, T, U> Drop for FromColorUnclampedMutGuard<'a, T, U>
353where
354    T: FromColorUnclampedMut<U> + ?Sized,
355    U: FromColorUnclampedMut<T> + ?Sized,
356{
357    #[inline]
358    fn drop(&mut self) {
359        // Forgetting the guard leaves the colors in the converted state.
360        core::mem::forget(self.current.take().map(U::from_color_unclamped_mut));
361    }
362}