Luma color

This commit is contained in:
Laurenz 2022-05-27 16:09:43 +02:00
parent d4e59d4be1
commit 1a7ce3da02
6 changed files with 137 additions and 66 deletions

View File

@ -36,8 +36,9 @@ pub fn pdf(ctx: &Context, frames: &[Arc<Frame>]) -> Vec<u8> {
PdfExporter::new(ctx).export(frames)
}
/// Identifies the sRGB color space definition.
/// Identifies the color space definitions.
const SRGB: Name<'static> = Name(b"sRGB");
const SRGB_GRAY: Name<'static> = Name(b"sRGBGray");
/// An exporter for a whole PDF document.
struct PdfExporter<'a> {
@ -365,6 +366,11 @@ impl<'a> PdfExporter<'a> {
let mut resources = pages.resources();
resources.color_spaces().insert(SRGB).start::<ColorSpace>().srgb();
resources
.color_spaces()
.insert(SRGB_GRAY)
.start::<ColorSpace>()
.srgb_gray();
let mut fonts = resources.fonts();
for (font_ref, f) in self.face_map.pdf_indices(&self.face_refs) {
@ -437,9 +443,11 @@ struct Page {
#[derive(Debug, Default, Clone)]
struct State {
transform: Transform,
fill: Option<Paint>,
stroke: Option<Stroke>,
font: Option<(FaceId, Length)>,
fill: Option<Paint>,
fill_space: Option<Name<'static>>,
stroke: Option<Stroke>,
stroke_space: Option<Name<'static>>,
}
impl<'a> PageExporter<'a> {
@ -469,8 +477,6 @@ impl<'a> PageExporter<'a> {
tx: Length::zero(),
ty: frame.size.y,
});
self.content.set_fill_color_space(ColorSpaceOperand::Named(SRGB));
self.content.set_stroke_color_space(ColorSpaceOperand::Named(SRGB));
self.write_frame(frame);
Page {
size: frame.size,
@ -708,6 +714,7 @@ impl<'a> PageExporter<'a> {
self.font_map.insert(face_id);
let name = format_eco!("F{}", self.font_map.map(face_id));
self.content.set_font(Name(name.as_bytes()), size.to_f32());
self.state.font = Some((face_id, size));
}
}
@ -716,13 +723,26 @@ impl<'a> PageExporter<'a> {
let f = |c| c as f32 / 255.0;
let Paint::Solid(color) = fill;
match color {
Color::Luma(c) => {
self.set_fill_color_space(SRGB_GRAY);
self.content.set_fill_gray(f(c.0));
}
Color::Rgba(c) => {
self.set_fill_color_space(SRGB);
self.content.set_fill_color([f(c.r), f(c.g), f(c.b)]);
}
Color::Cmyk(c) => {
self.content.set_fill_cmyk(f(c.c), f(c.m), f(c.y), f(c.k));
}
}
self.state.fill = Some(fill);
}
}
fn set_fill_color_space(&mut self, space: Name<'static>) {
if self.state.fill_space != Some(space) {
self.content.set_fill_color_space(ColorSpaceOperand::Named(space));
self.state.fill_space = Some(space);
}
}
@ -731,7 +751,12 @@ impl<'a> PageExporter<'a> {
let f = |c| c as f32 / 255.0;
let Paint::Solid(color) = stroke.paint;
match color {
Color::Luma(c) => {
self.set_stroke_color_space(SRGB_GRAY);
self.content.set_stroke_gray(f(c.0));
}
Color::Rgba(c) => {
self.set_stroke_color_space(SRGB);
self.content.set_stroke_color([f(c.r), f(c.g), f(c.b)]);
}
Color::Cmyk(c) => {
@ -740,6 +765,14 @@ impl<'a> PageExporter<'a> {
}
self.content.set_line_width(stroke.thickness.to_f32());
self.state.stroke = Some(stroke);
}
}
fn set_stroke_color_space(&mut self, space: Name<'static>) {
if self.state.stroke_space != Some(space) {
self.content.set_stroke_color_space(ColorSpaceOperand::Named(space));
self.state.stroke_space = Some(space);
}
}
}

View File

@ -31,6 +31,8 @@ impl Debug for Paint {
/// A color in a dynamic format.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub enum Color {
/// An 8-bit luma color.
Luma(LumaColor),
/// An 8-bit RGBA color.
Rgba(RgbaColor),
/// An 8-bit CMYK color.
@ -60,6 +62,7 @@ impl Color {
/// Convert this color to RGBA.
pub fn to_rgba(self) -> RgbaColor {
match self {
Self::Luma(luma) => luma.to_rgba(),
Self::Rgba(rgba) => rgba,
Self::Cmyk(cmyk) => cmyk.to_rgba(),
}
@ -69,18 +72,48 @@ impl Color {
impl Debug for Color {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Luma(c) => Debug::fmt(c, f),
Self::Rgba(c) => Debug::fmt(c, f),
Self::Cmyk(c) => Debug::fmt(c, f),
}
}
}
impl<T> From<T> for Color
where
T: Into<RgbaColor>,
{
fn from(rgba: T) -> Self {
Self::Rgba(rgba.into())
/// An 8-bit Luma color.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct LumaColor(pub u8);
impl LumaColor {
/// Construct a new luma color.
pub const fn new(luma: u8) -> Self {
Self(luma)
}
/// Convert to an opque RGBA color.
pub const fn to_rgba(self) -> RgbaColor {
RgbaColor::new(self.0, self.0, self.0, 255)
}
/// Convert to CMYK as a fraction of true black.
pub fn to_cmyk(self) -> CmykColor {
CmykColor::new(
(self.0 as f64 * 0.75) as u8,
(self.0 as f64 * 0.68) as u8,
(self.0 as f64 * 0.67) as u8,
(self.0 as f64 * 0.90) as u8,
)
}
}
impl Debug for LumaColor {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "luma({})", self.0)
}
}
impl From<LumaColor> for Color {
fn from(luma: LumaColor) -> Self {
Self::Luma(luma)
}
}
@ -102,11 +135,6 @@ impl RgbaColor {
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
/// Construct a new, opaque gray color.
pub const fn gray(luma: u8) -> Self {
Self::new(luma, luma, luma, 255)
}
}
impl FromStr for RgbaColor {
@ -161,11 +189,7 @@ impl From<SynColor> for RgbaColor {
impl Debug for RgbaColor {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if f.alternate() {
write!(
f,
"rgba({:02}, {:02}, {:02}, {:02})",
self.r, self.g, self.b, self.a,
)?;
write!(f, "rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a,)?;
} else {
write!(f, "rgb(\"#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
if self.a != 255 {
@ -177,6 +201,15 @@ impl Debug for RgbaColor {
}
}
impl<T> From<T> for Color
where
T: Into<RgbaColor>,
{
fn from(rgba: T) -> Self {
Self::Rgba(rgba.into())
}
}
/// An 8-bit CMYK color.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct CmykColor {
@ -196,16 +229,6 @@ impl CmykColor {
Self { c, m, y, k }
}
/// Construct a new, opaque gray color as a fraction of true black.
pub fn gray(luma: u8) -> Self {
Self::new(
(luma as f64 * 0.75) as u8,
(luma as f64 * 0.68) as u8,
(luma as f64 * 0.67) as u8,
(luma as f64 * 0.90) as u8,
)
}
/// Convert this color to RGBA.
pub fn to_rgba(self) -> RgbaColor {
let k = self.k as f32 / 255.0;

View File

@ -85,6 +85,7 @@ pub fn new() -> Scope {
std.def_fn("odd", utility::odd);
std.def_fn("mod", utility::mod_);
std.def_fn("range", utility::range);
std.def_fn("luma", utility::luma);
std.def_fn("rgb", utility::rgb);
std.def_fn("cmyk", utility::cmyk);
std.def_fn("repr", utility::repr);

View File

@ -2,57 +2,65 @@ use std::str::FromStr;
use crate::library::prelude::*;
/// Create a grayscale color.
pub fn luma(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
let Component(luma) = args.expect("gray component")?;
Ok(Value::Color(LumaColor::new(luma).into()))
}
/// Create an RGB(A) color.
pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
Ok(Value::from(
Ok(Value::Color(
if let Some(string) = args.find::<Spanned<EcoString>>()? {
match RgbaColor::from_str(&string.v) {
Ok(color) => color,
Ok(color) => color.into(),
Err(msg) => bail!(string.span, msg),
}
} else {
struct Component(u8);
castable! {
Component,
Expected: "integer or ratio",
Value::Int(v) => match v {
0 ..= 255 => Self(v as u8),
_ => Err("must be between 0 and 255")?,
},
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
Self((v.get() * 255.0).round() as u8)
} else {
Err("must be between 0% and 100%")?
},
}
let Component(r) = args.expect("red component")?;
let Component(g) = args.expect("green component")?;
let Component(b) = args.expect("blue component")?;
let Component(a) = args.eat()?.unwrap_or(Component(255));
RgbaColor::new(r, g, b, a)
RgbaColor::new(r, g, b, a).into()
},
))
}
/// Create a CMYK color.
pub fn cmyk(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
struct Component(u8);
castable! {
Component,
Expected: "ratio",
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
Self((v.get() * 255.0).round() as u8)
} else {
Err("must be between 0% and 100%")?
},
}
let Component(c) = args.expect("cyan component")?;
let Component(m) = args.expect("magenta component")?;
let Component(y) = args.expect("yellow component")?;
let Component(k) = args.expect("key component")?;
let RatioComponent(c) = args.expect("cyan component")?;
let RatioComponent(m) = args.expect("magenta component")?;
let RatioComponent(y) = args.expect("yellow component")?;
let RatioComponent(k) = args.expect("key component")?;
Ok(Value::Color(CmykColor::new(c, m, y, k).into()))
}
/// An integer or ratio component.
struct Component(u8);
castable! {
Component,
Expected: "integer or ratio",
Value::Int(v) => match v {
0 ..= 255 => Self(v as u8),
_ => Err("must be between 0 and 255")?,
},
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
Self((v.get() * 255.0).round() as u8)
} else {
Err("must be between 0% and 100%")?
},
}
/// A component that must be a ratio.
struct RatioComponent(u8);
castable! {
RatioComponent,
Expected: "ratio",
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
Self((v.get() * 255.0).round() as u8)
} else {
Err("must be between 0% and 100%")?
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 271 B

View File

@ -8,6 +8,12 @@
// Alpha channel.
#test(rgb(255, 0, 0, 50%), rgb("ff000080"))
---
// Test gray color conversion.
// Ref: true
#rect(fill: luma(0))
#rect(fill: luma(80%))
---
// Test CMYK color conversion.
// Ref: true