diff --git a/src/cli/damage_tracking.rs b/src/cli/damage_tracking.rs index cfc63464..726a0532 100644 --- a/src/cli/damage_tracking.rs +++ b/src/cli/damage_tracking.rs @@ -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) => { diff --git a/src/config/handler.rs b/src/config/handler.rs index f18ccc79..fb4ee4e5 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -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(()) } diff --git a/src/damage.rs b/src/damage.rs index 24e4bef2..86b0f4a6 100644 --- a/src/damage.rs +++ b/src/damage.rs @@ -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)), } } diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 67a06bc1..e832a8e1 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -352,25 +352,16 @@ impl dyn GfxFramebuffer { acquire_sync: AcquireSync, release_sync: ReleaseSync, ) -> Result, 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, acquire_sync: AcquireSync, release_sync: ReleaseSync, - r: f32, - g: f32, - b: f32, - a: f32, + color: &Color, ) -> Result, 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) { diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index f10acb32..ff86fc95 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -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 { } 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, diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 7c0c968d..550fdd18 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -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); diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 5eb50f1d..bce73eb1 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -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 { diff --git a/src/ifs/jay_damage_tracking.rs b/src/ifs/jay_damage_tracking.rs index 53f60429..021f512c 100644 --- a/src/ifs/jay_damage_tracking.rs +++ b/src/ifs/jay_damage_tracking.rs @@ -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, ) -> 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(()) } diff --git a/src/ifs/wl_buffer.rs b/src/ifs/wl_buffer.rs index d42aff1d..4c3f4835 100644 --- a/src/ifs/wl_buffer.rs +++ b/src/ifs/wl_buffer.rs @@ -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, pub storage: RefCell>, shm: bool, - pub color: Option, + pub color: Option<[u32; 4]>, width: i32, height: i32, pub tracker: Tracker, @@ -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]), } } diff --git a/src/it/test_gfx_api.rs b/src/it/test_gfx_api.rs index 6d5be8e7..1069fbd9 100644 --- a/src/it/test_gfx_api.rs +++ b/src/it/test_gfx_api.rs @@ -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; } diff --git a/src/it/test_ifs/test_shm_buffer.rs b/src/it/test_ifs/test_shm_buffer.rs index ffe4bdf9..7a2c7fee 100644 --- a/src/it/test_ifs/test_shm_buffer.rs +++ b/src/it/test_ifs/test_shm_buffer.rs @@ -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); diff --git a/src/it/test_ifs/test_single_pixel_buffer_manager.rs b/src/it/test_ifs/test_single_pixel_buffer_manager.rs index ed097aef..9fc989bb 100644 --- a/src/it/test_ifs/test_single_pixel_buffer_manager.rs +++ b/src/it/test_ifs/test_single_pixel_buffer_manager.rs @@ -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) diff --git a/src/it/test_utils/test_surface_ext.rs b/src/it/test_utils/test_surface_ext.rs index 4bafe68d..85b1d660 100644 --- a/src/it/test_utils/test_surface_ext.rs +++ b/src/it/test_utils/test_surface_ext.rs @@ -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)); } } diff --git a/src/it/tests/t0007_subsurface.rs b/src/it/tests/t0007_subsurface.rs index 087b564d..ea75622f 100644 --- a/src/it/tests/t0007_subsurface.rs +++ b/src/it/tests/t0007_subsurface.rs @@ -34,7 +34,7 @@ async fn test(run: Rc) -> 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)?; diff --git a/src/it/tests/t0020_surface_offset.rs b/src/it/tests/t0020_surface_offset.rs index 882c2f85..b7cc9d4a 100644 --- a/src/it/tests/t0020_surface_offset.rs +++ b/src/it/tests/t0020_surface_offset.rs @@ -25,7 +25,7 @@ async fn test(run: Rc) -> 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)?; diff --git a/src/it/tests/t0039_alpha_modifier.rs b/src/it/tests/t0039_alpha_modifier.rs index eb7f85f6..0a90bc60 100644 --- a/src/it/tests/t0039_alpha_modifier.rs +++ b/src/it/tests/t0039_alpha_modifier.rs @@ -36,11 +36,11 @@ async fn test(run: Rc) -> 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?; diff --git a/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi b/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi index bf8d4036..5c908f15 100644 Binary files a/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi and b/src/it/tests/t0039_alpha_modifier/screenshot_2.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_1.qoi b/src/it/tests/t0042_toplevel_select/screenshot_1.qoi index 05d1a952..81e7340e 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_1.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_1.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_2.qoi b/src/it/tests/t0042_toplevel_select/screenshot_2.qoi index 61f8ac62..e25c77ab 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_2.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_2.qoi differ diff --git a/src/it/tests/t0042_toplevel_select/screenshot_3.qoi b/src/it/tests/t0042_toplevel_select/screenshot_3.qoi index 61f8ac62..e25c77ab 100644 Binary files a/src/it/tests/t0042_toplevel_select/screenshot_3.qoi and b/src/it/tests/t0042_toplevel_select/screenshot_3.qoi differ diff --git a/src/portal/ptl_remote_desktop/remote_desktop_gui.rs b/src/portal/ptl_remote_desktop/remote_desktop_gui.rs index c616cb6b..0da1db0e 100644 --- a/src/portal/ptl_remote_desktop/remote_desktop_gui.rs +++ b/src/portal/ptl_remote_desktop/remote_desktop_gui.rs @@ -67,18 +67,18 @@ fn create_accept_gui(surface: &Rc) -> Rc { 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); diff --git a/src/portal/ptl_screencast/screencast_gui.rs b/src/portal/ptl_screencast/screencast_gui.rs index a4276be1..20ae0dff 100644 --- a/src/portal/ptl_screencast/screencast_gui.rs +++ b/src/portal/ptl_screencast/screencast_gui.rs @@ -87,22 +87,22 @@ fn create_accept_gui(surface: &Rc, 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); diff --git a/src/portal/ptl_text.rs b/src/portal/ptl_text.rs index 3804c101..32ecc0ed 100644 --- a/src/portal/ptl_text.rs +++ b/src/portal/ptl_text.rs @@ -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(); diff --git a/src/portal/ptr_gui.rs b/src/portal/ptr_gui.rs index 8d3630d9..458634c0 100644 --- a/src/portal/ptr_gui.rs +++ b/src/portal/ptr_gui.rs @@ -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() { diff --git a/src/renderer.rs b/src/renderer.rs index fa8a0458..9c48ba11 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -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 { diff --git a/src/text.rs b/src/text.rs index 6a0c972e..c8b2ab0c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -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(); diff --git a/src/theme.rs b/src/theme.rs index 16dd235c..72e594b7 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -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; 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; 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; 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 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) }; }