Skip to main content

Tint

Enum Tint 

Source
#[non_exhaustive]
pub enum Tint { None, Multiply { r: u8, g: u8, b: u8, }, Mix { r: u8, g: u8, b: u8, amount: u8, }, }
Expand description

How a sprite’s own pixels are recoloured at draw time.

A sprite is composited from the artwork’s pixels, and a cell’s Style::fg does not touch it (see Surface::put_span). A tint is the separate channel that does, so one piece of artwork can serve a biome variant, a damage flash, or a shadowed copy of itself without a second sprite in the sheet.

Pixel backends only. Cell backends have no sprite to recolour and ignore a tint entirely; they draw the cell’s glyph in its own Style, as always.

§Why not reuse fg

Tinting a sprite by the cell’s foreground colour is what most tileset libraries do, and it works for them because their foreground colour has exactly one job and defaults to white, the identity of a multiply.

Neither holds here. Color::Default means “whatever foreground the terminal is configured for”, not white, so it has no sensible reading as a modulation value. More importantly, a cell drawn as a sprite by a pixel backend is drawn as an fg-coloured glyph by a cell backend, and the colour that reads correctly as a solid character is not the colour that reads correctly multiplied onto artwork that already has colour of its own. One field cannot serve both.

§Choosing an operation

Multiply is the workhorse and can only darken: every channel scales toward zero. It preserves the artwork’s own shading, which is what makes it right for variants of one material (grass to savanna, stone to mossy stone) and for lighting.

Mix blends toward a colour and can therefore brighten, which multiply cannot express at all. It is also the only one of the two a caller could not approximate for themselves, since doing so needs the sprite’s pixels. Mix at full strength replaces the artwork’s colour outright while keeping its alpha, which is how a white-on-transparent mask sheet gets recoloured.

Alpha is never touched by either: a tint changes what the sprite’s opaque pixels look like, never which of them are opaque. Compositing and the cell background showing through transparent pixels behave identically tinted or not.

§Scope: what Tint is not for

Tint is per-cell and per-draw, not per-sheet or per-frame. “Is this sheet art or a mask” is a different, fixed-at-load-time question, answered once by retroglyph_window::tileset::SheetColor rather than by this type. The two compose instead of collapsing into one flag (see retroglyph_window::sprite_cache::SpriteTint, which resolves both in one place), because “is this sheet art or a mask” (fixed when the asset is authored) and “what colour to flash this cell right now” (fixed per frame) are different questions that would conflict if merged into a single modulate(bool)-style flag: a sheet declared art-not-mask still needs to be flashable.

Frame- or layer-level colour transforms (day/night cycles, fog of war, a “remembered” map render) are not a use case for Tint either. Those apply to everything already drawn, every frame, so routing them through per-cell Tint would mean writing the same value into a side-table entry for every cell of every layer, every frame: the wrong lever for a screen-wide effect. That is tracked as its own, not-yet-designed concern in retroglyph#562; it is out of scope here.

Tint is #[non_exhaustive] so more operations (add, screen, replace) can be added later without breaking either backend: the GL encoder already falls through to “no recolour” on an operation it does not recognize.

§Examples

use retroglyph_core::color::Tint;

// Grass artwork, dimmed toward its own shadow.
let shadowed = Tint::multiply(128, 128, 128);
assert_eq!(shadowed.apply((200, 180, 60)), (100, 90, 30));

// The same pixels, flashed most of the way to white.
let hit = Tint::mix(255, 255, 255, 192);
assert_eq!(hit.apply((200, 180, 60)), (241, 236, 207));

// The default costs nothing and changes nothing.
assert_eq!(Tint::None.apply((200, 180, 60)), (200, 180, 60));

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

None

Composite the sprite’s pixels verbatim.

§

Multiply

Scale each channel by rgb / 255, darkening toward black.

(255, 255, 255) is the identity and behaves as None, just less cheaply.

Fields

§r: u8

Red scale factor.

§g: u8

Green scale factor.

§b: u8

Blue scale factor.

§

Mix

Blend each channel amount / 255 of the way toward rgb.

amount of 0 is the identity; 255 replaces the sprite’s colour outright, keeping its alpha.

Fields

§r: u8

Red channel of the colour blended toward.

§g: u8

Green channel of the colour blended toward.

§b: u8

Blue channel of the colour blended toward.

§amount: u8

How far to blend, from 0 (unchanged) to 255 (fully replaced).

Implementations§

Source§

impl Tint

Source

pub const fn multiply(r: u8, g: u8, b: u8) -> Self

A Multiply tint scaling each channel by rgb / 255.

Source

pub const fn mix(r: u8, g: u8, b: u8, amount: u8) -> Self

A Mix tint blending amount / 255 of the way toward rgb.

Source

pub const fn is_identity(self) -> bool

Whether this tint leaves every pixel exactly as authored.

True for None and for the identity of either operation, so a renderer can take its untinted fast path for a tint that would do nothing.

Source

pub const fn apply(self, rgb: (u8, u8, u8)) -> (u8, u8, u8)

Applies this tint to one straight-alpha RGB triple, returning the recoloured channels.

The reference implementation of the operation. Both pixel backends produce their output from this, the software renderer by calling it per pixel and the GL renderer by matching its arithmetic in the sprite fragment shader, so that a sprite tinted on one backend matches the same sprite tinted on the other.

Alpha is not an input and not an output: a tint never changes which pixels are opaque.

Source

pub const fn apply_rgb888(self, px: Rgb888) -> Rgb888

Applies this tint to an [Rgb888], the same operation as apply but without the channel-order round trip through a bare (u8, u8, u8) tuple.

const because [Rgb888::to_rgb][gem::rgb::Rgb::to_rgb] is a const inherent method (gem 0.2.0): the equivalent by way of the HasRed-family traits cannot be, since trait methods aren’t const-callable on stable.

use retroglyph_core::color::Tint;
use gem::rgb::Rgb888;

const PX: Rgb888 = Tint::Multiply { r: 128, g: 128, b: 128 }
    .apply_rgb888(Rgb888::from_rgb(200, 180, 60));
assert_eq!(PX, Rgb888::from_rgb(100, 90, 30));
Source

pub const fn multiply_color(c: Color, default: (u8, u8, u8)) -> Self

A Multiply tint by color’s resolved RGB, falling back to default for Color::Default (which has no intrinsic reading as a modulation value). Built for retroglyph-window’s sheet-level recolouring: a SheetColor::Mask sheet is tinted by the cell’s own foreground colour this way before the cell’s own Tint is applied on top.

Trait Implementations§

Source§

impl Clone for Tint

Source§

fn clone(&self) -> Tint

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Tint

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Tint

Source§

fn default() -> Tint

Returns the “default value” for a type. Read more
Source§

impl Hash for Tint

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for Tint

Source§

fn eq(&self, other: &Tint) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for Tint

Source§

impl Eq for Tint

Source§

impl StructuralPartialEq for Tint

Auto Trait Implementations§

§

impl Freeze for Tint

§

impl RefUnwindSafe for Tint

§

impl Send for Tint

§

impl Sync for Tint

§

impl Unpin for Tint

§

impl UnsafeUnpin for Tint

§

impl UnwindSafe for Tint

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<C> WithAlpha for C
where C: Copy,

§

fn with_alpha_first<A>(self, alpha: A) -> AlphaFirst<A, Self>

Wraps self with alpha, storing alpha before the color in memory (see [AlphaFirst]).
§

fn with_alpha_last<A>(self, alpha: A) -> AlphaLast<A, Self>

Wraps self with alpha, storing alpha after the color in memory (see [AlphaLast]).