mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
feat(codecs): support full X11 rgb.txt color names in XPM (#4263)
* feat(codecs): import XPM3 images with transparency (#3692) * Correct XPM test expectation for green palette name * feat(codecs): resolve full X11 rgb.txt color names in XPM * XPM: fix Format::ALL length after DPX, reject '+' in hex colours, fix non-ASCII byte-string test, fmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Brandon Thomas
parent
9970cf7214
commit
5981a02e64
@@ -10,6 +10,7 @@ next to them.
|
||||
|
||||
| Path | Title | Author | Source | License |
|
||||
|---|---|---|---|---|
|
||||
| `crates/codecs/src/codecs/xpm/x11_colors.rs` | X11 `rgb.txt` color-name lookup table (normalized X.Org RGB values) | X.Org Foundation and X11 contributors | <https://cgit.freedesktop.org/xorg/app/rgb/tree/rgb.txt> | MIT (X.Org app/rgb) |
|
||||
| `crates/ui-egui/src/i18n/el.tsv` | Greek UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) |
|
||||
| `crates/ui-egui/src/i18n/de.tsv` | German UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) |
|
||||
| `crates/ui-egui/src/i18n/it.tsv` | Italian UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) |
|
||||
|
||||
@@ -21,8 +21,8 @@ mod recovery_tests;
|
||||
/// (.kys), which go to the preset libraries and Edit › Keyboard Shortcuts.
|
||||
const OPEN_EXTS: &[&str] = &[
|
||||
"pcraft", "pdn", "ora", "kra", "psd", "psb", "psdt", "png", "jpg", "jpeg", "tif", "tiff", "webp", "gif", "bmp", "tga", "ico", "qoi", "exr", "hdr", "pbm",
|
||||
"pgm", "ppm", "pam", "pfm", "pcx", "fits", "fit", "fts", "dpx", "heic", "heif", "hif", "dng", "cr2", "cr3", "nef", "nrw", "arw", "pef", "orf", "rw2",
|
||||
"raf", "abr", "grd", "svg", "svgz", "aco", "ase", "kys", "af", "afdesign", "afphoto", "afpub",
|
||||
"pgm", "ppm", "pam", "pfm", "pcx", "xpm", "fits", "fit", "fts", "dpx", "heic", "heif", "hif", "dng", "cr2", "cr3", "nef", "nrw", "arw", "pef", "orf",
|
||||
"rw2", "raf", "abr", "grd", "svg", "svgz", "aco", "ase", "kys", "af", "afdesign", "afphoto", "afpub",
|
||||
];
|
||||
const SVG_EXTS: &[&str] = &["svg", "svgz"];
|
||||
|
||||
|
||||
@@ -14,3 +14,4 @@ pub(crate) mod tiff_ifd;
|
||||
pub(crate) mod via_image;
|
||||
pub(crate) mod vp8;
|
||||
pub(crate) mod webp;
|
||||
pub(crate) mod xpm;
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
//! XPM3 reader for C-source icon arrays. Supports transparency (None), RGB hex
|
||||
//! at 4/8/16 bits per channel, and common X11 colour names. Does not run C code.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod x11_colors;
|
||||
|
||||
use crate::SampleType;
|
||||
use crate::error::CodecError;
|
||||
use crate::format::Format;
|
||||
use crate::image::{ChannelLayout, Image};
|
||||
use crate::options::Limits;
|
||||
|
||||
const MAX_SOURCE: usize = 256 << 20;
|
||||
const MAX_COLOURS: usize = 65_536;
|
||||
const MAX_CPP: usize = 8;
|
||||
|
||||
fn bad(msg: impl std::fmt::Display) -> CodecError {
|
||||
CodecError::malformed(Format::Xpm, msg)
|
||||
}
|
||||
|
||||
/// Extract C string literals without treating quoted text or // in a string as a comment.
|
||||
/// XPM uses one literal for the header, each palette entry and each row.
|
||||
fn c_strings(bytes: &[u8]) -> Result<Vec<Vec<u8>>, CodecError> {
|
||||
let mut lines = Vec::new();
|
||||
let mut i = 0usize;
|
||||
while i < bytes.len() {
|
||||
if bytes[i..].starts_with(b"/*") {
|
||||
let tail = &bytes[i + 2..];
|
||||
let Some(end) = tail.windows(2).position(|p| p == b"*/") else { return Err(bad("unterminated C comment")) };
|
||||
i += end + 4;
|
||||
} else if bytes[i..].starts_with(b"//") {
|
||||
i += bytes[i..].iter().position(|&c| c == b'\n').unwrap_or(bytes.len() - i);
|
||||
} else if bytes[i] == b'"' {
|
||||
i += 1;
|
||||
let mut s = Vec::new();
|
||||
let mut closed = false;
|
||||
while i < bytes.len() {
|
||||
let c = bytes[i];
|
||||
i += 1;
|
||||
match c {
|
||||
b'"' => {
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
b'\\' => {
|
||||
let next = *bytes.get(i).ok_or_else(|| bad("truncated C escape"))?;
|
||||
i += 1;
|
||||
s.push(match next {
|
||||
b'n' => b'\n',
|
||||
b'r' => b'\r',
|
||||
b't' => b'\t',
|
||||
b'0' => 0,
|
||||
_ => next, // quoted backslash or quote
|
||||
});
|
||||
}
|
||||
_ => s.push(c),
|
||||
}
|
||||
}
|
||||
if !closed {
|
||||
return Err(bad("unterminated XPM string"));
|
||||
}
|
||||
lines.push(s);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
Ok(lines)
|
||||
}
|
||||
|
||||
fn channel(hex: &str) -> Option<u8> {
|
||||
let v = u32::from_str_radix(hex, 16).ok()?;
|
||||
let max = (1u32 << (hex.len() * 4)) - 1;
|
||||
Some(((v * 255 + max / 2) / max) as u8)
|
||||
}
|
||||
|
||||
fn named(name: &str) -> Option<[u8; 3]> {
|
||||
let s = name.to_ascii_lowercase().replace([' ', '-', '_'], "");
|
||||
// XPM imports X11 names, whose RGB values differ from CSS for names
|
||||
// such as gray, maroon and purple. Keep these exact rather than guessing.
|
||||
if let Ok(index) = x11_colors::X11_COLORS.binary_search_by_key(&s.as_str(), |(name, _)| *name) {
|
||||
return Some(x11_colors::X11_COLORS[index].1);
|
||||
}
|
||||
// Retain useful web color aliases not present in the X11 database.
|
||||
Some(match s.as_str() {
|
||||
"silver" => [192, 192, 192],
|
||||
"olive" => [128, 128, 0],
|
||||
"teal" => [0, 128, 128],
|
||||
"fuchsia" => [255, 0, 255],
|
||||
"navyblue" => [0, 0, 128],
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn color(name: &str) -> Option<[u8; 4]> {
|
||||
if name.eq_ignore_ascii_case("none") {
|
||||
return Some([0, 0, 0, 0]);
|
||||
}
|
||||
let rgb = if let Some(hex) = name.strip_prefix('#') {
|
||||
// UTF-8 byte lengths can match a supported hex width while slicing would panic.
|
||||
// `from_str_radix` would also take a leading `+`: accept hex digits only.
|
||||
if !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
|
||||
return None;
|
||||
}
|
||||
let digits = match hex.len() {
|
||||
3 => 1,
|
||||
6 => 2,
|
||||
12 => 4,
|
||||
_ => return None,
|
||||
};
|
||||
let rgb = (0..3).map(|i| channel(&hex[i * digits..(i + 1) * digits])).collect::<Option<Vec<_>>>()?;
|
||||
[rgb[0], rgb[1], rgb[2]]
|
||||
} else {
|
||||
named(name)?
|
||||
};
|
||||
Some([rgb[0], rgb[1], rgb[2], 255])
|
||||
}
|
||||
|
||||
pub(crate) fn decode(bytes: &[u8], limits: &Limits) -> Result<Image, CodecError> {
|
||||
if !bytes.starts_with(b"/* XPM */") {
|
||||
return Err(bad("not an XPM3 image"));
|
||||
}
|
||||
if bytes.len() > MAX_SOURCE {
|
||||
return Err(CodecError::LimitExceeded("XPM source exceeds 256 MiB".into()));
|
||||
}
|
||||
let lines = c_strings(bytes)?;
|
||||
let header = lines.first().ok_or_else(|| bad("missing image header"))?;
|
||||
let parts = std::str::from_utf8(header).map_err(|_| bad("non-UTF-8 XPM header"))?;
|
||||
let mut parts = parts.split_ascii_whitespace();
|
||||
let number = |key: &str, s: Option<&str>| -> Result<usize, CodecError> {
|
||||
s.ok_or_else(|| bad(format!("missing {key}")))?.parse::<usize>().map_err(|_| bad(format!("invalid {key}")))
|
||||
};
|
||||
let width = number("width", parts.next())?;
|
||||
let height = number("height", parts.next())?;
|
||||
let colours = number("palette size", parts.next())?;
|
||||
let cpp = number("chars per pixel", parts.next())?;
|
||||
if width == 0 || height == 0 || colours == 0 || colours > MAX_COLOURS || !(1..=MAX_CPP).contains(&cpp) {
|
||||
return Err(bad("invalid XPM dimensions, palette size or chars per pixel"));
|
||||
}
|
||||
let (w, h) = (u32::try_from(width).map_err(|_| bad("width overflow"))?, u32::try_from(height).map_err(|_| bad("height overflow"))?);
|
||||
limits.check(w, h, ChannelLayout::Rgba, SampleType::U8)?;
|
||||
let required = 1usize.checked_add(colours).and_then(|n| n.checked_add(height)).ok_or_else(|| bad("row count overflow"))?;
|
||||
if lines.len() < required {
|
||||
return Err(bad("truncated palette or image rows"));
|
||||
}
|
||||
let mut palette = HashMap::<Vec<u8>, [u8; 4]>::with_capacity(colours);
|
||||
for line in &lines[1..1 + colours] {
|
||||
if line.len() < cpp {
|
||||
return Err(bad("truncated colour key"));
|
||||
}
|
||||
let fields = std::str::from_utf8(&line[cpp..]).map_err(|_| bad("invalid colour name"))?;
|
||||
// XPM priority is colour, gray, four-level gray, then monochrome,
|
||||
// regardless of the order of keys in the palette entry.
|
||||
let terms: Vec<&str> = fields.split_ascii_whitespace().collect();
|
||||
let mut picked: Option<(u8, String)> = None;
|
||||
let mut i = 0;
|
||||
while i < terms.len() {
|
||||
let priority = match terms[i] {
|
||||
"c" => 4,
|
||||
"g" => 3,
|
||||
"g4" => 2,
|
||||
"m" => 1,
|
||||
_ => 0, // Symbolic names ('s') are not colour values.
|
||||
};
|
||||
let start = i + 1;
|
||||
i = start;
|
||||
while i < terms.len() && !matches!(terms[i], "c" | "g" | "g4" | "m" | "s") {
|
||||
i += 1;
|
||||
}
|
||||
if priority > 0 && i > start && picked.as_ref().is_none_or(|(current, _)| priority > *current) {
|
||||
picked = Some((priority, terms[start..i].join(" ")));
|
||||
}
|
||||
}
|
||||
let value = picked.as_ref().and_then(|(_, name)| color(name)).ok_or_else(|| bad("unsupported or missing XPM colour"))?;
|
||||
if palette.insert(line[..cpp].to_vec(), value).is_some() {
|
||||
return Err(bad("duplicate XPM palette key"));
|
||||
}
|
||||
}
|
||||
let row_len = width.checked_mul(cpp).ok_or_else(|| bad("row byte count overflow"))?;
|
||||
let mut pixels = vec![0u8; width.checked_mul(height).and_then(|n| n.checked_mul(4)).ok_or_else(|| bad("pixel size overflow"))?];
|
||||
for (y, line) in lines[1 + colours..required].iter().enumerate() {
|
||||
if line.len() != row_len {
|
||||
return Err(bad("XPM row has the wrong width"));
|
||||
}
|
||||
for x in 0..width {
|
||||
let key = &line[x * cpp..(x + 1) * cpp];
|
||||
let rgba = palette.get(key).ok_or_else(|| bad("unknown palette key"))?;
|
||||
pixels[(y * width + x) * 4..(y * width + x + 1) * 4].copy_from_slice(rgba);
|
||||
}
|
||||
}
|
||||
Image::from_u8(w, h, ChannelLayout::Rgba, pixels)
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
//! X11 color-name database (X.Org rgb.txt) normalized to lowercase without
|
||||
//! spaces, underscores, or hyphens. RGB values are *X11* values, not CSS values.
|
||||
//! Source: https://cgit.freedesktop.org/xorg/app/rgb/tree/rgb.txt
|
||||
//! License: MIT (X.Org Foundation), see the upstream app/rgb COPYING file.
|
||||
//! Aliases differing only in case or spaces have been collapsed.
|
||||
//! Keep sorted: decoder uses binary search without allocation per candidate.
|
||||
|
||||
pub(super) const X11_COLORS: &[(&str, [u8; 3])] = &[
|
||||
("aliceblue", [240, 248, 255]),
|
||||
("antiquewhite", [250, 235, 215]),
|
||||
("antiquewhite1", [255, 239, 219]),
|
||||
("antiquewhite2", [238, 223, 204]),
|
||||
("antiquewhite3", [205, 192, 176]),
|
||||
("antiquewhite4", [139, 131, 120]),
|
||||
("aquamarine", [127, 255, 212]),
|
||||
("aquamarine1", [127, 255, 212]),
|
||||
("aquamarine2", [118, 238, 198]),
|
||||
("aquamarine3", [102, 205, 170]),
|
||||
("aquamarine4", [69, 139, 116]),
|
||||
("azure", [240, 255, 255]),
|
||||
("azure1", [240, 255, 255]),
|
||||
("azure2", [224, 238, 238]),
|
||||
("azure3", [193, 205, 205]),
|
||||
("azure4", [131, 139, 139]),
|
||||
("beige", [245, 245, 220]),
|
||||
("bisque", [255, 228, 196]),
|
||||
("bisque1", [255, 228, 196]),
|
||||
("bisque2", [238, 213, 183]),
|
||||
("bisque3", [205, 183, 158]),
|
||||
("bisque4", [139, 125, 107]),
|
||||
("black", [0, 0, 0]),
|
||||
("blanchedalmond", [255, 235, 205]),
|
||||
("blue", [0, 0, 255]),
|
||||
("blue1", [0, 0, 255]),
|
||||
("blue2", [0, 0, 238]),
|
||||
("blue3", [0, 0, 205]),
|
||||
("blue4", [0, 0, 139]),
|
||||
("blueviolet", [138, 43, 226]),
|
||||
("brown", [165, 42, 42]),
|
||||
("brown1", [255, 64, 64]),
|
||||
("brown2", [238, 59, 59]),
|
||||
("brown3", [205, 51, 51]),
|
||||
("brown4", [139, 35, 35]),
|
||||
("burlywood", [222, 184, 135]),
|
||||
("burlywood1", [255, 211, 155]),
|
||||
("burlywood2", [238, 197, 145]),
|
||||
("burlywood3", [205, 170, 125]),
|
||||
("burlywood4", [139, 115, 85]),
|
||||
("cadetblue", [95, 158, 160]),
|
||||
("cadetblue1", [152, 245, 255]),
|
||||
("cadetblue2", [142, 229, 238]),
|
||||
("cadetblue3", [122, 197, 205]),
|
||||
("cadetblue4", [83, 134, 139]),
|
||||
("chartreuse", [127, 255, 0]),
|
||||
("chartreuse1", [127, 255, 0]),
|
||||
("chartreuse2", [118, 238, 0]),
|
||||
("chartreuse3", [102, 205, 0]),
|
||||
("chartreuse4", [69, 139, 0]),
|
||||
("chocolate", [210, 105, 30]),
|
||||
("chocolate1", [255, 127, 36]),
|
||||
("chocolate2", [238, 118, 33]),
|
||||
("chocolate3", [205, 102, 29]),
|
||||
("chocolate4", [139, 69, 19]),
|
||||
("coral", [255, 127, 80]),
|
||||
("coral1", [255, 114, 86]),
|
||||
("coral2", [238, 106, 80]),
|
||||
("coral3", [205, 91, 69]),
|
||||
("coral4", [139, 62, 47]),
|
||||
("cornflowerblue", [100, 149, 237]),
|
||||
("cornsilk", [255, 248, 220]),
|
||||
("cornsilk1", [255, 248, 220]),
|
||||
("cornsilk2", [238, 232, 205]),
|
||||
("cornsilk3", [205, 200, 177]),
|
||||
("cornsilk4", [139, 136, 120]),
|
||||
("cyan", [0, 255, 255]),
|
||||
("cyan1", [0, 255, 255]),
|
||||
("cyan2", [0, 238, 238]),
|
||||
("cyan3", [0, 205, 205]),
|
||||
("cyan4", [0, 139, 139]),
|
||||
("darkblue", [0, 0, 139]),
|
||||
("darkcyan", [0, 139, 139]),
|
||||
("darkgoldenrod", [184, 134, 11]),
|
||||
("darkgoldenrod1", [255, 185, 15]),
|
||||
("darkgoldenrod2", [238, 173, 14]),
|
||||
("darkgoldenrod3", [205, 149, 12]),
|
||||
("darkgoldenrod4", [139, 101, 8]),
|
||||
("darkgray", [169, 169, 169]),
|
||||
("darkgreen", [0, 100, 0]),
|
||||
("darkgrey", [169, 169, 169]),
|
||||
("darkkhaki", [189, 183, 107]),
|
||||
("darkmagenta", [139, 0, 139]),
|
||||
("darkolivegreen", [85, 107, 47]),
|
||||
("darkolivegreen1", [202, 255, 112]),
|
||||
("darkolivegreen2", [188, 238, 104]),
|
||||
("darkolivegreen3", [162, 205, 90]),
|
||||
("darkolivegreen4", [110, 139, 61]),
|
||||
("darkorange", [255, 140, 0]),
|
||||
("darkorange1", [255, 127, 0]),
|
||||
("darkorange2", [238, 118, 0]),
|
||||
("darkorange3", [205, 102, 0]),
|
||||
("darkorange4", [139, 69, 0]),
|
||||
("darkorchid", [153, 50, 204]),
|
||||
("darkorchid1", [191, 62, 255]),
|
||||
("darkorchid2", [178, 58, 238]),
|
||||
("darkorchid3", [154, 50, 205]),
|
||||
("darkorchid4", [104, 34, 139]),
|
||||
("darkred", [139, 0, 0]),
|
||||
("darksalmon", [233, 150, 122]),
|
||||
("darkseagreen", [143, 188, 143]),
|
||||
("darkseagreen1", [193, 255, 193]),
|
||||
("darkseagreen2", [180, 238, 180]),
|
||||
("darkseagreen3", [155, 205, 155]),
|
||||
("darkseagreen4", [105, 139, 105]),
|
||||
("darkslateblue", [72, 61, 139]),
|
||||
("darkslategray", [47, 79, 79]),
|
||||
("darkslategray1", [151, 255, 255]),
|
||||
("darkslategray2", [141, 238, 238]),
|
||||
("darkslategray3", [121, 205, 205]),
|
||||
("darkslategray4", [82, 139, 139]),
|
||||
("darkslategrey", [47, 79, 79]),
|
||||
("darkturquoise", [0, 206, 209]),
|
||||
("darkviolet", [148, 0, 211]),
|
||||
("deeppink", [255, 20, 147]),
|
||||
("deeppink1", [255, 20, 147]),
|
||||
("deeppink2", [238, 18, 137]),
|
||||
("deeppink3", [205, 16, 118]),
|
||||
("deeppink4", [139, 10, 80]),
|
||||
("deepskyblue", [0, 191, 255]),
|
||||
("deepskyblue1", [0, 191, 255]),
|
||||
("deepskyblue2", [0, 178, 238]),
|
||||
("deepskyblue3", [0, 154, 205]),
|
||||
("deepskyblue4", [0, 104, 139]),
|
||||
("dimgray", [105, 105, 105]),
|
||||
("dimgrey", [105, 105, 105]),
|
||||
("dodgerblue", [30, 144, 255]),
|
||||
("dodgerblue1", [30, 144, 255]),
|
||||
("dodgerblue2", [28, 134, 238]),
|
||||
("dodgerblue3", [24, 116, 205]),
|
||||
("dodgerblue4", [16, 78, 139]),
|
||||
("firebrick", [178, 34, 34]),
|
||||
("firebrick1", [255, 48, 48]),
|
||||
("firebrick2", [238, 44, 44]),
|
||||
("firebrick3", [205, 38, 38]),
|
||||
("firebrick4", [139, 26, 26]),
|
||||
("floralwhite", [255, 250, 240]),
|
||||
("forestgreen", [34, 139, 34]),
|
||||
("gainsboro", [220, 220, 220]),
|
||||
("ghostwhite", [248, 248, 255]),
|
||||
("gold", [255, 215, 0]),
|
||||
("gold1", [255, 215, 0]),
|
||||
("gold2", [238, 201, 0]),
|
||||
("gold3", [205, 173, 0]),
|
||||
("gold4", [139, 117, 0]),
|
||||
("goldenrod", [218, 165, 32]),
|
||||
("goldenrod1", [255, 193, 37]),
|
||||
("goldenrod2", [238, 180, 34]),
|
||||
("goldenrod3", [205, 155, 29]),
|
||||
("goldenrod4", [139, 105, 20]),
|
||||
("gray", [190, 190, 190]),
|
||||
("gray0", [0, 0, 0]),
|
||||
("gray1", [3, 3, 3]),
|
||||
("gray10", [26, 26, 26]),
|
||||
("gray100", [255, 255, 255]),
|
||||
("gray11", [28, 28, 28]),
|
||||
("gray12", [31, 31, 31]),
|
||||
("gray13", [33, 33, 33]),
|
||||
("gray14", [36, 36, 36]),
|
||||
("gray15", [38, 38, 38]),
|
||||
("gray16", [41, 41, 41]),
|
||||
("gray17", [43, 43, 43]),
|
||||
("gray18", [46, 46, 46]),
|
||||
("gray19", [48, 48, 48]),
|
||||
("gray2", [5, 5, 5]),
|
||||
("gray20", [51, 51, 51]),
|
||||
("gray21", [54, 54, 54]),
|
||||
("gray22", [56, 56, 56]),
|
||||
("gray23", [59, 59, 59]),
|
||||
("gray24", [61, 61, 61]),
|
||||
("gray25", [64, 64, 64]),
|
||||
("gray26", [66, 66, 66]),
|
||||
("gray27", [69, 69, 69]),
|
||||
("gray28", [71, 71, 71]),
|
||||
("gray29", [74, 74, 74]),
|
||||
("gray3", [8, 8, 8]),
|
||||
("gray30", [77, 77, 77]),
|
||||
("gray31", [79, 79, 79]),
|
||||
("gray32", [82, 82, 82]),
|
||||
("gray33", [84, 84, 84]),
|
||||
("gray34", [87, 87, 87]),
|
||||
("gray35", [89, 89, 89]),
|
||||
("gray36", [92, 92, 92]),
|
||||
("gray37", [94, 94, 94]),
|
||||
("gray38", [97, 97, 97]),
|
||||
("gray39", [99, 99, 99]),
|
||||
("gray4", [10, 10, 10]),
|
||||
("gray40", [102, 102, 102]),
|
||||
("gray41", [105, 105, 105]),
|
||||
("gray42", [107, 107, 107]),
|
||||
("gray43", [110, 110, 110]),
|
||||
("gray44", [112, 112, 112]),
|
||||
("gray45", [115, 115, 115]),
|
||||
("gray46", [117, 117, 117]),
|
||||
("gray47", [120, 120, 120]),
|
||||
("gray48", [122, 122, 122]),
|
||||
("gray49", [125, 125, 125]),
|
||||
("gray5", [13, 13, 13]),
|
||||
("gray50", [127, 127, 127]),
|
||||
("gray51", [130, 130, 130]),
|
||||
("gray52", [133, 133, 133]),
|
||||
("gray53", [135, 135, 135]),
|
||||
("gray54", [138, 138, 138]),
|
||||
("gray55", [140, 140, 140]),
|
||||
("gray56", [143, 143, 143]),
|
||||
("gray57", [145, 145, 145]),
|
||||
("gray58", [148, 148, 148]),
|
||||
("gray59", [150, 150, 150]),
|
||||
("gray6", [15, 15, 15]),
|
||||
("gray60", [153, 153, 153]),
|
||||
("gray61", [156, 156, 156]),
|
||||
("gray62", [158, 158, 158]),
|
||||
("gray63", [161, 161, 161]),
|
||||
("gray64", [163, 163, 163]),
|
||||
("gray65", [166, 166, 166]),
|
||||
("gray66", [168, 168, 168]),
|
||||
("gray67", [171, 171, 171]),
|
||||
("gray68", [173, 173, 173]),
|
||||
("gray69", [176, 176, 176]),
|
||||
("gray7", [18, 18, 18]),
|
||||
("gray70", [179, 179, 179]),
|
||||
("gray71", [181, 181, 181]),
|
||||
("gray72", [184, 184, 184]),
|
||||
("gray73", [186, 186, 186]),
|
||||
("gray74", [189, 189, 189]),
|
||||
("gray75", [191, 191, 191]),
|
||||
("gray76", [194, 194, 194]),
|
||||
("gray77", [196, 196, 196]),
|
||||
("gray78", [199, 199, 199]),
|
||||
("gray79", [201, 201, 201]),
|
||||
("gray8", [20, 20, 20]),
|
||||
("gray80", [204, 204, 204]),
|
||||
("gray81", [207, 207, 207]),
|
||||
("gray82", [209, 209, 209]),
|
||||
("gray83", [212, 212, 212]),
|
||||
("gray84", [214, 214, 214]),
|
||||
("gray85", [217, 217, 217]),
|
||||
("gray86", [219, 219, 219]),
|
||||
("gray87", [222, 222, 222]),
|
||||
("gray88", [224, 224, 224]),
|
||||
("gray89", [227, 227, 227]),
|
||||
("gray9", [23, 23, 23]),
|
||||
("gray90", [229, 229, 229]),
|
||||
("gray91", [232, 232, 232]),
|
||||
("gray92", [235, 235, 235]),
|
||||
("gray93", [237, 237, 237]),
|
||||
("gray94", [240, 240, 240]),
|
||||
("gray95", [242, 242, 242]),
|
||||
("gray96", [245, 245, 245]),
|
||||
("gray97", [247, 247, 247]),
|
||||
("gray98", [250, 250, 250]),
|
||||
("gray99", [252, 252, 252]),
|
||||
("green", [0, 255, 0]),
|
||||
("green1", [0, 255, 0]),
|
||||
("green2", [0, 238, 0]),
|
||||
("green3", [0, 205, 0]),
|
||||
("green4", [0, 139, 0]),
|
||||
("greenyellow", [173, 255, 47]),
|
||||
("grey", [190, 190, 190]),
|
||||
("grey0", [0, 0, 0]),
|
||||
("grey1", [3, 3, 3]),
|
||||
("grey10", [26, 26, 26]),
|
||||
("grey100", [255, 255, 255]),
|
||||
("grey11", [28, 28, 28]),
|
||||
("grey12", [31, 31, 31]),
|
||||
("grey13", [33, 33, 33]),
|
||||
("grey14", [36, 36, 36]),
|
||||
("grey15", [38, 38, 38]),
|
||||
("grey16", [41, 41, 41]),
|
||||
("grey17", [43, 43, 43]),
|
||||
("grey18", [46, 46, 46]),
|
||||
("grey19", [48, 48, 48]),
|
||||
("grey2", [5, 5, 5]),
|
||||
("grey20", [51, 51, 51]),
|
||||
("grey21", [54, 54, 54]),
|
||||
("grey22", [56, 56, 56]),
|
||||
("grey23", [59, 59, 59]),
|
||||
("grey24", [61, 61, 61]),
|
||||
("grey25", [64, 64, 64]),
|
||||
("grey26", [66, 66, 66]),
|
||||
("grey27", [69, 69, 69]),
|
||||
("grey28", [71, 71, 71]),
|
||||
("grey29", [74, 74, 74]),
|
||||
("grey3", [8, 8, 8]),
|
||||
("grey30", [77, 77, 77]),
|
||||
("grey31", [79, 79, 79]),
|
||||
("grey32", [82, 82, 82]),
|
||||
("grey33", [84, 84, 84]),
|
||||
("grey34", [87, 87, 87]),
|
||||
("grey35", [89, 89, 89]),
|
||||
("grey36", [92, 92, 92]),
|
||||
("grey37", [94, 94, 94]),
|
||||
("grey38", [97, 97, 97]),
|
||||
("grey39", [99, 99, 99]),
|
||||
("grey4", [10, 10, 10]),
|
||||
("grey40", [102, 102, 102]),
|
||||
("grey41", [105, 105, 105]),
|
||||
("grey42", [107, 107, 107]),
|
||||
("grey43", [110, 110, 110]),
|
||||
("grey44", [112, 112, 112]),
|
||||
("grey45", [115, 115, 115]),
|
||||
("grey46", [117, 117, 117]),
|
||||
("grey47", [120, 120, 120]),
|
||||
("grey48", [122, 122, 122]),
|
||||
("grey49", [125, 125, 125]),
|
||||
("grey5", [13, 13, 13]),
|
||||
("grey50", [127, 127, 127]),
|
||||
("grey51", [130, 130, 130]),
|
||||
("grey52", [133, 133, 133]),
|
||||
("grey53", [135, 135, 135]),
|
||||
("grey54", [138, 138, 138]),
|
||||
("grey55", [140, 140, 140]),
|
||||
("grey56", [143, 143, 143]),
|
||||
("grey57", [145, 145, 145]),
|
||||
("grey58", [148, 148, 148]),
|
||||
("grey59", [150, 150, 150]),
|
||||
("grey6", [15, 15, 15]),
|
||||
("grey60", [153, 153, 153]),
|
||||
("grey61", [156, 156, 156]),
|
||||
("grey62", [158, 158, 158]),
|
||||
("grey63", [161, 161, 161]),
|
||||
("grey64", [163, 163, 163]),
|
||||
("grey65", [166, 166, 166]),
|
||||
("grey66", [168, 168, 168]),
|
||||
("grey67", [171, 171, 171]),
|
||||
("grey68", [173, 173, 173]),
|
||||
("grey69", [176, 176, 176]),
|
||||
("grey7", [18, 18, 18]),
|
||||
("grey70", [179, 179, 179]),
|
||||
("grey71", [181, 181, 181]),
|
||||
("grey72", [184, 184, 184]),
|
||||
("grey73", [186, 186, 186]),
|
||||
("grey74", [189, 189, 189]),
|
||||
("grey75", [191, 191, 191]),
|
||||
("grey76", [194, 194, 194]),
|
||||
("grey77", [196, 196, 196]),
|
||||
("grey78", [199, 199, 199]),
|
||||
("grey79", [201, 201, 201]),
|
||||
("grey8", [20, 20, 20]),
|
||||
("grey80", [204, 204, 204]),
|
||||
("grey81", [207, 207, 207]),
|
||||
("grey82", [209, 209, 209]),
|
||||
("grey83", [212, 212, 212]),
|
||||
("grey84", [214, 214, 214]),
|
||||
("grey85", [217, 217, 217]),
|
||||
("grey86", [219, 219, 219]),
|
||||
("grey87", [222, 222, 222]),
|
||||
("grey88", [224, 224, 224]),
|
||||
("grey89", [227, 227, 227]),
|
||||
("grey9", [23, 23, 23]),
|
||||
("grey90", [229, 229, 229]),
|
||||
("grey91", [232, 232, 232]),
|
||||
("grey92", [235, 235, 235]),
|
||||
("grey93", [237, 237, 237]),
|
||||
("grey94", [240, 240, 240]),
|
||||
("grey95", [242, 242, 242]),
|
||||
("grey96", [245, 245, 245]),
|
||||
("grey97", [247, 247, 247]),
|
||||
("grey98", [250, 250, 250]),
|
||||
("grey99", [252, 252, 252]),
|
||||
("honeydew", [240, 255, 240]),
|
||||
("honeydew1", [240, 255, 240]),
|
||||
("honeydew2", [224, 238, 224]),
|
||||
("honeydew3", [193, 205, 193]),
|
||||
("honeydew4", [131, 139, 131]),
|
||||
("hotpink", [255, 105, 180]),
|
||||
("hotpink1", [255, 110, 180]),
|
||||
("hotpink2", [238, 106, 167]),
|
||||
("hotpink3", [205, 96, 144]),
|
||||
("hotpink4", [139, 58, 98]),
|
||||
("indianred", [205, 92, 92]),
|
||||
("indianred1", [255, 106, 106]),
|
||||
("indianred2", [238, 99, 99]),
|
||||
("indianred3", [205, 85, 85]),
|
||||
("indianred4", [139, 58, 58]),
|
||||
("ivory", [255, 255, 240]),
|
||||
("ivory1", [255, 255, 240]),
|
||||
("ivory2", [238, 238, 224]),
|
||||
("ivory3", [205, 205, 193]),
|
||||
("ivory4", [139, 139, 131]),
|
||||
("khaki", [240, 230, 140]),
|
||||
("khaki1", [255, 246, 143]),
|
||||
("khaki2", [238, 230, 133]),
|
||||
("khaki3", [205, 198, 115]),
|
||||
("khaki4", [139, 134, 78]),
|
||||
("lavender", [230, 230, 250]),
|
||||
("lavenderblush", [255, 240, 245]),
|
||||
("lavenderblush1", [255, 240, 245]),
|
||||
("lavenderblush2", [238, 224, 229]),
|
||||
("lavenderblush3", [205, 193, 197]),
|
||||
("lavenderblush4", [139, 131, 134]),
|
||||
("lawngreen", [124, 252, 0]),
|
||||
("lemonchiffon", [255, 250, 205]),
|
||||
("lemonchiffon1", [255, 250, 205]),
|
||||
("lemonchiffon2", [238, 233, 191]),
|
||||
("lemonchiffon3", [205, 201, 165]),
|
||||
("lemonchiffon4", [139, 137, 112]),
|
||||
("lightblue", [173, 216, 230]),
|
||||
("lightblue1", [191, 239, 255]),
|
||||
("lightblue2", [178, 223, 238]),
|
||||
("lightblue3", [154, 192, 205]),
|
||||
("lightblue4", [104, 131, 139]),
|
||||
("lightcoral", [240, 128, 128]),
|
||||
("lightcyan", [224, 255, 255]),
|
||||
("lightcyan1", [224, 255, 255]),
|
||||
("lightcyan2", [209, 238, 238]),
|
||||
("lightcyan3", [180, 205, 205]),
|
||||
("lightcyan4", [122, 139, 139]),
|
||||
("lightgoldenrod", [238, 221, 130]),
|
||||
("lightgoldenrod1", [255, 236, 139]),
|
||||
("lightgoldenrod2", [238, 220, 130]),
|
||||
("lightgoldenrod3", [205, 190, 112]),
|
||||
("lightgoldenrod4", [139, 129, 76]),
|
||||
("lightgoldenrodyellow", [250, 250, 210]),
|
||||
("lightgray", [211, 211, 211]),
|
||||
("lightgreen", [144, 238, 144]),
|
||||
("lightgrey", [211, 211, 211]),
|
||||
("lightpink", [255, 182, 193]),
|
||||
("lightpink1", [255, 174, 185]),
|
||||
("lightpink2", [238, 162, 173]),
|
||||
("lightpink3", [205, 140, 149]),
|
||||
("lightpink4", [139, 95, 101]),
|
||||
("lightsalmon", [255, 160, 122]),
|
||||
("lightsalmon1", [255, 160, 122]),
|
||||
("lightsalmon2", [238, 149, 114]),
|
||||
("lightsalmon3", [205, 129, 98]),
|
||||
("lightsalmon4", [139, 87, 66]),
|
||||
("lightseagreen", [32, 178, 170]),
|
||||
("lightskyblue", [135, 206, 250]),
|
||||
("lightskyblue1", [176, 226, 255]),
|
||||
("lightskyblue2", [164, 211, 238]),
|
||||
("lightskyblue3", [141, 182, 205]),
|
||||
("lightskyblue4", [96, 123, 139]),
|
||||
("lightslateblue", [132, 112, 255]),
|
||||
("lightslategray", [119, 136, 153]),
|
||||
("lightslategrey", [119, 136, 153]),
|
||||
("lightsteelblue", [176, 196, 222]),
|
||||
("lightsteelblue1", [202, 225, 255]),
|
||||
("lightsteelblue2", [188, 210, 238]),
|
||||
("lightsteelblue3", [162, 181, 205]),
|
||||
("lightsteelblue4", [110, 123, 139]),
|
||||
("lightyellow", [255, 255, 224]),
|
||||
("lightyellow1", [255, 255, 224]),
|
||||
("lightyellow2", [238, 238, 209]),
|
||||
("lightyellow3", [205, 205, 180]),
|
||||
("lightyellow4", [139, 139, 122]),
|
||||
("limegreen", [50, 205, 50]),
|
||||
("linen", [250, 240, 230]),
|
||||
("magenta", [255, 0, 255]),
|
||||
("magenta1", [255, 0, 255]),
|
||||
("magenta2", [238, 0, 238]),
|
||||
("magenta3", [205, 0, 205]),
|
||||
("magenta4", [139, 0, 139]),
|
||||
("maroon", [176, 48, 96]),
|
||||
("maroon1", [255, 52, 179]),
|
||||
("maroon2", [238, 48, 167]),
|
||||
("maroon3", [205, 41, 144]),
|
||||
("maroon4", [139, 28, 98]),
|
||||
("mediumaquamarine", [102, 205, 170]),
|
||||
("mediumblue", [0, 0, 205]),
|
||||
("mediumorchid", [186, 85, 211]),
|
||||
("mediumorchid1", [224, 102, 255]),
|
||||
("mediumorchid2", [209, 95, 238]),
|
||||
("mediumorchid3", [180, 82, 205]),
|
||||
("mediumorchid4", [122, 55, 139]),
|
||||
("mediumpurple", [147, 112, 219]),
|
||||
("mediumpurple1", [171, 130, 255]),
|
||||
("mediumpurple2", [159, 121, 238]),
|
||||
("mediumpurple3", [137, 104, 205]),
|
||||
("mediumpurple4", [93, 71, 139]),
|
||||
("mediumseagreen", [60, 179, 113]),
|
||||
("mediumslateblue", [123, 104, 238]),
|
||||
("mediumspringgreen", [0, 250, 154]),
|
||||
("mediumturquoise", [72, 209, 204]),
|
||||
("mediumvioletred", [199, 21, 133]),
|
||||
("midnightblue", [25, 25, 112]),
|
||||
("mintcream", [245, 255, 250]),
|
||||
("mistyrose", [255, 228, 225]),
|
||||
("mistyrose1", [255, 228, 225]),
|
||||
("mistyrose2", [238, 213, 210]),
|
||||
("mistyrose3", [205, 183, 181]),
|
||||
("mistyrose4", [139, 125, 123]),
|
||||
("moccasin", [255, 228, 181]),
|
||||
("navajowhite", [255, 222, 173]),
|
||||
("navajowhite1", [255, 222, 173]),
|
||||
("navajowhite2", [238, 207, 161]),
|
||||
("navajowhite3", [205, 179, 139]),
|
||||
("navajowhite4", [139, 121, 94]),
|
||||
("navy", [0, 0, 128]),
|
||||
("navyblue", [0, 0, 128]),
|
||||
("oldlace", [253, 245, 230]),
|
||||
("olivedrab", [107, 142, 35]),
|
||||
("olivedrab1", [192, 255, 62]),
|
||||
("olivedrab2", [179, 238, 58]),
|
||||
("olivedrab3", [154, 205, 50]),
|
||||
("olivedrab4", [105, 139, 34]),
|
||||
("orange", [255, 165, 0]),
|
||||
("orange1", [255, 165, 0]),
|
||||
("orange2", [238, 154, 0]),
|
||||
("orange3", [205, 133, 0]),
|
||||
("orange4", [139, 90, 0]),
|
||||
("orangered", [255, 69, 0]),
|
||||
("orangered1", [255, 69, 0]),
|
||||
("orangered2", [238, 64, 0]),
|
||||
("orangered3", [205, 55, 0]),
|
||||
("orangered4", [139, 37, 0]),
|
||||
("orchid", [218, 112, 214]),
|
||||
("orchid1", [255, 131, 250]),
|
||||
("orchid2", [238, 122, 233]),
|
||||
("orchid3", [205, 105, 201]),
|
||||
("orchid4", [139, 71, 137]),
|
||||
("palegoldenrod", [238, 232, 170]),
|
||||
("palegreen", [152, 251, 152]),
|
||||
("palegreen1", [154, 255, 154]),
|
||||
("palegreen2", [144, 238, 144]),
|
||||
("palegreen3", [124, 205, 124]),
|
||||
("palegreen4", [84, 139, 84]),
|
||||
("paleturquoise", [175, 238, 238]),
|
||||
("paleturquoise1", [187, 255, 255]),
|
||||
("paleturquoise2", [174, 238, 238]),
|
||||
("paleturquoise3", [150, 205, 205]),
|
||||
("paleturquoise4", [102, 139, 139]),
|
||||
("palevioletred", [219, 112, 147]),
|
||||
("palevioletred1", [255, 130, 171]),
|
||||
("palevioletred2", [238, 121, 159]),
|
||||
("palevioletred3", [205, 104, 137]),
|
||||
("palevioletred4", [139, 71, 93]),
|
||||
("papayawhip", [255, 239, 213]),
|
||||
("peachpuff", [255, 218, 185]),
|
||||
("peachpuff1", [255, 218, 185]),
|
||||
("peachpuff2", [238, 203, 173]),
|
||||
("peachpuff3", [205, 175, 149]),
|
||||
("peachpuff4", [139, 119, 101]),
|
||||
("peru", [205, 133, 63]),
|
||||
("pink", [255, 192, 203]),
|
||||
("pink1", [255, 181, 197]),
|
||||
("pink2", [238, 169, 184]),
|
||||
("pink3", [205, 145, 158]),
|
||||
("pink4", [139, 99, 108]),
|
||||
("plum", [221, 160, 221]),
|
||||
("plum1", [255, 187, 255]),
|
||||
("plum2", [238, 174, 238]),
|
||||
("plum3", [205, 150, 205]),
|
||||
("plum4", [139, 102, 139]),
|
||||
("powderblue", [176, 224, 230]),
|
||||
("purple", [160, 32, 240]),
|
||||
("purple1", [155, 48, 255]),
|
||||
("purple2", [145, 44, 238]),
|
||||
("purple3", [125, 38, 205]),
|
||||
("purple4", [85, 26, 139]),
|
||||
("red", [255, 0, 0]),
|
||||
("red1", [255, 0, 0]),
|
||||
("red2", [238, 0, 0]),
|
||||
("red3", [205, 0, 0]),
|
||||
("red4", [139, 0, 0]),
|
||||
("rosybrown", [188, 143, 143]),
|
||||
("rosybrown1", [255, 193, 193]),
|
||||
("rosybrown2", [238, 180, 180]),
|
||||
("rosybrown3", [205, 155, 155]),
|
||||
("rosybrown4", [139, 105, 105]),
|
||||
("royalblue", [65, 105, 225]),
|
||||
("royalblue1", [72, 118, 255]),
|
||||
("royalblue2", [67, 110, 238]),
|
||||
("royalblue3", [58, 95, 205]),
|
||||
("royalblue4", [39, 64, 139]),
|
||||
("saddlebrown", [139, 69, 19]),
|
||||
("salmon", [250, 128, 114]),
|
||||
("salmon1", [255, 140, 105]),
|
||||
("salmon2", [238, 130, 98]),
|
||||
("salmon3", [205, 112, 84]),
|
||||
("salmon4", [139, 76, 57]),
|
||||
("sandybrown", [244, 164, 96]),
|
||||
("seagreen", [46, 139, 87]),
|
||||
("seagreen1", [84, 255, 159]),
|
||||
("seagreen2", [78, 238, 148]),
|
||||
("seagreen3", [67, 205, 128]),
|
||||
("seagreen4", [46, 139, 87]),
|
||||
("seashell", [255, 245, 238]),
|
||||
("seashell1", [255, 245, 238]),
|
||||
("seashell2", [238, 229, 222]),
|
||||
("seashell3", [205, 197, 191]),
|
||||
("seashell4", [139, 134, 130]),
|
||||
("sienna", [160, 82, 45]),
|
||||
("sienna1", [255, 130, 71]),
|
||||
("sienna2", [238, 121, 66]),
|
||||
("sienna3", [205, 104, 57]),
|
||||
("sienna4", [139, 71, 38]),
|
||||
("skyblue", [135, 206, 235]),
|
||||
("skyblue1", [135, 206, 255]),
|
||||
("skyblue2", [126, 192, 238]),
|
||||
("skyblue3", [108, 166, 205]),
|
||||
("skyblue4", [74, 112, 139]),
|
||||
("slateblue", [106, 90, 205]),
|
||||
("slateblue1", [131, 111, 255]),
|
||||
("slateblue2", [122, 103, 238]),
|
||||
("slateblue3", [105, 89, 205]),
|
||||
("slateblue4", [71, 60, 139]),
|
||||
("slategray", [112, 128, 144]),
|
||||
("slategray1", [198, 226, 255]),
|
||||
("slategray2", [185, 211, 238]),
|
||||
("slategray3", [159, 182, 205]),
|
||||
("slategray4", [108, 123, 139]),
|
||||
("slategrey", [112, 128, 144]),
|
||||
("snow", [255, 250, 250]),
|
||||
("snow1", [255, 250, 250]),
|
||||
("snow2", [238, 233, 233]),
|
||||
("snow3", [205, 201, 201]),
|
||||
("snow4", [139, 137, 137]),
|
||||
("springgreen", [0, 255, 127]),
|
||||
("springgreen1", [0, 255, 127]),
|
||||
("springgreen2", [0, 238, 118]),
|
||||
("springgreen3", [0, 205, 102]),
|
||||
("springgreen4", [0, 139, 69]),
|
||||
("steelblue", [70, 130, 180]),
|
||||
("steelblue1", [99, 184, 255]),
|
||||
("steelblue2", [92, 172, 238]),
|
||||
("steelblue3", [79, 148, 205]),
|
||||
("steelblue4", [54, 100, 139]),
|
||||
("tan", [210, 180, 140]),
|
||||
("tan1", [255, 165, 79]),
|
||||
("tan2", [238, 154, 73]),
|
||||
("tan3", [205, 133, 63]),
|
||||
("tan4", [139, 90, 43]),
|
||||
("thistle", [216, 191, 216]),
|
||||
("thistle1", [255, 225, 255]),
|
||||
("thistle2", [238, 210, 238]),
|
||||
("thistle3", [205, 181, 205]),
|
||||
("thistle4", [139, 123, 139]),
|
||||
("tomato", [255, 99, 71]),
|
||||
("tomato1", [255, 99, 71]),
|
||||
("tomato2", [238, 92, 66]),
|
||||
("tomato3", [205, 79, 57]),
|
||||
("tomato4", [139, 54, 38]),
|
||||
("turquoise", [64, 224, 208]),
|
||||
("turquoise1", [0, 245, 255]),
|
||||
("turquoise2", [0, 229, 238]),
|
||||
("turquoise3", [0, 197, 205]),
|
||||
("turquoise4", [0, 134, 139]),
|
||||
("violet", [238, 130, 238]),
|
||||
("violetred", [208, 32, 144]),
|
||||
("violetred1", [255, 62, 150]),
|
||||
("violetred2", [238, 58, 140]),
|
||||
("violetred3", [205, 50, 120]),
|
||||
("violetred4", [139, 34, 82]),
|
||||
("wheat", [245, 222, 179]),
|
||||
("wheat1", [255, 231, 186]),
|
||||
("wheat2", [238, 216, 174]),
|
||||
("wheat3", [205, 186, 150]),
|
||||
("wheat4", [139, 126, 102]),
|
||||
("white", [255, 255, 255]),
|
||||
("whitesmoke", [245, 245, 245]),
|
||||
("yellow", [255, 255, 0]),
|
||||
("yellow1", [255, 255, 0]),
|
||||
("yellow2", [238, 238, 0]),
|
||||
("yellow3", [205, 205, 0]),
|
||||
("yellow4", [139, 139, 0]),
|
||||
("yellowgreen", [154, 205, 50]),
|
||||
];
|
||||
@@ -17,6 +17,8 @@ pub enum Format {
|
||||
Pnm,
|
||||
/// ZSoft Paintbrush: 1/2/4-bit planar palettes, 8-bit indexed and 24-bit RGB.
|
||||
Pcx,
|
||||
/// X PixMap icons (XPM3 C-string representation).
|
||||
Xpm,
|
||||
Qoi,
|
||||
OpenExr,
|
||||
/// Radiance RGBE.
|
||||
@@ -60,6 +62,7 @@ pub struct FormatCaps {
|
||||
/// Formats that are enabled but intentionally not symmetric, with the reason.
|
||||
/// The test-suite asserts that every other enabled format is read+write.
|
||||
pub const ASYMMETRIC_EXCEPTIONS: &[(Format, &str)] = &[
|
||||
(Format::Xpm, "XPM is a legacy Unix icon interchange format. Reading existing XPM icons is supported; writing modern image assets should use PNG instead."),
|
||||
(
|
||||
Format::Pcx,
|
||||
"PCX is supported for opening legacy paint and clipart assets. New files should use PNG; writing obsolete indexed and planar variants is intentionally not provided.",
|
||||
@@ -86,7 +89,7 @@ const ALL_LAYOUTS: &[ChannelLayout] = &[L::Gray, L::GrayA, L::Rgb, L::Rgba, L::C
|
||||
|
||||
impl Format {
|
||||
/// Every format known to the crate (enabled or not).
|
||||
pub const ALL: [Format; 17] = [
|
||||
pub const ALL: [Format; 18] = [
|
||||
Format::Png,
|
||||
Format::Jpeg,
|
||||
Format::Tiff,
|
||||
@@ -97,6 +100,7 @@ impl Format {
|
||||
Format::Ico,
|
||||
Format::Pnm,
|
||||
Format::Pcx,
|
||||
Format::Xpm,
|
||||
Format::Qoi,
|
||||
Format::OpenExr,
|
||||
Format::Hdr,
|
||||
@@ -128,6 +132,7 @@ impl Format {
|
||||
Format::Ico => "ICO",
|
||||
Format::Pnm => "Netpbm",
|
||||
Format::Pcx => "PCX",
|
||||
Format::Xpm => "XPM",
|
||||
Format::Qoi => "QOI",
|
||||
Format::OpenExr => "OpenEXR",
|
||||
Format::Hdr => "Radiance HDR",
|
||||
@@ -152,6 +157,7 @@ impl Format {
|
||||
Format::Ico => &["ico"],
|
||||
Format::Pnm => &["pnm", "pbm", "pgm", "ppm", "pam", "pfm"],
|
||||
Format::Pcx => &["pcx"],
|
||||
Format::Xpm => &["xpm"],
|
||||
Format::Qoi => &["qoi"],
|
||||
Format::OpenExr => &["exr"],
|
||||
Format::Hdr => &["hdr"],
|
||||
@@ -174,6 +180,7 @@ impl Format {
|
||||
Format::Ico => "image/x-icon",
|
||||
Format::Pnm => "image/x-portable-anymap",
|
||||
Format::Pcx => "image/x-pcx",
|
||||
Format::Xpm => "image/x-xpixmap",
|
||||
Format::Qoi => "image/qoi",
|
||||
Format::OpenExr => "image/x-exr",
|
||||
Format::Hdr => "image/vnd.radiance",
|
||||
@@ -224,6 +231,7 @@ pub fn caps(format: Format) -> FormatCaps {
|
||||
Format::Ico => FormatCaps { layouts: &[L::Rgba], ..base },
|
||||
Format::Pnm => FormatCaps { depths: &[S::U8, S::U16, S::F32], layouts: ALL_LAYOUTS, ..base },
|
||||
Format::Pcx => FormatCaps { read: true, write: false, layouts: &[L::Rgb], alpha: false, ..base },
|
||||
Format::Xpm => FormatCaps { read: true, write: false, layouts: &[L::Rgba], ..base },
|
||||
Format::Qoi => base,
|
||||
Format::OpenExr => FormatCaps { depths: &[S::F16, S::F32], layouts: RGB_GRAY, ..base },
|
||||
Format::Hdr => FormatCaps { depths: &[S::F32], layouts: &[L::Rgb], alpha: false, lossy: true, ..base },
|
||||
@@ -288,6 +296,9 @@ pub fn detect(bytes: &[u8]) -> Option<Format> {
|
||||
if b.starts_with(b"SIMPLE =") {
|
||||
return Some(Format::Fits);
|
||||
}
|
||||
if b.starts_with(b"/* XPM */") {
|
||||
return Some(Format::Xpm);
|
||||
}
|
||||
if crate::codecs::pcx::looks_like_pcx(b) {
|
||||
return Some(Format::Pcx);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub use crate::resolution::{exif_resolution, export_exif, export_xmp, photoshop_
|
||||
pub use crate::source_settings::{WebpEncoding, jpeg_quality, webp_encoding};
|
||||
pub use half::f16;
|
||||
|
||||
use crate::codecs::{dpx, exr, fits, heif, jpeg, pcx, png, pnm, tiff, via_image, webp};
|
||||
use crate::codecs::{dpx, exr, fits, heif, jpeg, pcx, png, pnm, tiff, via_image, webp, xpm};
|
||||
|
||||
/// Detect the format and decode with default [`Limits`].
|
||||
pub fn decode(bytes: &[u8]) -> Result<Image, CodecError> {
|
||||
@@ -75,6 +75,7 @@ pub fn decode_as_with(format: Format, bytes: &[u8], opts: &DecodeOptions) -> Res
|
||||
Format::WebP => webp::decode(bytes, l),
|
||||
Format::Pnm => pnm::decode(bytes, l),
|
||||
Format::Pcx => pcx::decode(bytes, l),
|
||||
Format::Xpm => xpm::decode(bytes, l),
|
||||
Format::Fits => fits::decode(bytes, l),
|
||||
Format::Dpx => dpx::decode(bytes, l),
|
||||
Format::OpenExr => exr::decode(bytes, l),
|
||||
@@ -225,6 +226,7 @@ pub fn encode(image: &Image, format: Format, opts: &EncodeOptions) -> Result<Vec
|
||||
Format::WebP => webp::encode(image, plan, opts),
|
||||
Format::Pnm => pnm::encode(image, plan, opts),
|
||||
Format::Pcx => Err(CodecError::unsupported(format, "PCX is import-only; save as PNG instead")),
|
||||
Format::Xpm => Err(CodecError::unsupported(format, "XPM is import-only; save as PNG instead")),
|
||||
Format::Fits => fits::encode(image, plan, opts),
|
||||
Format::Dpx => dpx::encode(image, plan, opts),
|
||||
Format::OpenExr => exr::encode(image, plan, opts),
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use photocraft_codecs::{ChannelLayout, CodecError, Format, Image, caps, decode, decode_as, detect, encode, from_extension};
|
||||
|
||||
const SAMPLE: &[u8] = br#"/* XPM */
|
||||
static char *icon[] = {
|
||||
"2 2 3 1",
|
||||
". c #ff0000",
|
||||
" c None",
|
||||
"+ c #00ff00",
|
||||
".+",
|
||||
" +"
|
||||
};
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn xpm3_decodes_palette_transparency_and_spaces_in_keys() {
|
||||
assert_eq!(detect(SAMPLE), Some(Format::Xpm));
|
||||
assert_eq!(from_extension("icon.XPM"), Some(Format::Xpm));
|
||||
let img = decode(SAMPLE).unwrap();
|
||||
assert_eq!(img.layout(), ChannelLayout::Rgba);
|
||||
assert_eq!(img.dimensions(), (2, 2));
|
||||
assert_eq!(img.data(), &[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 255, 0, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rgb_precisions_and_x11_name() {
|
||||
let xpm = br#"/* XPM */
|
||||
static char *i[] = {
|
||||
"3 1 3 1",
|
||||
"a c #f00",
|
||||
"b c #ffff00000000",
|
||||
"c c gray70",
|
||||
"abc"
|
||||
};"#;
|
||||
let img = decode(xpm).unwrap();
|
||||
assert_eq!(img.data(), &[255, 0, 0, 255, 255, 0, 0, 255, 179, 179, 179, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_truncation_unsupported_colours_and_unknown_keys() {
|
||||
for bytes in [
|
||||
&b"/* XPM */\nstatic char *i[] = { \"1 1 1 1\", \"a c None\" };"[..],
|
||||
&b"/* XPM */\nstatic char *i[] = { \"1 1 1 1\", \"a c NotAColor\", \"a\" };"[..],
|
||||
&b"/* XPM */\nstatic char *i[] = { \"1 1 1 1\", \"a c #a\xC3\xA9\", \"a\" };"[..],
|
||||
&b"/* XPM */\nstatic char *i[] = { \"1 1 1 1\", \"a c #+f0000\", \"a\" };"[..],
|
||||
&b"/* XPM */\nstatic char *i[] = { \"1 1 1 1\", \"a c black\", \"z\" };"[..],
|
||||
&b"/* XPM */\nstatic char *i[] = { \"1 1 1 1\", \"a c black\", \"aa\" };"[..],
|
||||
&b"/* XPM */\nstatic char *i[] = { \"1 1 2 1\", \"a c black\", \"a c white\", \"a\" };"[..],
|
||||
] {
|
||||
assert!(decode_as(Format::Xpm, bytes).is_err(), "{:?}", bytes);
|
||||
}
|
||||
assert!(decode_as(Format::Xpm, b"/* XPM */\n\"4294967295 4294967295 1 1\"").is_err());
|
||||
assert!(decode_as(Format::Xpm, b"/* XPM */\n\"2 1 1 1\" \"a c black\" \"aa\" /* bad").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn x11_names_and_field_priority() {
|
||||
let xpm = br#"/* XPM */
|
||||
static char *i[] = {
|
||||
"4 1 4 1",
|
||||
"a c LightGray",
|
||||
"b c DarkBlue",
|
||||
"c m black g white c light blue",
|
||||
"d m black g4 red g green",
|
||||
"abcd"
|
||||
};"#;
|
||||
let image = decode(xpm).unwrap();
|
||||
assert_eq!(image.data(), &[211, 211, 211, 255, 0, 0, 139, 255, 173, 216, 230, 255, 0, 255, 0, 255]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn named_x11_colors_use_x11_not_css_values() {
|
||||
let bytes = br#"/* XPM */
|
||||
static char *icon[] = {
|
||||
"6 1 6 1",
|
||||
"a c gray",
|
||||
"b c maroon",
|
||||
"c c purple",
|
||||
"d c LightGoldenrodYellow",
|
||||
"e c DarkSlateGray4",
|
||||
"f c Ghost White",
|
||||
"abcdef"
|
||||
};"#;
|
||||
let image = decode(bytes).unwrap();
|
||||
assert_eq!(image.data(), &[190, 190, 190, 255, 176, 48, 96, 255, 160, 32, 240, 255, 250, 250, 210, 255, 82, 139, 139, 255, 248, 248, 255, 255,]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xpm_is_import_only() {
|
||||
assert!(caps(Format::Xpm).read);
|
||||
assert!(!caps(Format::Xpm).write);
|
||||
let image = Image::from_u8(1, 1, ChannelLayout::Rgba, vec![0, 0, 0, 0]).unwrap();
|
||||
assert!(matches!(encode(&image, Format::Xpm, &Default::default()), Err(CodecError::Unsupported { .. })));
|
||||
}
|
||||
Reference in New Issue
Block a user