Skip to content

Instantly share code, notes, and snippets.

@Miha-x64
Last active April 18, 2019 08:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Miha-x64/4397d092515fe743ee2d4f6ffa8e66dd to your computer and use it in GitHub Desktop.
Save Miha-x64/4397d092515fe743ee2d4f6ffa8e66dd to your computer and use it in GitHub Desktop.
Rust script for extracting a transparent colour from the given pre-blent opaque colours
use std::env;
use std::io;
use std::io::Write;
use std::str::FromStr;
#[macro_use]
extern crate lazy_static;
fn main() {
match obtain_colour_args() {
Some((bg, fg, formats)) => {
let unblent = unblend(&bg, &fg);
match single_ref(&formats) {
Some(format) => println!("{}", (format.format)(&unblent)),
None => {
for format in formats.iter() {
println!("{}: {}", format.name, (format.format)(&unblent));
}
}
}
},
None => println!(
"Usage: unblend [output format[, output format[...]]] background foreground\n\
\twhere 'background' and 'foreground' are #RRGGBB colours\n\
output formats:\n\
\t--android: #AARRGGBB\n\
\t--java: 0xAA_RRGGBB\n\
\t--html: rgba(red/255, green/255, blue/255, alpha/1.0)"
)
}
}
#[derive(PartialEq, Eq)]
struct OpaqueColour {
red: u8,
green: u8,
blue: u8,
}
impl FromStr for OpaqueColour {
type Err = ();
fn from_str(hex: &str) -> Result<Self, Self::Err> {
let hex = hex.trim();
let hex = if hex.starts_with("#") { &hex[1..] } else { &hex };
if hex.len() == 6 {
let r = from_hex(hex[0..2].as_bytes()).ok_or(())?;
let g = from_hex(hex[2..4].as_bytes()).ok_or(())?;
let b = from_hex(hex[4..6].as_bytes()).ok_or(())?;
Ok(OpaqueColour { red: r, green: g, blue: b })
} else {
Err(())
}
}
}
struct TransparentColour {
alpha: u8,
red: u8,
green: u8,
blue: u8,
}
struct ColourFormat {
name: &'static str,
slug: &'static str,
format: fn(source: &TransparentColour) -> String,
}
const ALL_FORMATS: &[&ColourFormat] = &[
&ColourFormat {
name: "Android ColorRes",
slug: "android",
format: |source| {
format!("#{:02X}{:02X}{:02X}{:02X}", source.alpha, source.red, source.green, source.blue)
},
},
&ColourFormat {
name: "Java HEX literal",
slug: "java",
format: |source| {
format!("0x{:02X}_{:02X}{:02X}{:02X}", source.alpha, source.red, source.green, source.blue)
},
},
&ColourFormat {
name: "HTML/CSS",
slug: "html",
format: |source| {
format!("rgba({}, {}, {}, {:.3})",
source.red, source.green, source.blue, source.alpha as f32 / 255.0)
},
},
];
fn obtain_colour_args() -> Option<(OpaqueColour, OpaqueColour, Vec<&'static ColourFormat>)> {
let mut args: Vec<String> = env::args().collect();
let mut formats: Vec<&ColourFormat> = Vec::new();
args.remove(0); // pop first useless arg
loop {
if has_option(&args) {
let slug = &args.remove(0)[2..];
let format = ALL_FORMATS.iter()
.find(|format| { format.slug == slug })?;
formats.push(format)
} else {
break;
}
}
if formats.is_empty() {
formats = ALL_FORMATS.into();
}
match args.len() {
0 => { // interactive
let mut buf = String::new();
let bg = read_colour_interactive(
"Background colour (#RRGGBB):", &mut buf)?;
let fg = read_colour_interactive(
"Foreground colour:", &mut buf)?;
Some((bg, fg, formats))
},
2 => { // CLI arguments
let bg: OpaqueColour = args.remove(0).parse().ok()?;
let fg: OpaqueColour = args.remove(0).parse().ok()?;
Some((bg, fg, formats))
},
_ => None
}
}
fn has_option(vec: &Vec<String>) -> bool {
if let Some(arg) = vec.first() {
if arg.starts_with("--") {
true
} else {
false
}
} else {
false
}
}
fn read_colour_interactive(prompt: &str, io_buf: &mut String) -> Option<OpaqueColour> {
println!("{}", prompt);
io::stdout().flush().unwrap();
io::stdin().read_line(io_buf).unwrap();
let colour_result = io_buf.parse();
io_buf.clear();
colour_result.ok()
}
fn unblend(bg: &OpaqueColour, fg: &OpaqueColour) -> TransparentColour {
if bg == fg {
return TransparentColour { alpha: 0, /*nevermind:*/ red: 0, green: 0, blue: 0 };
}
for i in 1 .. 256 {
let alpha_guess = i as u8;
let r = colour_component(bg.red, fg.red, alpha_guess);
if r > 255 { continue; }
let g = colour_component(bg.green, fg.green, alpha_guess);
if g > 255 { continue; }
let b = colour_component(bg.blue, fg.blue, alpha_guess);
if b > 255 { continue; }
return TransparentColour { alpha: alpha_guess, red: r as u8, green: g as u8, blue: b as u8 };
}
unreachable!();
}
fn colour_component(bg: u8, fg: u8, alpha: u8) -> u16 {
// fg = (1-α)*bg + α*x
// fg = bg - α*bg + α*x
// α*x = -bg + α*bg + fg
// x = (-bg + α*bg + fg) / α
let bg = bg as i32;
let fg = fg as i32;
let alpha = alpha as i32;
((-bg + alpha*bg/255 + fg)*255/alpha) as u16
}
fn from_hex(hex: &[u8]) -> Option<u8> {
lazy_static! {
static ref VALUES: [u8; 'f' as usize + 1] = {
let mut v = [0xFF; 'f' as usize + 1];
(&mut v['0' as usize ..= '9' as usize]).write(&[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]).unwrap();
(&mut v['A' as usize ..= 'F' as usize]).write(&[0xA, 0xB, 0xC, 0xD, 0xE, 0xF]).unwrap();
(&mut v['a' as usize ..= 'f' as usize]).write(&[0xA, 0xB, 0xC, 0xD, 0xE, 0xF]).unwrap();
v
};
}
if hex.len() == 2 {
let hi_char = hex[0] as usize;
let lo_char = hex[1] as usize;
if hi_char < VALUES.len() && lo_char < VALUES.len() {
let hi = VALUES[hi_char];
let lo = VALUES[lo_char];
if hi == 0xFF || lo == 0xFF { // 0xFF stands for 'invalid hex'
None
} else {
Some((hi << 4) | lo)
}
} else {
None
}
} else {
None
}
}
fn single_ref<'a, T : ?Sized>(vec: &'a Vec<&T>) -> Option<&'a T> {
vec.first().map(|x: &&T| { *x })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse() {
assert_eq!(from_hex(b""), None);
assert_eq!(from_hex(b"0"), None);
assert_eq!(from_hex(b"00"), Some(0));
assert_eq!(from_hex(b"01"), Some(1));
assert_eq!(from_hex(b"10"), Some(0x10));
assert_eq!(from_hex(b"1f"), Some(0x1f));
assert_eq!(from_hex(b"bE"), Some(0xBE));
assert_eq!(from_hex(b"Da"), Some(0xDA));
}
}
[package]
name = "unblend"
version = "0.0.1"
authors = ["miha-x64"]
[dependencies]
lazy_static = "1.3.0"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment