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}