iced_graphics/
gradient.rs
1use crate::color;
5use crate::core::gradient::ColorStop;
6use crate::core::{self, Color, Point, Rectangle};
7
8use bytemuck::{Pod, Zeroable};
9use half::f16;
10use std::cmp::Ordering;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum Gradient {
17 Linear(Linear),
20}
21
22impl From<Linear> for Gradient {
23 fn from(gradient: Linear) -> Self {
24 Self::Linear(gradient)
25 }
26}
27
28impl Gradient {
29 pub fn pack(&self) -> Packed {
31 match self {
32 Gradient::Linear(linear) => linear.pack(),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq)]
39pub struct Linear {
40 pub start: Point,
42
43 pub end: Point,
45
46 pub stops: [Option<ColorStop>; 8],
48}
49
50impl Linear {
51 pub fn new(start: Point, end: Point) -> Self {
53 Self {
54 start,
55 end,
56 stops: [None; 8],
57 }
58 }
59
60 pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
66 if offset.is_finite() && (0.0..=1.0).contains(&offset) {
67 let (Ok(index) | Err(index)) =
68 self.stops.binary_search_by(|stop| match stop {
69 None => Ordering::Greater,
70 Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
71 });
72
73 if index < 8 {
74 self.stops[index] = Some(ColorStop { offset, color });
75 }
76 } else {
77 log::warn!("Gradient: ColorStop must be within 0.0..=1.0 range.");
78 };
79
80 self
81 }
82
83 pub fn add_stops(
87 mut self,
88 stops: impl IntoIterator<Item = ColorStop>,
89 ) -> Self {
90 for stop in stops {
91 self = self.add_stop(stop.offset, stop.color);
92 }
93
94 self
95 }
96
97 pub fn pack(&self) -> Packed {
99 let mut colors = [[0u32; 2]; 8];
100 let mut offsets = [f16::from(0u8); 8];
101
102 for (index, stop) in self.stops.iter().enumerate() {
103 let [r, g, b, a] =
104 color::pack(stop.map_or(Color::default(), |s| s.color))
105 .components();
106
107 colors[index] = [
108 pack_f16s([f16::from_f32(r), f16::from_f32(g)]),
109 pack_f16s([f16::from_f32(b), f16::from_f32(a)]),
110 ];
111
112 offsets[index] =
113 stop.map_or(f16::from_f32(2.0), |s| f16::from_f32(s.offset));
114 }
115
116 let offsets = [
117 pack_f16s([offsets[0], offsets[1]]),
118 pack_f16s([offsets[2], offsets[3]]),
119 pack_f16s([offsets[4], offsets[5]]),
120 pack_f16s([offsets[6], offsets[7]]),
121 ];
122
123 let direction = [self.start.x, self.start.y, self.end.x, self.end.y];
124
125 Packed {
126 colors,
127 offsets,
128 direction,
129 }
130 }
131}
132
133#[derive(Debug, Copy, Clone, PartialEq, Zeroable, Pod)]
135#[repr(C)]
136pub struct Packed {
137 colors: [[u32; 2]; 8],
139 offsets: [u32; 4],
141 direction: [f32; 4],
142}
143
144pub fn pack(gradient: &core::Gradient, bounds: Rectangle) -> Packed {
146 match gradient {
147 core::Gradient::Linear(linear) => {
148 let mut colors = [[0u32; 2]; 8];
149 let mut offsets = [f16::from(0u8); 8];
150
151 for (index, stop) in linear.stops.iter().enumerate() {
152 let [r, g, b, a] =
153 color::pack(stop.map_or(Color::default(), |s| s.color))
154 .components();
155
156 colors[index] = [
157 pack_f16s([f16::from_f32(r), f16::from_f32(g)]),
158 pack_f16s([f16::from_f32(b), f16::from_f32(a)]),
159 ];
160
161 offsets[index] = stop
162 .map_or(f16::from_f32(2.0), |s| f16::from_f32(s.offset));
163 }
164
165 let offsets = [
166 pack_f16s([offsets[0], offsets[1]]),
167 pack_f16s([offsets[2], offsets[3]]),
168 pack_f16s([offsets[4], offsets[5]]),
169 pack_f16s([offsets[6], offsets[7]]),
170 ];
171
172 let (start, end) = linear.angle.to_distance(&bounds);
173
174 let direction = [start.x, start.y, end.x, end.y];
175
176 Packed {
177 colors,
178 offsets,
179 direction,
180 }
181 }
182 }
183}
184
185fn pack_f16s(f: [f16; 2]) -> u32 {
187 let one = (f[0].to_bits() as u32) << 16;
188 let two = f[1].to_bits() as u32;
189
190 one | two
191}