1
0
Fork 0
forked from wry/wry

theme: store colors in linear space

This commit is contained in:
Julian Orth 2025-02-25 15:43:05 +01:00
parent b7f93b37a6
commit 135f37dbcd
27 changed files with 221 additions and 135 deletions

View file

@ -1,6 +1,7 @@
use {
crate::{
cli::{GlobalArgs, color::parse_color, duration::parse_duration},
theme::TransferFunction,
tools::tool_client::{ToolClient, with_tool_client},
wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled},
},
@ -85,12 +86,13 @@ impl DamageTracking {
}
DamageTrackingCmd::SetColor(c) => {
let color = parse_color(&c.color);
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
tc.send(SetVisualizerColor {
self_id: dt,
r: color.r,
g: color.g,
b: color.b,
a: color.a,
r,
g,
b,
a,
});
}
DamageTrackingCmd::SetDecay(c) => {

View file

@ -14,7 +14,7 @@ use {
output_schedule::map_cursor_hz,
scale::Scale,
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
theme::{Color, ThemeSized},
theme::{Color, ThemeSized, TransferFunction},
tree::{
ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode,
TearingMode, VrrMode, WsMoveConfig, move_ws_to_output,
@ -1599,8 +1599,8 @@ impl ConfigProxyHandler {
fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> {
let color = self.get_color(colorable)?.get();
let color =
jay_config::theme::Color::new_f32_premultiplied(color.r, color.g, color.b, color.a);
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
let color = jay_config::theme::Color::new_f32_premultiplied(r, g, b, a);
self.respond(Response::GetColor { color });
Ok(())
}

View file

@ -93,7 +93,7 @@ impl DamageVisualizer {
entry_added: Default::default(),
enabled: Default::default(),
decay: Cell::new(Duration::from_secs(2)),
color: Cell::new(Color::from_rgba_straight(255, 0, 0, 128)),
color: Cell::new(Color::from_srgba_straight(255, 0, 0, 128)),
}
}

View file

@ -352,25 +352,16 @@ impl dyn GfxFramebuffer {
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
) -> Result<Option<SyncFile>, GfxError> {
self.clear_with(acquire_sync, release_sync, 0.0, 0.0, 0.0, 0.0)
self.clear_with(acquire_sync, release_sync, &Color::TRANSPARENT)
}
pub fn clear_with(
self: &Rc<Self>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
r: f32,
g: f32,
b: f32,
a: f32,
color: &Color,
) -> Result<Option<SyncFile>, GfxError> {
self.render(
acquire_sync,
release_sync,
&[],
Some(&Color { r, g, b, a }),
None,
)
self.render(acquire_sync, release_sync, &[], Some(color), None)
}
pub fn logical_size(&self, transform: Transform) -> (i32, i32) {

View file

@ -84,7 +84,7 @@ use {
GL_TRIANGLE_STRIP, GL_TRIANGLES,
},
},
theme::Color,
theme::{Color, TransferFunction},
utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage},
video::{
dmabuf::DMA_BUF_SYNC_READ,
@ -305,10 +305,11 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) -> Option<SyncFile> {
}
fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) {
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
let gles = ctx.ctx.dpy.gles;
unsafe {
(gles.glUseProgram)(ctx.fill_prog.prog);
(gles.glUniform4f)(ctx.fill_prog_color, color.r, color.g, color.b, color.a);
(gles.glUniform4f)(ctx.fill_prog_color, r, g, b, a);
(gles.glVertexAttribPointer)(
ctx.fill_prog_pos as _,
2,

View file

@ -18,7 +18,7 @@ use {
sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
},
rect::Region,
theme::Color,
theme::{Color, TransferFunction},
},
std::{
cell::Cell,
@ -78,7 +78,8 @@ impl Framebuffer {
(gles.glBindFramebuffer)(GL_FRAMEBUFFER, self.gl.fbo);
(gles.glViewport)(0, 0, self.gl.width, self.gl.height);
if let Some(c) = clear {
(gles.glClearColor)(c.r, c.g, c.b, c.a);
let [r, g, b, a] = c.to_array(TransferFunction::Srgb);
(gles.glClearColor)(r, g, b, a);
(gles.glClear)(GL_COLOR_BUFFER_BIT);
}
(gles.glBlendFunc)(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

View file

@ -30,7 +30,7 @@ use {
},
io_uring::IoUring,
rect::{Rect, Region},
theme::Color,
theme::{Color, TransferFunction},
utils::{
copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, once::Once,
stack::Stack,
@ -589,8 +589,8 @@ impl VulkanRenderer {
let clear_value = ClearValue {
color: ClearColorValue {
float32: match pass {
RenderPass::BlendBuffer => clear.to_array_linear(None),
RenderPass::FrameBuffer => clear.to_array_srgb(None),
RenderPass::BlendBuffer => clear.to_array(TransferFunction::Linear),
RenderPass::FrameBuffer => clear.to_array(TransferFunction::Srgb),
},
},
};
@ -704,8 +704,12 @@ impl VulkanRenderer {
let push = FillPushConstants {
pos: r.rect.to_points(),
color: match pass {
RenderPass::BlendBuffer => r.color.to_array_linear(r.alpha),
RenderPass::FrameBuffer => r.color.to_array_srgb(r.alpha),
RenderPass::BlendBuffer => {
r.color.to_array2(TransferFunction::Linear, r.alpha)
}
RenderPass::FrameBuffer => {
r.color.to_array2(TransferFunction::Srgb, r.alpha)
}
},
};
let source_type = match push.color[3] < 1.0 {
@ -1309,7 +1313,7 @@ impl VulkanRenderer {
for opt in opts.iter().rev() {
let (opaque, fb_rect) = match opt {
GfxApiOpt::Sync => continue,
GfxApiOpt::FillRect(f) => (f.effective_color().a >= 1.0, f.rect),
GfxApiOpt::FillRect(f) => (f.effective_color().is_opaque(), f.rect),
GfxApiOpt::CopyTexture(c) => {
let opaque = 'opaque: {
if let Some(a) = c.alpha {

View file

@ -4,7 +4,7 @@ use {
globals::{Global, GlobalName},
leaks::Tracker,
object::{Object, Version},
theme::Color,
theme::{Color, TransferFunction},
wire::{
JayCompositorId,
jay_damage_tracking::{
@ -96,12 +96,8 @@ impl JayDamageTrackingRequestHandler for JayDamageTracking {
req: SetVisualizerColor,
_slf: &Rc<Self>,
) -> Result<(), Self::Error> {
self.client.state.damage_visualizer.set_color(Color {
r: req.r,
g: req.g,
b: req.b,
a: req.a,
});
let color = Color::new(TransferFunction::Srgb, req.r, req.g, req.b) * req.a;
self.client.state.damage_visualizer.set_color(color);
Ok(())
}

View file

@ -8,7 +8,6 @@ use {
leaks::Tracker,
object::{Object, Version},
rect::{Rect, Region},
theme::Color,
utils::errorfmt::ErrorFmt,
video::dmabuf::DmaBuf,
wire::{WlBufferId, wl_buffer::*},
@ -42,7 +41,7 @@ pub struct WlBuffer {
render_ctx_version: Cell<u32>,
pub storage: RefCell<Option<WlBufferStorage>>,
shm: bool,
pub color: Option<Color>,
pub color: Option<[u32; 4]>,
width: i32,
height: i32,
pub tracker: Tracker<Self>,
@ -149,7 +148,7 @@ impl WlBuffer {
width: 1,
height: 1,
tracker: Default::default(),
color: Some(Color::from_u32_rgba_premultiplied(r, g, b, a)),
color: Some([r, g, b, a]),
}
}

View file

@ -435,7 +435,7 @@ impl GfxFramebuffer for TestGfxFb {
a = 255;
}
staging[(y * width + x) as usize] =
Color::from_rgba_premultiplied(r, g, b, a);
Color::from_srgba_premultiplied(r, g, b, a);
}
data = data.add(stride as usize);
}
@ -446,7 +446,7 @@ impl GfxFramebuffer for TestGfxFb {
for y in 0..height {
for x in 0..width {
let [r, g, b, a] =
staging[(y * width + x) as usize].to_rgba_premultiplied();
staging[(y * width + x) as usize].to_srgba_premultiplied();
*data.add((x * 4) as usize).cast::<[u8; 4]>() = [b, g, r, a];
}
data = data.add(stride as usize);
@ -499,7 +499,7 @@ impl GfxFramebuffer for TestGfxFb {
if !t_format.has_alpha {
a = 255;
}
let mut color = Color::from_rgba_premultiplied(r, g, b, a);
let mut color = Color::from_srgba_premultiplied(r, g, b, a);
if let Some(alpha) = c.alpha {
color = color * alpha;
}

View file

@ -19,7 +19,7 @@ pub struct TestShmBuffer {
impl TestShmBuffer {
pub fn fill(&self, color: Color) {
let [cr, cg, cb, ca] = color.to_rgba_premultiplied();
let [cr, cg, cb, ca] = color.to_srgba_premultiplied();
for [b, g, r, a] in self.deref().array_chunks_ext::<4>() {
r.set(cr);
g.set(cg);

View file

@ -4,7 +4,7 @@ use {
test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject,
test_transport::TestTransport,
},
theme::Color,
theme::{Color, TransferFunction},
wire::{WpSinglePixelBufferManagerV1Id, wp_single_pixel_buffer_manager_v1::*},
},
std::{cell::Cell, rc::Rc},
@ -31,13 +31,14 @@ impl TestSinglePixelBufferManager {
destroyed: Cell::new(false),
});
let map = |c: f32| (c as f64 * u32::MAX as f64) as u32;
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
self.tran.send(CreateU32RgbaBuffer {
self_id: self.id,
id: obj.id,
r: map(color.r),
g: map(color.g),
b: map(color.b),
a: map(color.a),
r: map(r),
g: map(g),
b: map(b),
a: map(a),
})?;
self.tran.add_obj(obj.clone())?;
Ok(obj)

View file

@ -39,6 +39,6 @@ impl TestSurfaceExt {
}
pub fn set_color(&self, r: u8, g: u8, b: u8, a: u8) {
self.color.set(Color::from_rgba_straight(r, g, b, a));
self.color.set(Color::from_srgba_straight(r, g, b, a));
}
}

View file

@ -34,7 +34,7 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let buffer = client
.spbm
.create_buffer(Color::from_rgba_straight(255, 255, 255, 255))?;
.create_buffer(Color::from_srgba_straight(255, 255, 255, 255))?;
child.attach(buffer.id)?;
child_viewport.set_source(0, 0, 1, 1)?;

View file

@ -25,7 +25,7 @@ async fn test(run: Rc<TestRun>) -> Result<(), TestError> {
let win1 = client.create_window().await?;
win1.map2().await?;
let buffer = client.spbm.create_buffer(Color::from_rgb(255, 0, 0))?;
let buffer = client.spbm.create_buffer(Color::from_srgb(255, 0, 0))?;
let surface = client.comp.create_surface().await?;
let vp = client.viewporter.get_viewport(&surface)?;
vp.set_destination(100, 100)?;

View file

@ -36,11 +36,11 @@ async fn test(run: Rc<TestRun>) -> TestResult {
}};
}
let buf1 = client.spbm.create_buffer(Color::from_rgb(0, 255, 0))?;
let buf1 = client.spbm.create_buffer(Color::from_srgb(0, 255, 0))?;
let (ss1, alpha1) = create_surface!(&buf1, 0, 0);
let buf2 = client.shm.create_buffer(1, 1)?;
buf2.fill(Color::from_rgb(0, 255, 0));
buf2.fill(Color::from_srgb(0, 255, 0));
let (ss2, alpha2) = create_surface!(&buf2.buffer, 100, 0);
client.compare_screenshot("1", false).await?;

View file

@ -67,18 +67,18 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>) -> Rc<dyn GuiElement> {
let accept_button = static_button(surface, ButtonRole::Accept, "Allow");
let reject_button = static_button(surface, ButtonRole::Reject, "Reject");
for button in [&accept_button, &reject_button] {
button.border_color.set(Color::from_gray(100));
button.border_color.set(Color::from_gray_srgb(100));
button.border.set(2.0);
button.padding.set(5.0);
}
accept_button.bg_color.set(Color::from_rgb(170, 200, 170));
accept_button.bg_color.set(Color::from_srgb(170, 200, 170));
accept_button
.bg_hover_color
.set(Color::from_rgb(170, 255, 170));
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
.set(Color::from_srgb(170, 255, 170));
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
reject_button
.bg_hover_color
.set(Color::from_rgb(255, 170, 170));
.set(Color::from_srgb(255, 170, 170));
let flow = Rc::new(Flow::default());
flow.orientation.set(Orientation::Vertical);
flow.cross_align.set(Align::Center);

View file

@ -87,22 +87,22 @@ fn create_accept_gui(surface: &Rc<SelectionGuiSurface>, for_restore: bool) -> Rc
&window_button,
&reject_button,
] {
button.border_color.set(Color::from_gray(100));
button.border_color.set(Color::from_gray_srgb(100));
button.border.set(2.0);
button.padding.set(5.0);
}
restore_button.bg_color.set(Color::from_rgb(170, 170, 200));
restore_button.bg_color.set(Color::from_srgb(170, 170, 200));
restore_button
.bg_hover_color
.set(Color::from_rgb(170, 170, 255));
.set(Color::from_srgb(170, 170, 255));
for button in [&accept_button, &workspace_button, &window_button] {
button.bg_color.set(Color::from_rgb(170, 200, 170));
button.bg_hover_color.set(Color::from_rgb(170, 255, 170));
button.bg_color.set(Color::from_srgb(170, 200, 170));
button.bg_hover_color.set(Color::from_srgb(170, 255, 170));
}
reject_button.bg_color.set(Color::from_rgb(200, 170, 170));
reject_button.bg_color.set(Color::from_srgb(200, 170, 170));
reject_button
.bg_hover_color
.set(Color::from_rgb(255, 170, 170));
.set(Color::from_srgb(255, 170, 170));
let flow = Rc::new(Flow::default());
flow.orientation.set(Orientation::Vertical);
flow.cross_align.set(Align::Center);

View file

@ -7,7 +7,7 @@ use {
consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE},
},
rect::Rect,
theme::Color,
theme::{Color, TransferFunction},
},
std::{ops::Neg, rc::Rc, sync::Arc},
};
@ -78,9 +78,9 @@ pub fn render(
let data = create_data(font, width, height, scale)?;
data.layout.set_text(text);
let font_height = data.layout.pixel_size().1;
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
data.cctx
.set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _);
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
let y = y.unwrap_or((height - font_height) / 2);
data.cctx.move_to(x as f64, y as f64);
data.layout.show_layout();

View file

@ -139,9 +139,9 @@ impl Default for Button {
hover: Default::default(),
padding: Default::default(),
border: Default::default(),
border_color: Cell::new(Color::from_gray(0)),
bg_color: Cell::new(Color::from_gray(255)),
bg_hover_color: Cell::new(Color::from_gray(255)),
border_color: Cell::new(Color::from_gray_srgb(0)),
bg_color: Cell::new(Color::from_gray_srgb(255)),
bg_hover_color: Cell::new(Color::from_gray_srgb(255)),
text: Default::default(),
font: Arc::new(DEFAULT_FONT.to_string()),
tex: Default::default(),
@ -172,7 +172,7 @@ impl GuiElement for Button {
None,
&self.font,
&text,
Color::from_gray(0),
Color::from_gray_srgb(0),
Some(scale as _),
true,
);
@ -296,7 +296,7 @@ impl GuiElement for Label {
None,
&self.font,
&text,
Color::from_gray(255),
Color::from_gray_srgb(255),
Some(scale as _),
false,
);
@ -635,7 +635,7 @@ impl WindowData {
AcquireSync::Implicit,
ReleaseSync::Implicit,
self.scale.get(),
Some(&Color::from_gray(0)),
Some(&Color::from_gray_srgb(0)),
None,
&mut |r| {
if let Some(content) = self.content.get() {

View file

@ -11,7 +11,7 @@ use {
renderer::renderer_base::RendererBase,
scale::Scale,
state::State,
theme::Color,
theme::{Color, TransferFunction},
tree::{
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
ToplevelNodeBase, WorkspaceNode,
@ -202,7 +202,7 @@ impl Renderer<'_> {
let pos = placeholder.tl_data().pos.get();
self.base.fill_boxes(
std::slice::from_ref(&pos.at_point(x, y)),
&Color::from_rgba_straight(20, 20, 20, 255),
&Color::from_srgba_straight(20, 20, 20, 255),
);
if let Some(tex) = placeholder.textures.borrow().get(&self.base.scale) {
if let Some(texture) = tex.texture() {
@ -448,8 +448,15 @@ impl Renderer<'_> {
Some(bounds) => rect.intersect(*bounds),
};
if !rect.is_empty() {
let color = Color::from_u32_premultiplied(
TransferFunction::Srgb,
color[0],
color[1],
color[2],
color[3],
);
self.base.ops.push(GfxApiOpt::Sync);
self.base.fill_scaled_boxes(&[rect], color, alpha);
self.base.fill_scaled_boxes(&[rect], &color, alpha);
}
}
} else {

View file

@ -14,7 +14,7 @@ use {
},
},
rect::{Rect, Region},
theme::Color,
theme::{Color, TransferFunction},
utils::{
clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent,
},
@ -187,9 +187,9 @@ fn render(
data.layout.set_text(text);
}
let font_height = data.layout.pixel_size().1;
let [r, g, b, a] = color.to_array(TransferFunction::Srgb);
data.cctx.set_operator(CAIRO_OPERATOR_SOURCE);
data.cctx
.set_source_rgba(color.r as _, color.g as _, color.b as _, color.a as _);
data.cctx.set_source_rgba(r as _, g as _, b as _, a as _);
let y = y.unwrap_or((height - font_height) / 2);
data.cctx.move_to(x as f64, y as f64);
data.layout.show_layout();

View file

@ -3,12 +3,18 @@ use {
std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc},
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TransferFunction {
Srgb,
Linear,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
r: f32,
g: f32,
b: f32,
a: f32,
}
impl Eq for Color {}
@ -65,77 +71,155 @@ impl Color {
a: 1.0,
};
pub fn from_gray(g: u8) -> Self {
Self::from_rgb(g, g, g)
pub fn new(transfer_function: TransferFunction, mut r: f32, mut g: f32, mut b: f32) -> Self {
fn srgb(c: f32) -> f32 {
if c <= 0.04045 {
c / 12.92
} else {
((c + 0.055) / 1.055).powf(2.4)
}
}
#[inline(always)]
fn linear(c: f32) -> f32 {
c
}
macro_rules! convert {
($tf:ident) => {{
r = $tf(r);
g = $tf(g);
b = $tf(b);
}};
}
match transfer_function {
TransferFunction::Srgb => convert!(srgb),
TransferFunction::Linear => convert!(linear),
}
Self { r, g, b, a: 1.0 }
}
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
Self {
r: to_f32(r),
g: to_f32(g),
b: to_f32(b),
a: 1.0,
pub fn new_premultiplied(
transfer_function: TransferFunction,
mut r: f32,
mut g: f32,
mut b: f32,
a: f32,
) -> Self {
if transfer_function == TransferFunction::Linear {
return Self { r, g, b, a };
}
if a < 1.0 && a > 0.0 {
for c in [&mut r, &mut g, &mut b] {
*c /= a;
}
}
let mut c = Self::new(transfer_function, r, g, b);
if a < 1.0 {
c = c * a;
}
c
}
pub fn is_opaque(&self) -> bool {
self.a >= 1.0
}
pub fn from_gray_srgb(g: u8) -> Self {
Self::from_srgb(g, g, g)
}
pub fn from_srgb(r: u8, g: u8, b: u8) -> Self {
Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b))
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
Self {
r: to_f32(r),
g: to_f32(g),
b: to_f32(b),
a: to_f32(a),
}
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::new_premultiplied(
TransferFunction::Srgb,
to_f32(r),
to_f32(g),
to_f32(b),
to_f32(a),
)
}
pub fn from_u32_rgba_premultiplied(r: u32, g: u32, b: u32, a: u32) -> Self {
pub fn from_u32_premultiplied(
transfer_function: TransferFunction,
r: u32,
g: u32,
b: u32,
a: u32,
) -> Self {
fn to_f32(c: u32) -> f32 {
((c as f64) / (u32::MAX as f64)) as f32
}
Self {
r: to_f32(r),
g: to_f32(g),
b: to_f32(b),
a: to_f32(a),
}
Self::new_premultiplied(
transfer_function,
to_f32(r),
to_f32(g),
to_f32(b),
to_f32(a),
)
}
pub fn from_rgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
let alpha = to_f32(a);
Self {
r: to_f32(r) * alpha,
g: to_f32(g) * alpha,
b: to_f32(b) * alpha,
a: alpha,
pub fn from_srgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
let mut c = Self::new(TransferFunction::Srgb, to_f32(r), to_f32(g), to_f32(b));
if a < 255 {
c = c * to_f32(a);
}
c
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
pub fn to_rgba_premultiplied(self) -> [u8; 4] {
[to_u8(self.r), to_u8(self.g), to_u8(self.b), to_u8(self.a)]
pub fn to_srgba_premultiplied(self) -> [u8; 4] {
let [r, g, b, a] = self.to_array(TransferFunction::Srgb);
[to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
}
pub fn to_array_srgb(self, alpha: Option<f32>) -> [f32; 4] {
let a = alpha.unwrap_or(1.0);
[self.r * a, self.g * a, self.b * a, self.a * a]
pub fn to_array(self, transfer_function: TransferFunction) -> [f32; 4] {
self.to_array2(transfer_function, None)
}
pub fn to_array_linear(self, alpha: Option<f32>) -> [f32; 4] {
fn to_linear(srgb: f32) -> f32 {
if srgb <= 0.04045 {
srgb / 12.92
pub fn to_array2(self, transfer_function: TransferFunction, alpha: Option<f32>) -> [f32; 4] {
let mut res = [self.r, self.g, self.b, self.a];
fn srgb(c: f32) -> f32 {
if c <= 0.0031308 {
c * 12.92
} else {
((srgb + 0.055) / 1.055).powf(2.4)
1.055 * c.powf(1.0 / 2.4) - 0.055
}
}
let a1 = if self.a == 0.0 { 1.0 } else { self.a };
let a2 = self.a * alpha.unwrap_or(1.0);
[
to_linear(self.r / a1) * a2,
to_linear(self.g / a1) * a2,
to_linear(self.b / a1) * a2,
a2,
]
fn linear(c: f32) -> f32 {
c
}
macro_rules! convert {
($tf:ident) => {{
for c in &mut res[..3] {
*c = $tf(*c);
}
}};
}
if transfer_function != TransferFunction::Linear {
if self.a < 1.0 && self.a > 0.0 {
for c in &mut res[..3] {
*c /= self.a;
}
}
match transfer_function {
TransferFunction::Srgb => convert!(srgb),
TransferFunction::Linear => convert!(linear),
}
if self.a < 1.0 {
for c in &mut res[..3] {
*c *= self.a;
}
}
}
if let Some(a) = alpha {
for c in &mut res {
*c *= a;
}
}
res
}
#[cfg_attr(not(feature = "it"), expect(dead_code))]
@ -152,7 +236,7 @@ impl Color {
impl From<jay_config::theme::Color> for Color {
fn from(f: jay_config::theme::Color) -> Self {
let [r, g, b, a] = f.to_f32_premultiplied();
Self { r, g, b, a }
Self::new_premultiplied(TransferFunction::Srgb, r, g, b, a)
}
}
@ -184,10 +268,10 @@ macro_rules! colors {
}
};
(@colors ($r:expr, $g:expr, $b:expr)) => {
Color::from_rgb($r, $g, $b)
Color::from_srgb($r, $g, $b)
};
(@colors ($r:expr, $g:expr, $b:expr, $a:expr)) => {
Color::from_rgba_straight($r, $g, $b, $a)
Color::from_srgba_straight($r, $g, $b, $a)
};
}