Skip to content

Commit

Permalink
feat: add signed & unsigned integer colour repr (#27)
Browse files Browse the repository at this point in the history
* feat: add signed & unsigned integer colour repr

* feat: add 24-bit int repr and tests

* docs: add int24/uint32/sint32 to color table
  • Loading branch information
backwardspy authored Sep 8, 2024
1 parent ee6471b commit 6d2f354
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 11 deletions.
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,16 +142,19 @@ These types are designed to closely match the [palette.json](https://github.com/

##### Color

| Field | Type | Description | Examples |
| ------------ | -------- | --------------------------------------- | -------------------------------------- |
| `name` | `String` | The name of the color. | `"Rosewater"`, `"Surface 0"`, `"Base"` |
| `identifier` | `String` | The identifier of the color. | `"rosewater"`, `"surface0"`, `"base"` |
| `order` | `u32` | Order of the color in the palette spec. | `0` to `25` |
| `accent` | `bool` | Whether the color is an accent color. | |
| `hex` | `String` | The color in hexadecimal format. | `"1e1e2e"` |
| `rgb` | `RGB` | The color in RGB format. | |
| `hsl` | `HSL` | The color in HSL format. | |
| `opacity` | `u8` | The opacity of the color. | `0` to `255` |
| Field | Type | Description | Examples |
| ------------ | -------- | ----------------------------------------------- | -------------------------------------- |
| `name` | `String` | The name of the color. | `"Rosewater"`, `"Surface 0"`, `"Base"` |
| `identifier` | `String` | The identifier of the color. | `"rosewater"`, `"surface0"`, `"base"` |
| `order` | `u32` | Order of the color in the palette spec. | `0` to `25` |
| `accent` | `bool` | Whether the color is an accent color. | |
| `hex` | `String` | The color in hexadecimal format. | `"1e1e2e"` |
| `int24` | `u32` | Big-endian 24-bit color in RGB order. | `1973806` |
| `uint32` | `u32` | Big-endian unsigned 32-bit color in ARGB order. | `4280163886` |
| `sint32` | `i32` | Big-endian signed 32-bit color in ARGB order. | `-14803410` |
| `rgb` | `RGB` | The color in RGB format. | |
| `hsl` | `HSL` | The color in HSL format. | |
| `opacity` | `u8` | The opacity of the color. | `0` to `255` |

##### RGB

Expand Down
54 changes: 54 additions & 0 deletions src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub struct Color {
pub order: u32,
pub accent: bool,
pub hex: String,
pub int24: u32,
pub uint32: u32,
pub sint32: i32,
pub rgb: RGB,
pub hsl: HSL,
pub opacity: u8,
Expand Down Expand Up @@ -101,6 +104,18 @@ fn format_hex(r: u8, g: u8, b: u8, a: u8, hex_format: &str) -> tera::Result<Stri
)
}

/// produce three values from a given rgb value and opacity:
/// 1. a 24-bit unsigned integer with the format `0xRRGGBB`
/// 2. a 32-bit unsigned integer with the format `0xAARRGGBB`
/// 3. a 32-bit signed integer with the format `0xAARRGGBB`
/// opacity is optional, and defaults to `0xFF`.
fn rgb_to_ints(rgb: &RGB, opacity: Option<u8>) -> (u32, u32, i32) {
let opacity = opacity.unwrap_or(0xFF);
let uint24 = u32::from_be_bytes([0x00, rgb.r, rgb.g, rgb.b]);
let uint32 = u32::from_be_bytes([opacity, rgb.r, rgb.g, rgb.b]);
(uint24, uint32, uint32 as i32)
}

fn color_from_hex_override(hex: &str, blueprint: &catppuccin::Color) -> Result<Color, Error> {
let i = u32::from_str_radix(hex, 16)?;
let rgb = RGB {
Expand All @@ -110,12 +125,16 @@ fn color_from_hex_override(hex: &str, blueprint: &catppuccin::Color) -> Result<C
};
let hsl = css_colors::rgb(rgb.r, rgb.g, rgb.b).to_hsl();
let hex = format_hex!(rgb.r, rgb.g, rgb.b, 0xFF)?;
let (int24, uint32, sint32) = rgb_to_ints(&rgb, None);
Ok(Color {
name: blueprint.name.to_string(),
identifier: blueprint.name.identifier().to_string(),
order: blueprint.order,
accent: blueprint.accent,
hex,
int24,
uint32,
sint32,
rgb,
hsl: HSL {
h: hsl.h.degrees(),
Expand All @@ -128,12 +147,17 @@ fn color_from_hex_override(hex: &str, blueprint: &catppuccin::Color) -> Result<C

fn color_from_catppuccin(color: &catppuccin::Color) -> tera::Result<Color> {
let hex = format_hex!(color.rgb.r, color.rgb.g, color.rgb.b, 0xFF)?;
let rgb: RGB = color.rgb.into();
let (int24, uint32, sint32) = rgb_to_ints(&rgb, None);
Ok(Color {
name: color.name.to_string(),
identifier: color.name.identifier().to_string(),
order: color.order,
accent: color.accent,
hex,
int24,
uint32,
sint32,
rgb: RGB {
r: color.rgb.r,
g: color.rgb.g,
Expand Down Expand Up @@ -253,12 +277,16 @@ impl Color {
l: hsla.l.as_f32(),
};
let opacity = hsla.a.as_u8();
let (int24, uint32, sint32) = rgb_to_ints(&rgb, Some(opacity));
Ok(Self {
name: blueprint.name.clone(),
identifier: blueprint.identifier.clone(),
order: blueprint.order,
accent: blueprint.accent,
hex: rgb_to_hex(&rgb, opacity)?,
int24,
uint32,
sint32,
rgb,
hsl,
opacity,
Expand All @@ -278,12 +306,16 @@ impl Color {
l: hsl.l.as_f32(),
};
let opacity = rgba.a.as_u8();
let (int24, uint32, sint32) = rgb_to_ints(&rgb, Some(opacity));
Ok(Self {
name: blueprint.name.clone(),
identifier: blueprint.identifier.clone(),
order: blueprint.order,
accent: blueprint.accent,
hex: rgb_to_hex(&rgb, opacity)?,
int24,
uint32,
sint32,
rgb,
hsl,
opacity,
Expand Down Expand Up @@ -356,29 +388,41 @@ impl Color {

pub fn mod_opacity(&self, opacity: f32) -> tera::Result<Self> {
let opacity = (opacity * 255.0).round() as u8;
let (int24, uint32, sint32) = rgb_to_ints(&self.rgb, Some(opacity));
Ok(Self {
opacity,
hex: rgb_to_hex(&self.rgb, opacity)?,
int24,
uint32,
sint32,
..self.clone()
})
}

pub fn add_opacity(&self, opacity: f32) -> tera::Result<Self> {
let opacity = (opacity * 255.0).round() as u8;
let opacity = self.opacity.saturating_add(opacity);
let (int24, uint32, sint32) = rgb_to_ints(&self.rgb, Some(opacity));
Ok(Self {
opacity,
hex: rgb_to_hex(&self.rgb, opacity)?,
int24,
uint32,
sint32,
..self.clone()
})
}

pub fn sub_opacity(&self, opacity: f32) -> tera::Result<Self> {
let opacity = (opacity * 255.0).round() as u8;
let opacity = self.opacity.saturating_sub(opacity);
let (int24, uint32, sint32) = rgb_to_ints(&self.rgb, Some(opacity));
Ok(Self {
opacity,
hex: rgb_to_hex(&self.rgb, opacity)?,
int24,
uint32,
sint32,
..self.clone()
})
}
Expand Down Expand Up @@ -425,3 +469,13 @@ impl From<&Color> for css_colors::HSLA {
}
}
}

impl From<catppuccin::Rgb> for RGB {
fn from(rgb: catppuccin::Rgb) -> Self {
Self {
r: rgb.r,
g: rgb.g,
b: rgb.b,
}
}
}
16 changes: 15 additions & 1 deletion tests/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(test)]
mod happy_path {
use assert_cmd::Command;
use predicates::prelude::predicate;
use predicates::prelude::{predicate, PredicateBooleanExt};

/// Test that the CLI can render a single-flavor template file
#[test]
Expand Down Expand Up @@ -49,6 +49,20 @@ mod happy_path {
.stdout(include_str!("fixtures/read_file/read_file.md"));
}

/// Test that the CLI can render colours in specific formats
#[test]
fn test_formats() {
let mut cmd = Command::cargo_bin("whiskers").expect("binary exists");
let assert = cmd
.args(["tests/fixtures/formats.tera", "-f", "latte"])
.assert();
assert.success().stdout(
predicate::str::contains("24-bit red: 13766457")
.and(predicate::str::contains("unsigned 32-bit red: 4291956537"))
.and(predicate::str::contains("signed 32-bit red: -3010759")),
);
}

/// Test that the CLI can render a UTF-8 template file
#[test]
fn test_utf8() {
Expand Down
7 changes: 7 additions & 0 deletions tests/fixtures/formats.tera
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
whiskers:
version: "2.0.0"
---
24-bit red: {{red.int24}}
unsigned 32-bit red: {{red.uint32}}
signed 32-bit red: {{red.sint32}}

0 comments on commit 6d2f354

Please sign in to comment.