Introduce web-colors feature flag to enable sRGB linear blending
This is how browsers perform color management. They treat gamma-corrected sRGB colors as if they were linear RGB. Correctness aside, this mode is introduced for legacy reasons. Most UI/UX tooling uses this color management as well, and many have created an intuition about how color should behave from interacting with a browser. This feature flag should facilitate application development with `iced` in those cases. More details: https://webcolorisstillbroken.com/
This commit is contained in:
parent
b5f102c558
commit
faa7627ea4
16 changed files with 99 additions and 30 deletions
|
|
@ -37,6 +37,8 @@ smol = ["iced_futures/smol"]
|
||||||
palette = ["iced_core/palette"]
|
palette = ["iced_core/palette"]
|
||||||
# Enables querying system information
|
# Enables querying system information
|
||||||
system = ["iced_winit/system"]
|
system = ["iced_winit/system"]
|
||||||
|
# Enables broken "sRGB linear" blending to reproduce color management of the Web
|
||||||
|
web-colors = ["iced_renderer/web-colors"]
|
||||||
# Enables the advanced module
|
# Enables the advanced module
|
||||||
advanced = []
|
advanced = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
mod rainbow {
|
mod rainbow {
|
||||||
use iced_graphics::primitive::{ColoredVertex2D, Primitive};
|
use iced_graphics::primitive::{ColoredVertex2D, Primitive};
|
||||||
|
|
||||||
|
use iced::advanced::graphics::color;
|
||||||
use iced::advanced::layout::{self, Layout};
|
use iced::advanced::layout::{self, Layout};
|
||||||
use iced::advanced::renderer;
|
use iced::advanced::renderer;
|
||||||
use iced::advanced::widget::{self, Widget};
|
use iced::advanced::widget::{self, Widget};
|
||||||
|
|
@ -84,39 +85,39 @@ mod rainbow {
|
||||||
vertices: vec![
|
vertices: vec![
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_center,
|
position: posn_center,
|
||||||
color: [1.0, 1.0, 1.0, 1.0],
|
color: color::pack([1.0, 1.0, 1.0, 1.0]),
|
||||||
},
|
},
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_tl,
|
position: posn_tl,
|
||||||
color: color_r,
|
color: color::pack(color_r),
|
||||||
},
|
},
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_t,
|
position: posn_t,
|
||||||
color: color_o,
|
color: color::pack(color_o),
|
||||||
},
|
},
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_tr,
|
position: posn_tr,
|
||||||
color: color_y,
|
color: color::pack(color_y),
|
||||||
},
|
},
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_r,
|
position: posn_r,
|
||||||
color: color_g,
|
color: color::pack(color_g),
|
||||||
},
|
},
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_br,
|
position: posn_br,
|
||||||
color: color_gb,
|
color: color::pack(color_gb),
|
||||||
},
|
},
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_b,
|
position: posn_b,
|
||||||
color: color_b,
|
color: color::pack(color_b),
|
||||||
},
|
},
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_bl,
|
position: posn_bl,
|
||||||
color: color_i,
|
color: color::pack(color_i),
|
||||||
},
|
},
|
||||||
ColoredVertex2D {
|
ColoredVertex2D {
|
||||||
position: posn_l,
|
position: posn_l,
|
||||||
color: color_v,
|
color: color::pack(color_v),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
indices: vec![
|
indices: vec![
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ categories = ["gui"]
|
||||||
geometry = ["lyon_path"]
|
geometry = ["lyon_path"]
|
||||||
opengl = []
|
opengl = []
|
||||||
image = ["dep:image", "kamadak-exif"]
|
image = ["dep:image", "kamadak-exif"]
|
||||||
|
web-colors = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam = "0.24"
|
glam = "0.24"
|
||||||
|
|
|
||||||
46
graphics/src/color.rs
Normal file
46
graphics/src/color.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
//! Manage colors for shaders.
|
||||||
|
use crate::core::Color;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
||||||
|
/// A color packed as 4 floats representing RGBA channels.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Packed([f32; 4]);
|
||||||
|
|
||||||
|
impl Packed {
|
||||||
|
/// Returns the internal components of the [`Packed`] color.
|
||||||
|
pub fn components(self) -> [f32; 4] {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A flag that indicates whether the renderer should perform gamma correction.
|
||||||
|
pub const GAMMA_CORRECTION: bool = internal::GAMMA_CORRECTION;
|
||||||
|
|
||||||
|
/// Packs a [`Color`].
|
||||||
|
pub fn pack(color: impl Into<Color>) -> Packed {
|
||||||
|
Packed(internal::pack(color.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "web-colors"))]
|
||||||
|
mod internal {
|
||||||
|
use crate::core::Color;
|
||||||
|
|
||||||
|
pub const GAMMA_CORRECTION: bool = true;
|
||||||
|
|
||||||
|
pub fn pack(color: Color) -> [f32; 4] {
|
||||||
|
color.into_linear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "web-colors")]
|
||||||
|
mod internal {
|
||||||
|
use crate::core::Color;
|
||||||
|
|
||||||
|
pub const GAMMA_CORRECTION: bool = false;
|
||||||
|
|
||||||
|
pub fn pack(color: Color) -> [f32; 4] {
|
||||||
|
[color.r, color.g, color.b, color.a]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,10 @@
|
||||||
//! For a gradient that you can use as a background variant for a widget, see [`Gradient`].
|
//! For a gradient that you can use as a background variant for a widget, see [`Gradient`].
|
||||||
//!
|
//!
|
||||||
//! [`Gradient`]: crate::core::Gradient;
|
//! [`Gradient`]: crate::core::Gradient;
|
||||||
|
use crate::color;
|
||||||
use crate::core::gradient::ColorStop;
|
use crate::core::gradient::ColorStop;
|
||||||
use crate::core::{self, Color, Point, Rectangle};
|
use crate::core::{self, Color, Point, Rectangle};
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
@ -101,7 +103,8 @@ impl Linear {
|
||||||
|
|
||||||
for (index, stop) in self.stops.iter().enumerate() {
|
for (index, stop) in self.stops.iter().enumerate() {
|
||||||
let [r, g, b, a] =
|
let [r, g, b, a] =
|
||||||
stop.map_or(Color::default(), |s| s.color).into_linear();
|
color::pack(stop.map_or(Color::default(), |s| s.color))
|
||||||
|
.components();
|
||||||
|
|
||||||
data[index * 4] = r;
|
data[index * 4] = r;
|
||||||
data[(index * 4) + 1] = g;
|
data[(index * 4) + 1] = g;
|
||||||
|
|
@ -133,7 +136,8 @@ pub fn pack(gradient: &core::Gradient, bounds: Rectangle) -> Packed {
|
||||||
|
|
||||||
for (index, stop) in linear.stops.iter().enumerate() {
|
for (index, stop) in linear.stops.iter().enumerate() {
|
||||||
let [r, g, b, a] =
|
let [r, g, b, a] =
|
||||||
stop.map_or(Color::default(), |s| s.color).into_linear();
|
color::pack(stop.map_or(Color::default(), |s| s.color))
|
||||||
|
.components();
|
||||||
|
|
||||||
data[index * 4] = r;
|
data[index * 4] = r;
|
||||||
data[(index * 4) + 1] = g;
|
data[(index * 4) + 1] = g;
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ mod transformation;
|
||||||
mod viewport;
|
mod viewport;
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
|
pub mod color;
|
||||||
pub mod compositor;
|
pub mod compositor;
|
||||||
pub mod damage;
|
pub mod damage;
|
||||||
pub mod gradient;
|
pub mod gradient;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
//! Draw using different graphical primitives.
|
//! Draw using different graphical primitives.
|
||||||
|
use crate::color;
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::image;
|
use crate::core::image;
|
||||||
use crate::core::svg;
|
use crate::core::svg;
|
||||||
|
|
@ -248,7 +249,7 @@ pub struct ColoredVertex2D {
|
||||||
pub position: [f32; 2],
|
pub position: [f32; 2],
|
||||||
|
|
||||||
/// The color of the vertex in __linear__ RGBA.
|
/// The color of the vertex in __linear__ RGBA.
|
||||||
pub color: [f32; 4],
|
pub color: color::Packed,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A vertex which contains 2D position & packed gradient data.
|
/// A vertex which contains 2D position & packed gradient data.
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ image = ["iced_tiny_skia/image", "iced_wgpu?/image"]
|
||||||
svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"]
|
svg = ["iced_tiny_skia/svg", "iced_wgpu?/svg"]
|
||||||
geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"]
|
geometry = ["iced_graphics/geometry", "iced_tiny_skia/geometry", "iced_wgpu?/geometry"]
|
||||||
tracing = ["iced_wgpu?/tracing"]
|
tracing = ["iced_wgpu?/tracing"]
|
||||||
|
web-colors = ["iced_wgpu?/web-colors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
raw-window-handle = "0.5"
|
raw-window-handle = "0.5"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ repository = "https://github.com/iced-rs/iced"
|
||||||
geometry = ["iced_graphics/geometry", "lyon"]
|
geometry = ["iced_graphics/geometry", "lyon"]
|
||||||
image = ["iced_graphics/image"]
|
image = ["iced_graphics/image"]
|
||||||
svg = ["resvg"]
|
svg = ["resvg"]
|
||||||
|
web-colors = ["iced_graphics/web-colors"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wgpu = "0.16"
|
wgpu = "0.16"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::core::{Color, Font, Point, Size};
|
use crate::core::{Color, Font, Point, Size};
|
||||||
use crate::graphics::backend;
|
use crate::graphics::backend;
|
||||||
|
use crate::graphics::color;
|
||||||
use crate::graphics::{Primitive, Transformation, Viewport};
|
use crate::graphics::{Primitive, Transformation, Viewport};
|
||||||
use crate::quad;
|
use crate::quad;
|
||||||
use crate::text;
|
use crate::text;
|
||||||
|
|
@ -239,7 +240,7 @@ impl Backend {
|
||||||
load: match clear_color {
|
load: match clear_color {
|
||||||
Some(background_color) => wgpu::LoadOp::Clear({
|
Some(background_color) => wgpu::LoadOp::Clear({
|
||||||
let [r, g, b, a] =
|
let [r, g, b, a] =
|
||||||
background_color.into_linear();
|
color::pack(background_color).components();
|
||||||
|
|
||||||
wgpu::Color {
|
wgpu::Color {
|
||||||
r: f64::from(r),
|
r: f64::from(r),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
//! Build and draw geometry.
|
//! Build and draw geometry.
|
||||||
use crate::core::{Point, Rectangle, Size, Vector};
|
use crate::core::{Point, Rectangle, Size, Vector};
|
||||||
|
use crate::graphics::color;
|
||||||
use crate::graphics::geometry::fill::{self, Fill};
|
use crate::graphics::geometry::fill::{self, Fill};
|
||||||
use crate::graphics::geometry::{
|
use crate::graphics::geometry::{
|
||||||
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
|
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
|
||||||
|
|
@ -68,7 +69,7 @@ impl BufferStack {
|
||||||
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
||||||
Box::new(tessellation::BuffersBuilder::new(
|
Box::new(tessellation::BuffersBuilder::new(
|
||||||
buffer,
|
buffer,
|
||||||
TriangleVertex2DBuilder(color.into_linear()),
|
TriangleVertex2DBuilder(color::pack(*color)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
(Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
|
(Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
|
||||||
|
|
@ -91,7 +92,7 @@ impl BufferStack {
|
||||||
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
(Style::Solid(color), Buffer::Solid(buffer)) => {
|
||||||
Box::new(tessellation::BuffersBuilder::new(
|
Box::new(tessellation::BuffersBuilder::new(
|
||||||
buffer,
|
buffer,
|
||||||
TriangleVertex2DBuilder(color.into_linear()),
|
TriangleVertex2DBuilder(color::pack(*color)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
(Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
|
(Style::Gradient(gradient), Buffer::Gradient(buffer)) => {
|
||||||
|
|
@ -526,7 +527,7 @@ impl tessellation::StrokeVertexConstructor<primitive::GradientVertex2D>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TriangleVertex2DBuilder([f32; 4]);
|
struct TriangleVertex2DBuilder(color::Packed);
|
||||||
|
|
||||||
impl tessellation::FillVertexConstructor<primitive::ColoredVertex2D>
|
impl tessellation::FillVertexConstructor<primitive::ColoredVertex2D>
|
||||||
for TriangleVertex2DBuilder
|
for TriangleVertex2DBuilder
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ pub use text::Text;
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::{Color, Font, Point, Rectangle, Size, Vector};
|
use crate::core::{Color, Font, Point, Rectangle, Size, Vector};
|
||||||
|
use crate::graphics::color;
|
||||||
use crate::graphics::{Primitive, Viewport};
|
use crate::graphics::{Primitive, Viewport};
|
||||||
use crate::quad::{self, Quad};
|
use crate::quad::{self, Quad};
|
||||||
|
|
||||||
|
|
@ -150,7 +151,7 @@ impl<'a> Layer<'a> {
|
||||||
bounds.y + translation.y,
|
bounds.y + translation.y,
|
||||||
],
|
],
|
||||||
size: [bounds.width, bounds.height],
|
size: [bounds.width, bounds.height],
|
||||||
border_color: border_color.into_linear(),
|
border_color: color::pack(*border_color),
|
||||||
border_radius: *border_radius,
|
border_radius: *border_radius,
|
||||||
border_width: *border_width,
|
border_width: *border_width,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use gradient::Gradient;
|
||||||
use solid::Solid;
|
use solid::Solid;
|
||||||
|
|
||||||
use crate::core::{Background, Rectangle};
|
use crate::core::{Background, Rectangle};
|
||||||
|
use crate::graphics::color;
|
||||||
use crate::graphics::{self, Transformation};
|
use crate::graphics::{self, Transformation};
|
||||||
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
|
@ -217,7 +218,7 @@ pub struct Quad {
|
||||||
pub size: [f32; 2],
|
pub size: [f32; 2],
|
||||||
|
|
||||||
/// The border color of the [`Quad`], in __linear RGB__.
|
/// The border color of the [`Quad`], in __linear RGB__.
|
||||||
pub border_color: [f32; 4],
|
pub border_color: color::Packed,
|
||||||
|
|
||||||
/// The border radii of the [`Quad`].
|
/// The border radii of the [`Quad`].
|
||||||
pub border_radius: [f32; 4],
|
pub border_radius: [f32; 4],
|
||||||
|
|
@ -250,7 +251,7 @@ impl Batch {
|
||||||
let kind = match background {
|
let kind = match background {
|
||||||
Background::Color(color) => {
|
Background::Color(color) => {
|
||||||
self.solids.push(Solid {
|
self.solids.push(Solid {
|
||||||
color: color.into_linear(),
|
color: color::pack(*color),
|
||||||
quad,
|
quad,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::graphics::color;
|
||||||
use crate::quad::{self, Quad};
|
use crate::quad::{self, Quad};
|
||||||
use crate::Buffer;
|
use crate::Buffer;
|
||||||
|
|
||||||
|
|
@ -9,7 +10,7 @@ use std::ops::Range;
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Solid {
|
pub struct Solid {
|
||||||
/// The background color data of the quad.
|
/// The background color data of the quad.
|
||||||
pub color: [f32; 4],
|
pub color: color::Packed,
|
||||||
|
|
||||||
/// The [`Quad`] data of the [`Solid`].
|
/// The [`Quad`] data of the [`Solid`].
|
||||||
pub quad: Quad,
|
pub quad: Quad,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use crate::core::alignment;
|
||||||
use crate::core::font::{self, Font};
|
use crate::core::font::{self, Font};
|
||||||
use crate::core::text::{Hit, LineHeight, Shaping};
|
use crate::core::text::{Hit, LineHeight, Shaping};
|
||||||
use crate::core::{Pixels, Point, Rectangle, Size};
|
use crate::core::{Pixels, Point, Rectangle, Size};
|
||||||
|
use crate::graphics::color;
|
||||||
use crate::layer::Text;
|
use crate::layer::Text;
|
||||||
|
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
@ -155,7 +156,8 @@ impl Pipeline {
|
||||||
bottom: (clip_bounds.y + clip_bounds.height) as i32,
|
bottom: (clip_bounds.y + clip_bounds.height) as i32,
|
||||||
},
|
},
|
||||||
default_color: {
|
default_color: {
|
||||||
let [r, g, b, a] = section.color.into_linear();
|
let [r, g, b, a] =
|
||||||
|
color::pack(section.color).components();
|
||||||
|
|
||||||
glyphon::Color::rgba(
|
glyphon::Color::rgba(
|
||||||
(r * 255.0) as u8,
|
(r * 255.0) as u8,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
//! Connect a window with a renderer.
|
//! Connect a window with a renderer.
|
||||||
use crate::core::Color;
|
use crate::core::Color;
|
||||||
use crate::graphics;
|
use crate::graphics;
|
||||||
|
use crate::graphics::color;
|
||||||
use crate::graphics::compositor;
|
use crate::graphics::compositor;
|
||||||
use crate::graphics::{Error, Primitive, Viewport};
|
use crate::graphics::{Error, Primitive, Viewport};
|
||||||
use crate::{Backend, Renderer, Settings};
|
use crate::{Backend, Renderer, Settings};
|
||||||
|
|
@ -69,16 +70,19 @@ impl<Theme> Compositor<Theme> {
|
||||||
let format = compatible_surface.as_ref().and_then(|surface| {
|
let format = compatible_surface.as_ref().and_then(|surface| {
|
||||||
let capabilities = surface.get_capabilities(&adapter);
|
let capabilities = surface.get_capabilities(&adapter);
|
||||||
|
|
||||||
capabilities
|
let mut formats = capabilities.formats.iter().copied();
|
||||||
.formats
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.find(wgpu::TextureFormat::is_srgb)
|
|
||||||
.or_else(|| {
|
|
||||||
log::warn!("No sRGB format found!");
|
|
||||||
|
|
||||||
capabilities.formats.first().copied()
|
let format = if color::GAMMA_CORRECTION {
|
||||||
})
|
formats.find(wgpu::TextureFormat::is_srgb)
|
||||||
|
} else {
|
||||||
|
formats.find(|format| !wgpu::TextureFormat::is_srgb(format))
|
||||||
|
};
|
||||||
|
|
||||||
|
format.or_else(|| {
|
||||||
|
log::warn!("No format found!");
|
||||||
|
|
||||||
|
capabilities.formats.first().copied()
|
||||||
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
log::info!("Selected format: {:?}", format);
|
log::info!("Selected format: {:?}", format);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue