use pix::{gray::Gray8, Raster, Region};
const CHANNELS: usize = 3;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ColorTableExistence {
Absent,
Present,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ColorTableOrdering {
NotSorted,
Sorted,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ColorTableConfig {
existence: ColorTableExistence,
ordering: ColorTableOrdering,
table_len: usize,
}
impl Default for ColorTableConfig {
fn default() -> Self {
let existence = ColorTableExistence::Absent;
let ordering = ColorTableOrdering::NotSorted;
let table_len = 2;
ColorTableConfig {
existence,
ordering,
table_len,
}
}
}
impl ColorTableConfig {
pub fn new(
existence: ColorTableExistence,
ordering: ColorTableOrdering,
table_len: u16,
) -> Self {
let table_len =
(table_len as usize).max(2).next_power_of_two().min(256);
ColorTableConfig {
existence,
ordering,
table_len,
}
}
pub fn existence(&self) -> ColorTableExistence {
self.existence
}
pub fn ordering(&self) -> ColorTableOrdering {
self.ordering
}
pub fn len(&self) -> usize {
match self.existence {
ColorTableExistence::Absent => 0,
ColorTableExistence::Present => self.table_len,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn len_bits(&self) -> u8 {
let sz = self.table_len;
for b in 0..7 {
if (sz >> (b + 1)) == 1 {
return b;
}
}
7
}
pub fn size_bytes(&self) -> usize {
self.len() * CHANNELS
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DisposalMethod {
NoAction,
Keep,
Background,
Previous,
Reserved(u8),
}
impl Default for DisposalMethod {
fn default() -> Self {
DisposalMethod::Keep
}
}
impl From<u8> for DisposalMethod {
fn from(n: u8) -> Self {
use self::DisposalMethod::*;
match n & 0b0111 {
0 => NoAction,
1 => Keep,
2 => Background,
3 => Previous,
_ => Reserved(n),
}
}
}
impl From<DisposalMethod> for u8 {
fn from(d: DisposalMethod) -> Self {
use self::DisposalMethod::*;
match d {
NoAction => 0,
Keep => 1,
Background => 2,
Previous => 3,
Reserved(n) => n & 0b0111,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum BlockCode {
Header_,
LogicalScreenDesc_,
GlobalColorTable_,
Extension_,
ImageDesc_,
LocalColorTable_,
ImageData_,
Trailer_,
}
impl BlockCode {
pub fn from_u8(t: u8) -> Option<Self> {
use self::BlockCode::*;
match t {
b',' => Some(ImageDesc_),
b'!' => Some(Extension_),
b';' => Some(Trailer_),
_ => None,
}
}
pub fn signature(self) -> &'static [u8] {
use self::BlockCode::*;
match self {
ImageDesc_ => b",",
Extension_ => b"!",
Trailer_ => b";",
_ => &[],
}
}
pub fn size(self) -> usize {
use self::BlockCode::*;
match self {
Header_ => 6,
LogicalScreenDesc_ => 7,
ImageDesc_ => 10,
Trailer_ => 1,
Extension_ => 2,
ImageData_ => 1,
_ => 0,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ExtensionCode {
PlainText_,
GraphicControl_,
Comment_,
Application_,
Unknown_(u8),
}
impl From<u8> for ExtensionCode {
fn from(n: u8) -> Self {
use self::ExtensionCode::*;
match n {
0x01 => PlainText_,
0xF9 => GraphicControl_,
0xFE => Comment_,
0xFF => Application_,
_ => Unknown_(n),
}
}
}
impl From<ExtensionCode> for u8 {
fn from(t: ExtensionCode) -> Self {
use self::ExtensionCode::*;
match t {
PlainText_ => 0x01,
GraphicControl_ => 0xF9,
Comment_ => 0xFE,
Application_ => 0xFF,
Unknown_(n) => n,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Header {
version: [u8; 3],
}
impl Default for Header {
fn default() -> Self {
let version = *b"89a";
Header { version }
}
}
impl Header {
pub fn with_version(version: [u8; 3]) -> Self {
Header { version }
}
pub fn version(self) -> [u8; 3] {
self.version
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct LogicalScreenDesc {
screen_width: u16,
screen_height: u16,
flags: u8,
background_color_idx: u8,
pixel_aspect_ratio: u8,
}
impl LogicalScreenDesc {
const COLOR_TABLE_PRESENT: u8 = 0b1000_0000;
const COLOR_RESOLUTION: u8 = 0b0111_0000;
const COLOR_TABLE_ORDERING: u8 = 0b0000_1000;
const COLOR_TABLE_SIZE: u8 = 0b0000_0111;
pub fn with_screen_width(mut self, screen_width: u16) -> Self {
self.screen_width = screen_width;
self
}
pub fn screen_width(self) -> u16 {
self.screen_width
}
pub fn with_screen_height(mut self, screen_height: u16) -> Self {
self.screen_height = screen_height;
self
}
pub fn screen_height(self) -> u16 {
self.screen_height
}
pub fn equal_size(self, rhs: Self) -> bool {
self.screen_width == rhs.screen_width
&& self.screen_height == rhs.screen_height
}
pub fn with_flags(mut self, flags: u8) -> Self {
self.flags = flags;
self
}
pub fn flags(self) -> u8 {
self.flags
}
fn color_table_existence(self) -> ColorTableExistence {
if self.flags & Self::COLOR_TABLE_PRESENT != 0 {
ColorTableExistence::Present
} else {
ColorTableExistence::Absent
}
}
pub fn color_resolution(self) -> u16 {
2 << ((self.flags & Self::COLOR_RESOLUTION) >> 4 as u16)
}
fn color_table_ordering(self) -> ColorTableOrdering {
if self.flags & Self::COLOR_TABLE_ORDERING != 0 {
ColorTableOrdering::Sorted
} else {
ColorTableOrdering::NotSorted
}
}
fn color_table_len(self) -> usize {
2 << ((self.flags & Self::COLOR_TABLE_SIZE) as usize)
}
pub fn color_table_config(self) -> ColorTableConfig {
let existence = self.color_table_existence();
let ordering = self.color_table_ordering();
let table_len = self.color_table_len();
ColorTableConfig {
existence,
ordering,
table_len,
}
}
pub fn with_color_table_config(mut self, tbl: ColorTableConfig) -> Self {
let mut flags = tbl.len_bits() & Self::COLOR_TABLE_SIZE;
flags |= (flags << 4) & Self::COLOR_RESOLUTION;
if tbl.existence == ColorTableExistence::Present {
flags |= Self::COLOR_TABLE_PRESENT;
}
if tbl.ordering == ColorTableOrdering::Sorted {
flags |= Self::COLOR_TABLE_ORDERING;
}
self.flags = flags;
self
}
pub fn with_background_color_idx(
mut self,
background_color_idx: u8,
) -> Self {
self.background_color_idx = background_color_idx;
self
}
pub fn background_color_idx(self) -> u8 {
self.background_color_idx
}
pub fn with_pixel_aspect_ratio(mut self, pixel_aspect_ratio: u8) -> Self {
self.pixel_aspect_ratio = pixel_aspect_ratio;
self
}
pub fn pixel_aspect_ratio(self) -> u8 {
self.pixel_aspect_ratio
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GlobalColorTable {
colors: Vec<u8>,
}
impl GlobalColorTable {
pub fn with_colors(colors: &[u8]) -> Self {
assert_eq!(colors.len() / CHANNELS * CHANNELS, colors.len());
let colors = colors.to_vec();
GlobalColorTable { colors }
}
pub fn len(&self) -> usize {
self.colors.len() / CHANNELS
}
pub fn is_empty(&self) -> bool {
self.colors.is_empty()
}
pub fn colors(&self) -> &[u8] {
&self.colors
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct PlainText {
sub_blocks: Vec<Vec<u8>>,
}
impl PlainText {
pub fn add_sub_block(&mut self, b: &[u8]) {
assert!(!b.is_empty() && b.len() < 256);
self.sub_blocks.push(b.to_vec());
}
pub fn sub_blocks(&self) -> &Vec<Vec<u8>> {
&self.sub_blocks
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct GraphicControl {
flags: u8,
delay_time_cs: u16,
transparent_color_idx: u8,
}
impl GraphicControl {
#[allow(dead_code)]
const RESERVED: u8 = 0b1110_0000;
const DISPOSAL_METHOD: u8 = 0b0001_1100;
const USER_INPUT: u8 = 0b0000_0010;
const TRANSPARENT_COLOR: u8 = 0b0000_0001;
pub fn set_flags(&mut self, flags: u8) {
self.flags = flags;
}
pub fn flags(self) -> u8 {
self.flags
}
pub fn disposal_method(self) -> DisposalMethod {
((self.flags & Self::DISPOSAL_METHOD) >> 2).into()
}
pub fn set_disposal_method(&mut self, disposal_method: DisposalMethod) {
let d: u8 = disposal_method.into();
self.flags = (self.flags | !Self::DISPOSAL_METHOD) | (d << 2);
}
pub fn user_input(self) -> bool {
(self.flags & Self::USER_INPUT) != 0
}
pub fn set_user_input(&mut self, user_input: bool) {
let u = (user_input as u8) << 1;
self.flags = (self.flags | !Self::USER_INPUT) | u;
}
pub fn delay_time_cs(self) -> u16 {
self.delay_time_cs
}
pub fn set_delay_time_cs(&mut self, delay_time_cs: u16) {
self.delay_time_cs = delay_time_cs;
}
pub fn transparent_color(self) -> Option<u8> {
let t = (self.flags & Self::TRANSPARENT_COLOR) != 0;
if t {
Some(self.transparent_color_idx)
} else {
None
}
}
pub fn transparent_color_idx(self) -> u8 {
self.transparent_color_idx
}
pub fn set_transparent_color_idx(&mut self, transparent_color_idx: u8) {
self.transparent_color_idx = transparent_color_idx;
}
pub fn set_transparent_color(&mut self, transparent_color: Option<u8>) {
match transparent_color {
Some(t) => {
self.flags |= Self::TRANSPARENT_COLOR;
self.transparent_color_idx = t;
}
None => {
self.flags |= !Self::TRANSPARENT_COLOR;
self.transparent_color_idx = 0;
}
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Comment {
comments: Vec<Vec<u8>>,
}
impl Comment {
pub fn add_comment(&mut self, b: &[u8]) {
assert!(!b.is_empty() && b.len() < 256);
self.comments.push(b.to_vec());
}
pub fn comments(&self) -> &Vec<Vec<u8>> {
&self.comments
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Application {
app_data: Vec<Vec<u8>>,
}
impl Application {
fn is_looping(app_id: &[u8]) -> bool {
app_id == b"NETSCAPE2.0" || app_id == b"ANIMEXTS1.0"
}
pub fn with_loop_count(loop_count: u16) -> Self {
let mut app_data = vec![];
app_data.push(b"NETSCAPE2.0".to_vec());
let mut v = vec![1];
v.push((loop_count >> 8) as u8);
v.push(loop_count as u8);
app_data.push(v);
Application { app_data }
}
pub fn add_app_data(&mut self, b: &[u8]) {
assert!(!b.is_empty() && b.len() < 256);
self.app_data.push(b.to_vec());
}
pub fn app_data(&self) -> &Vec<Vec<u8>> {
&self.app_data
}
pub fn loop_count(&self) -> Option<u16> {
let d = &self.app_data;
let exists = d.len() == 2 &&
Self::is_looping(&d[0]) &&
d[1].len() == 3 &&
d[1][0] == 1;
if exists {
Some(u16::from(d[1][1]) << 8 | u16::from(d[1][2]))
} else {
None
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Unknown {
sub_blocks: Vec<Vec<u8>>,
}
impl Unknown {
pub fn ext_id(&self) -> &[u8] {
if self.sub_blocks.is_empty() {
&[]
} else {
&self.sub_blocks[0]
}
}
pub fn add_sub_block(&mut self, b: &[u8]) {
assert!(!b.is_empty() && b.len() < 256);
self.sub_blocks.push(b.to_vec());
}
pub fn sub_blocks(&self) -> &[Vec<u8>] {
if self.sub_blocks.is_empty() {
&[]
} else {
&self.sub_blocks[1..]
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ImageDesc {
left: u16,
top: u16,
width: u16,
height: u16,
flags: u8,
}
impl ImageDesc {
const COLOR_TABLE_PRESENT: u8 = 0b1000_0000;
const INTERLACED: u8 = 0b0100_0000;
const COLOR_TABLE_ORDERING: u8 = 0b0010_0000;
#[allow(dead_code)]
const RESERVED: u8 = 0b0001_1000;
const COLOR_TABLE_SIZE: u8 = 0b0000_0111;
pub fn with_left(mut self, left: u16) -> Self {
self.left = left;
self
}
pub fn left(&self) -> u16 {
self.left
}
pub fn with_top(mut self, top: u16) -> Self {
self.top = top;
self
}
pub fn top(&self) -> u16 {
self.top
}
pub fn with_width(mut self, width: u16) -> Self {
self.width = width;
self
}
pub fn width(&self) -> u16 {
self.width
}
pub fn with_height(mut self, height: u16) -> Self {
self.height = height;
self
}
pub fn height(&self) -> u16 {
self.height
}
pub fn with_flags(mut self, flags: u8) -> Self {
self.flags = flags;
self
}
pub fn flags(&self) -> u8 {
self.flags
}
pub fn with_interlaced(mut self, interlaced: bool) -> Self {
self.flags = if interlaced {
self.flags | Self::INTERLACED
} else {
self.flags & !Self::INTERLACED
};
self
}
pub fn interlaced(&self) -> bool {
(self.flags & Self::INTERLACED) != 0
}
fn color_table_existence(&self) -> ColorTableExistence {
if self.flags & Self::COLOR_TABLE_PRESENT != 0 {
ColorTableExistence::Present
} else {
ColorTableExistence::Absent
}
}
fn color_table_ordering(&self) -> ColorTableOrdering {
if self.flags & Self::COLOR_TABLE_ORDERING != 0 {
ColorTableOrdering::Sorted
} else {
ColorTableOrdering::NotSorted
}
}
fn color_table_len(&self) -> usize {
2 << u16::from(self.flags & Self::COLOR_TABLE_SIZE)
}
pub fn color_table_config(&self) -> ColorTableConfig {
let existence = self.color_table_existence();
let ordering = self.color_table_ordering();
let table_len = self.color_table_len();
ColorTableConfig {
existence,
ordering,
table_len,
}
}
pub fn with_color_table_config(mut self, tbl: ColorTableConfig) -> Self {
let mut flags = self.flags & (Self::INTERLACED | Self::RESERVED);
flags |= tbl.len_bits() & Self::COLOR_TABLE_SIZE;
if tbl.existence == ColorTableExistence::Present {
flags |= Self::COLOR_TABLE_PRESENT;
}
if tbl.ordering == ColorTableOrdering::Sorted {
flags |= Self::COLOR_TABLE_ORDERING;
}
self.flags = flags;
self
}
pub fn image_sz(&self) -> usize {
self.width as usize * self.height as usize
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LocalColorTable {
colors: Vec<u8>,
}
impl LocalColorTable {
pub fn with_colors(colors: &[u8]) -> Self {
assert_eq!(colors.len() / CHANNELS * CHANNELS, colors.len());
let colors = colors.to_vec();
LocalColorTable { colors }
}
pub fn len(&self) -> usize {
self.colors.len() / CHANNELS
}
pub fn is_empty(&self) -> bool {
self.colors.is_empty()
}
pub fn colors(&self) -> &[u8] {
&self.colors
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ImageData {
data: Vec<u8>,
}
impl From<&Raster<Gray8>> for ImageData {
fn from(raster: &Raster<Gray8>) -> Self {
let buf = raster.as_u8_slice();
let mut image_data = ImageData::new(buf.len());
image_data.data_mut().extend_from_slice(buf);
image_data
}
}
impl ImageData {
pub fn new(image_sz: usize) -> Self {
let data = Vec::with_capacity(image_sz);
ImageData { data }
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn data_mut(&mut self) -> &mut Vec<u8> {
&mut self.data
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Trailer {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Block {
Header(Header),
LogicalScreenDesc(LogicalScreenDesc),
GlobalColorTable(GlobalColorTable),
PlainText(PlainText),
GraphicControl(GraphicControl),
Comment(Comment),
Application(Application),
Unknown(Unknown),
ImageDesc(ImageDesc),
LocalColorTable(LocalColorTable),
ImageData(ImageData),
Trailer(Trailer),
}
impl Block {
pub fn has_sub_blocks(&self) -> bool {
use self::Block::*;
matches!(
self,
PlainText(_) | GraphicControl(_) | Comment(_) | Application(_)
| Unknown(_) | ImageData(_)
)
}
}
impl From<Header> for Block {
fn from(b: Header) -> Self {
Block::Header(b)
}
}
impl From<LogicalScreenDesc> for Block {
fn from(b: LogicalScreenDesc) -> Self {
Block::LogicalScreenDesc(b)
}
}
impl From<GlobalColorTable> for Block {
fn from(b: GlobalColorTable) -> Self {
Block::GlobalColorTable(b)
}
}
impl From<PlainText> for Block {
fn from(b: PlainText) -> Self {
Block::PlainText(b)
}
}
impl From<GraphicControl> for Block {
fn from(b: GraphicControl) -> Self {
Block::GraphicControl(b)
}
}
impl From<Comment> for Block {
fn from(b: Comment) -> Self {
Block::Comment(b)
}
}
impl From<Application> for Block {
fn from(b: Application) -> Self {
Block::Application(b)
}
}
impl From<Unknown> for Block {
fn from(b: Unknown) -> Self {
Block::Unknown(b)
}
}
impl From<ImageDesc> for Block {
fn from(b: ImageDesc) -> Self {
Block::ImageDesc(b)
}
}
impl From<LocalColorTable> for Block {
fn from(b: LocalColorTable) -> Self {
Block::LocalColorTable(b)
}
}
impl From<ImageData> for Block {
fn from(b: ImageData) -> Self {
Block::ImageData(b)
}
}
impl From<Trailer> for Block {
fn from(b: Trailer) -> Self {
Block::Trailer(b)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Preamble {
pub header: Header,
pub logical_screen_desc: LogicalScreenDesc,
pub global_color_table: Option<GlobalColorTable>,
pub loop_count_ext: Option<Application>,
pub comments: Vec<Comment>,
}
impl Preamble {
pub fn screen_width(&self) -> u16 {
self.logical_screen_desc.screen_width()
}
pub fn screen_height(&self) -> u16 {
self.logical_screen_desc.screen_height()
}
}
#[derive(Debug)]
pub struct Frame {
pub graphic_control_ext: Option<GraphicControl>,
pub image_desc: ImageDesc,
pub local_color_table: Option<LocalColorTable>,
pub image_data: ImageData,
}
impl Frame {
pub fn new(
graphic_control_ext: Option<GraphicControl>,
image_desc: ImageDesc,
local_color_table: Option<LocalColorTable>,
image_data: ImageData,
) -> Self {
Frame {
graphic_control_ext,
image_desc,
local_color_table,
image_data,
}
}
pub fn disposal_method(&self) -> DisposalMethod {
match &self.graphic_control_ext {
Some(gc) => gc.disposal_method(),
None => DisposalMethod::NoAction,
}
}
pub fn transparent_color(&self) -> Option<u8> {
match &self.graphic_control_ext {
Some(gc) => gc.transparent_color(),
None => None,
}
}
pub fn left(&self) -> u16 {
self.image_desc.left()
}
pub fn top(&self) -> u16 {
self.image_desc.top()
}
pub fn width(&self) -> u16 {
self.image_desc.width()
}
pub fn height(&self) -> u16 {
self.image_desc.height()
}
pub fn region(&self) -> Region {
let x = self.left().into();
let y = self.top().into();
let w = self.width().into();
let h = self.height().into();
Region::new(x, y, w, h)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn block_size() {
dbg!(std::mem::size_of::<Block>());
assert!(std::mem::size_of::<Block>() <= 32);
}
#[test]
fn color_table_len() {
let t = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
0,
);
assert_eq!(t.len_bits(), 0);
let t = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
4,
);
assert_eq!(t.len_bits(), 1);
let t = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
7,
);
assert_eq!(t.len_bits(), 2);
let t = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
16,
);
assert_eq!(t.len_bits(), 3);
let t = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
17,
);
assert_eq!(t.len_bits(), 4);
let t = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
64,
);
assert_eq!(t.len_bits(), 5);
let t = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
65,
);
assert_eq!(t.len_bits(), 6);
let t = ColorTableConfig::new(
ColorTableExistence::Present,
ColorTableOrdering::NotSorted,
130,
);
assert_eq!(t.len_bits(), 7);
let t = ColorTableConfig::default();
assert_eq!(t.len_bits(), 0);
}
#[test]
fn loop_count() {
let b = Application::default();
assert_eq!(b.loop_count(), None);
let b = Application::with_loop_count(0);
assert_eq!(b.loop_count(), Some(0));
let b = Application::with_loop_count(4);
assert_eq!(b.loop_count(), Some(4));
}
}