Skip to content

Instantly share code, notes, and snippets.

@Gankra

Gankra/gradient.rs

Last active Jul 16, 2019
Embed
What would you like to do?
/// Generate a color ramp filling the indices in [start_idx, end_idx) and interpolating
/// from start_color to end_color.
fn fill_colors(
start_idx: usize,
end_idx: usize,
start_color: &PremultipliedColorF,
end_color: &PremultipliedColorF,
entries: &mut ArrayVec<[GradientDataEntry; GRADIENT_DATA_SIZE]>,
) {
debug_assert_eq!(start_idx, entries.len());
// Calculate the color difference for individual steps in the ramp.
let inv_steps = 1.0 / (end_idx - start_idx) as f32;
let step_r = (end_color.r - start_color.r) * inv_steps;
let step_g = (end_color.g - start_color.g) * inv_steps;
let step_b = (end_color.b - start_color.b) * inv_steps;
let step_a = (end_color.a - start_color.a) * inv_steps;
let mut cur_color = *start_color;
// Walk the ramp writing start and end colors for each entry.
for index in start_idx .. end_idx {
entries.push(GradientDataEntry {
start_color: cur_color,
end_color: {
cur_color.r += step_r;
cur_color.g += step_g;
cur_color.b += step_b;
cur_color.a += step_a;
cur_color
}
});
}
}
/// Compute an index into the gradient entry table based on a gradient stop offset. This
/// function maps offsets from [0, 1] to indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END].
#[inline]
fn get_index(offset: f32) -> usize {
(offset.max(0.0).min(1.0) * GRADIENT_DATA_TABLE_SIZE as f32 +
GRADIENT_DATA_TABLE_BEGIN as f32)
.round() as usize
}
// Build the gradient data from the supplied stops, reversing them if necessary.
fn build(
reverse_stops: bool,
request: &mut GpuDataRequest,
src_stops: &[GradientStop],
) {
// Preconditions (should be ensured by DisplayListBuilder):
// * we have at least two stops
// * first stop has offset 0.0
// * last stop has offset 1.0
let mut src_stops = src_stops.into_iter();
let mut cur_color = match src_stops.next() {
Some(stop) => {
debug_assert_eq!(stop.offset, 0.0);
stop.color.premultiplied()
}
None => {
error!("Zero gradient stops found!");
PremultipliedColorF::BLACK
}
};
// A table of gradient entries, with two colors per entry, that specify the start and end color
// within the segment of the gradient space represented by that entry. To lookup a gradient result,
// first the entry index is calculated to determine which two colors to interpolate between, then
// the offset within that entry bucket is used to interpolate between the two colors in that entry.
// This layout preserves hard stops, as the end color for a given entry can differ from the start
// color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
// format for texture upload. This table requires the gradient color stops to be normalized to the
// range [0, 1]. The first and last entries hold the first and last color stop colors respectively,
// while the entries in between hold the interpolated color stop values for the range [0, 1].
let mut entries = ArrayVec::<[GradientDataEntry; GRADIENT_DATA_SIZE]>::new();
// Fill in the first entry with the first color stop
GradientGpuBlockBuilder::fill_colors(
GRADIENT_DATA_FIRST_STOP,
GRADIENT_DATA_FIRST_STOP + 1,
&cur_color,
&cur_color,
&mut entries,
);
// Fill in the center of the gradient table, generating a color ramp between each consecutive pair
// of gradient stops. Each iteration of a loop will fill the indices in [cur_idx, next_idx). The
// loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN;
for next in src_stops {
let next_color = next.color.premultiplied();
let next_idx = Self::get_index(next.offset);
if next_idx > cur_idx {
GradientGpuBlockBuilder::fill_colors(
cur_idx,
next_idx,
&cur_color,
&next_color,
&mut entries,
);
cur_idx = next_idx;
}
cur_color = next_color;
}
if cur_idx != GRADIENT_DATA_TABLE_END {
error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
GradientGpuBlockBuilder::fill_colors(
cur_idx,
GRADIENT_DATA_TABLE_END,
&PremultipliedColorF::WHITE,
&cur_color,
&mut entries,
);
}
// Fill in the last entry with the last color stop
GradientGpuBlockBuilder::fill_colors(
GRADIENT_DATA_LAST_STOP,
GRADIENT_DATA_LAST_STOP + 1,
&cur_color,
&cur_color,
&mut entries,
);
debug_assert!(entries.is_full());
if reverse_stops {
for entry in entries.as_slice().iter().rev() {
request.push(entry.start_color);
request.push(entry.end_color);
}
} else {
for entry in entries.as_slice().iter() {
request.push(entry.start_color);
request.push(entry.end_color);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.