use crate::{decoder::DcsSettings, Result, SixelError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BackgroundMode {
Opaque,
#[default]
Transparent,
}
impl BackgroundMode {
pub fn from_p2(p2: u16) -> Self {
match p2 {
1 => Self::Transparent,
_ => Self::Opaque, }
}
pub fn to_p2_value(self) -> u8 {
match self {
Self::Opaque => 0,
Self::Transparent => 1,
}
}
#[inline]
pub fn is_transparent(self) -> bool {
matches!(self, Self::Transparent)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PixelAspectRatio {
Ratio5To1,
Ratio3To1,
Ratio2To1,
#[default]
Square,
}
impl PixelAspectRatio {
pub fn from_p1(p1: u16) -> Self {
match p1 {
0 | 1 => Self::Ratio5To1,
2 => Self::Ratio3To1,
3..=6 => Self::Ratio2To1,
7..=9 => Self::Square,
_ => Self::Square,
}
}
pub fn to_p1_value(self) -> u8 {
match self {
Self::Ratio5To1 => 0,
Self::Ratio3To1 => 2,
Self::Ratio2To1 => 3,
Self::Square => 9,
}
}
#[inline]
pub fn pan(self) -> u16 {
match self {
Self::Ratio5To1 => 1,
Self::Ratio3To1 => 1,
Self::Ratio2To1 => 1,
Self::Square => 1,
}
}
#[inline]
pub fn pad(self) -> u16 {
match self {
Self::Ratio5To1 => 5,
Self::Ratio3To1 => 3,
Self::Ratio2To1 => 2,
Self::Square => 1,
}
}
#[inline]
pub fn as_f32(self) -> f32 {
self.pan() as f32 / self.pad() as f32
}
#[inline]
pub fn is_square(self) -> bool {
matches!(self, Self::Square)
}
}
#[derive(Debug, Clone)]
pub struct SixelImage {
pub pixels: Vec<u8>,
pub width: usize,
pub height: usize,
pub aspect_ratio: PixelAspectRatio,
pub background_mode: BackgroundMode,
}
impl SixelImage {
#[must_use = "this returns the decoded SixelImage"]
pub fn decode(data: &[u8]) -> Result<Self> {
crate::decoder::decode_sixel(data)
}
#[must_use = "this returns the decoded SixelImage"]
pub fn decode_from_dcs(payload: &[u8], settings: DcsSettings) -> Result<Self> {
crate::decoder::decode_sixel_from_dcs(payload, settings)
}
pub fn corrected_dimensions(&self) -> (usize, usize) {
if self.aspect_ratio.is_square() {
(self.width, self.height)
} else if self.aspect_ratio.pan() > self.aspect_ratio.pad() {
let new_width =
(self.width * self.aspect_ratio.pan() as usize) / self.aspect_ratio.pad() as usize;
(new_width, self.height)
} else {
let new_height =
(self.height * self.aspect_ratio.pad() as usize) / self.aspect_ratio.pan() as usize;
(self.width, new_height)
}
}
pub fn from_rgba(pixels: Vec<u8>, width: usize, height: usize) -> Self {
Self {
pixels,
width,
height,
aspect_ratio: PixelAspectRatio::default(),
background_mode: BackgroundMode::default(),
}
}
pub fn try_from_rgba(pixels: Vec<u8>, width: usize, height: usize) -> Result<Self> {
if width == 0 || height == 0 {
return Err(SixelError::InvalidDimensions { width, height });
}
let expected = width
.checked_mul(height)
.and_then(|v| v.checked_mul(4))
.ok_or(SixelError::IntegerOverflow)?;
if pixels.len() != expected {
return Err(SixelError::BufferSizeMismatch {
expected,
actual: pixels.len(),
});
}
Ok(Self::from_rgba(pixels, width, height))
}
#[must_use]
pub fn with_aspect_ratio(mut self, aspect_ratio: PixelAspectRatio) -> Self {
self.aspect_ratio = aspect_ratio;
self
}
#[must_use]
pub fn with_background_mode(mut self, background_mode: BackgroundMode) -> Self {
self.background_mode = background_mode;
self
}
#[must_use = "this returns the encoded SIXEL string"]
pub fn encode(&self) -> Result<String> {
crate::encoder::sixel_encode_impl(
&self.pixels,
self.width,
self.height,
&Default::default(),
self.aspect_ratio,
self.background_mode,
)
}
#[must_use = "this returns the encoded SIXEL string"]
pub fn encode_with(&self, opts: &crate::encoder::EncodeOptions) -> Result<String> {
crate::encoder::sixel_encode_impl(
&self.pixels,
self.width,
self.height,
opts,
self.aspect_ratio,
self.background_mode,
)
}
#[inline]
pub fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
pub fn has_transparency(&self) -> bool {
self.pixels.chunks_exact(4).any(|c| c[3] < 128)
}
}
impl core::fmt::Display for SixelImage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.encode() {
Ok(sixel) => f.write_str(&sixel),
Err(_) => Err(core::fmt::Error),
}
}
}
impl core::str::FromStr for SixelImage {
type Err = SixelError;
fn from_str(s: &str) -> Result<Self> {
Self::decode(s.as_bytes())
}
}