diff --git a/src/commands/bitmap_linear.rs b/src/commands/bitmap_linear.rs index 36f91d1..fafc324 100644 --- a/src/commands/bitmap_linear.rs +++ b/src/commands/bitmap_linear.rs @@ -1,14 +1,31 @@ +use crate::compression::into_compressed; use crate::{ - commands::TryFromPacketError, command_code::CommandCode, + command_code::CommandCode, commands::TryFromPacketError, compression::into_decompressed, BitVec, CompressionCode, Header, Offset, Packet, TypedCommand, }; +/// Binary operations for use with the [BitmapLinear] command. +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub enum BinaryOperation { + #[default] + Overwrite, + And, + Or, + Xor, +} + /// Set pixel data starting at the pixel offset on screen. /// /// The screen will continuously overwrite more pixel data without regarding the offset, meaning /// once the starting row is full, overwriting will continue on column 0. /// +/// The [BinaryOperation] will be applied on the display comparing old and sent bit. +/// +/// `new_bit = old_bit op sent_bit` +/// +/// For example, [BinaryOperation::Or] can be used to turn on some pixels without affecting other pixels. +/// /// The contained [BitVec] is always uncompressed. #[derive(Clone, PartialEq, Debug)] pub struct BitmapLinear { @@ -16,18 +33,34 @@ pub struct BitmapLinear { pub offset: Offset, /// the pixels to send to the display as one long row pub bitvec: BitVec, + /// The operation to apply on the display per bit comparing old and new state. + pub operation: BinaryOperation, /// how to compress the command when converting to packet pub compression: CompressionCode, } impl From for Packet { - fn from(bitmap: BitmapLinear) -> Self { - Packet::bitmap_linear_into_packet( - CommandCode::BitmapLinear, - bitmap.offset, - bitmap.compression, - bitmap.bitvec.into(), - ) + fn from(command: BitmapLinear) -> Self { + let command_code = match command.operation { + BinaryOperation::Overwrite => CommandCode::BitmapLinear, + BinaryOperation::And => CommandCode::BitmapLinearAnd, + BinaryOperation::Or => CommandCode::BitmapLinearOr, + BinaryOperation::Xor => CommandCode::BitmapLinearXor, + }; + + let payload: Vec<_> = command.bitvec.into(); + let length = payload.len() as u16; + let payload = into_compressed(command.compression, payload); + Packet { + header: Header { + command_code: command_code.into(), + a: command.offset as u16, + b: length, + c: command.compression.into(), + d: 0, + }, + payload, + } } } @@ -35,30 +68,10 @@ impl TryFrom for BitmapLinear { type Error = TryFromPacketError; fn try_from(packet: Packet) -> Result { - let (offset, bitvec, compression) = - Self::packet_into_linear_bitmap(packet)?; - Ok(Self { - offset, - bitvec, - compression, - }) - } -} - -impl From for TypedCommand { - fn from(command: BitmapLinear) -> Self { - Self::BitmapLinear(command) - } -} - -impl BitmapLinear { - /// Helper method for Packets into `BitmapLinear*`-Commands - pub(crate) fn packet_into_linear_bitmap( - packet: Packet, - ) -> Result<(Offset, BitVec, CompressionCode), TryFromPacketError> { let Packet { header: Header { + command_code, a: offset, b: length, c: sub, @@ -67,16 +80,30 @@ impl BitmapLinear { }, payload, } = packet; + let command_code = CommandCode::try_from(command_code) + .map_err(|_| TryFromPacketError::InvalidCommand(command_code))?; + let operation = match command_code { + CommandCode::BitmapLinear => BinaryOperation::Overwrite, + CommandCode::BitmapLinearAnd => BinaryOperation::And, + CommandCode::BitmapLinearOr => BinaryOperation::Or, + CommandCode::BitmapLinearXor => BinaryOperation::Xor, + _ => { + return Err(TryFromPacketError::InvalidCommand( + command_code.into(), + )) + } + }; + if reserved != 0 { return Err(TryFromPacketError::ExtraneousHeaderValues); } - let sub = match CompressionCode::try_from(sub) { + let compression = match CompressionCode::try_from(sub) { Err(()) => { return Err(TryFromPacketError::InvalidCompressionCode(sub)); } Ok(value) => value, }; - let payload = match into_decompressed(sub, payload) { + let payload = match into_decompressed(compression, payload) { None => return Err(TryFromPacketError::DecompressionFailed), Some(value) => value, }; @@ -86,6 +113,17 @@ impl BitmapLinear { payload.len(), )); } - Ok((offset as Offset, BitVec::from_vec(payload), sub)) + Ok(Self { + offset: offset as Offset, + bitvec: BitVec::from_vec(payload), + compression, + operation, + }) + } +} + +impl From for TypedCommand { + fn from(command: BitmapLinear) -> Self { + Self::BitmapLinear(command) } } diff --git a/src/commands/bitmap_linear_and.rs b/src/commands/bitmap_linear_and.rs deleted file mode 100644 index 123439c..0000000 --- a/src/commands/bitmap_linear_and.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{ - commands::{BitmapLinear, TryFromPacketError}, - command_code::CommandCode, - BitVec, CompressionCode, Offset, Packet, TypedCommand, -}; - -/// Set pixel data according to an and-mask starting at the offset. -/// -/// The screen will continuously overwrite more pixel data without regarding the offset, meaning -/// once the starting row is full, overwriting will continue on column 0. -/// -/// The contained [BitVec] is always uncompressed. -#[derive(Clone, PartialEq, Debug)] -pub struct BitmapLinearAnd { - /// where to start overwriting pixel data - pub offset: Offset, - /// the pixels to send to the display as one long row - pub bitvec: BitVec, - /// how to compress the command when converting to packet - pub compression: CompressionCode, -} - -impl TryFrom for BitmapLinearAnd { - type Error = TryFromPacketError; - - fn try_from(packet: Packet) -> Result { - let (offset, bitvec, compression) = - BitmapLinear::packet_into_linear_bitmap(packet)?; - Ok(Self { - offset, - bitvec, - compression, - }) - } -} - -impl From for Packet { - fn from(bitmap: BitmapLinearAnd) -> Self { - Packet::bitmap_linear_into_packet( - CommandCode::BitmapLinearAnd, - bitmap.offset, - bitmap.compression, - bitmap.bitvec.into(), - ) - } -} - -impl From for TypedCommand { - fn from(command: BitmapLinearAnd) -> Self { - Self::BitmapLinearAnd(command) - } -} diff --git a/src/commands/bitmap_linear_or.rs b/src/commands/bitmap_linear_or.rs deleted file mode 100644 index 4da6a72..0000000 --- a/src/commands/bitmap_linear_or.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{ - commands::{BitmapLinear, TryFromPacketError}, - command_code::CommandCode, - BitVec, CompressionCode, Offset, Packet, TypedCommand, -}; - -/// Set pixel data according to an or-mask starting at the offset. -/// -/// The screen will continuously overwrite more pixel data without regarding the offset, meaning -/// once the starting row is full, overwriting will continue on column 0. -/// -/// The contained [BitVec] is always uncompressed. -#[derive(Clone, PartialEq, Debug)] -pub struct BitmapLinearOr { - /// where to start overwriting pixel data - pub offset: Offset, - /// the pixels to send to the display as one long row - pub bitvec: BitVec, - /// how to compress the command when converting to packet - pub compression: CompressionCode, -} - -impl TryFrom for BitmapLinearOr { - type Error = TryFromPacketError; - - fn try_from(packet: Packet) -> Result { - let (offset, bitvec, compression) = - BitmapLinear::packet_into_linear_bitmap(packet)?; - Ok(Self { - offset, - bitvec, - compression, - }) - } -} - -impl From for Packet { - fn from(bitmap: BitmapLinearOr) -> Self { - Packet::bitmap_linear_into_packet( - CommandCode::BitmapLinearOr, - bitmap.offset, - bitmap.compression, - bitmap.bitvec.into(), - ) - } -} - -impl From for TypedCommand { - fn from(command: BitmapLinearOr) -> Self { - Self::BitmapLinearOr(command) - } -} diff --git a/src/commands/bitmap_linear_xor.rs b/src/commands/bitmap_linear_xor.rs deleted file mode 100644 index 2a33d55..0000000 --- a/src/commands/bitmap_linear_xor.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{ - commands::{BitmapLinear, TryFromPacketError}, - command_code::CommandCode, - BitVec, CompressionCode, Offset, Packet, TypedCommand, -}; - -/// Set pixel data according to a xor-mask starting at the offset. -/// -/// The screen will continuously overwrite more pixel data without regarding the offset, meaning -/// once the starting row is full, overwriting will continue on column 0. -/// -/// The contained [BitVec] is always uncompressed. -#[derive(Clone, PartialEq, Debug)] -pub struct BitmapLinearXor { - /// where to start overwriting pixel data - pub offset: Offset, - /// the pixels to send to the display as one long row - pub bitvec: BitVec, - /// how to compress the command when converting to packet - pub compression: CompressionCode, -} - -impl TryFrom for BitmapLinearXor { - type Error = TryFromPacketError; - - fn try_from(packet: Packet) -> Result { - let (offset, bitvec, compression) = - BitmapLinear::packet_into_linear_bitmap(packet)?; - Ok(Self { - offset, - bitvec, - compression, - }) - } -} - -impl From for Packet { - fn from(bitmap: BitmapLinearXor) -> Self { - Packet::bitmap_linear_into_packet( - CommandCode::BitmapLinearXor, - bitmap.offset, - bitmap.compression, - bitmap.bitvec.into(), - ) - } -} - -impl From for TypedCommand { - fn from(command: BitmapLinearXor) -> Self { - Self::BitmapLinearXor(command) - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6e0cbd7..3d818fa 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -49,10 +49,7 @@ mod bitmap_legacy; mod bitmap_linear; -mod bitmap_linear_and; -mod bitmap_linear_or; mod bitmap_linear_win; -mod bitmap_linear_xor; mod char_brightness; mod clear; mod cp437_data; @@ -67,10 +64,7 @@ use std::fmt::Debug; pub use bitmap_legacy::*; pub use bitmap_linear::*; -pub use bitmap_linear_and::*; -pub use bitmap_linear_or::*; pub use bitmap_linear_win::*; -pub use bitmap_linear_xor::*; pub use char_brightness::*; pub use clear::*; pub use cp437_data::*; @@ -105,12 +99,6 @@ pub enum TypedCommand { BitmapLinear(BitmapLinear), - BitmapLinearAnd(BitmapLinearAnd), - - BitmapLinearOr(BitmapLinearOr), - - BitmapLinearXor(BitmapLinearXor), - HardReset(HardReset), FadeOut(FadeOut), @@ -194,18 +182,12 @@ impl TryFrom for TypedCommand { CommandCode::BitmapLegacy => { packet_to_command_case!(BitmapLegacy, packet) } - CommandCode::BitmapLinear => { + CommandCode::BitmapLinear + | CommandCode::BitmapLinearOr + | CommandCode::BitmapLinearAnd + | CommandCode::BitmapLinearXor => { packet_to_command_case!(BitmapLinear, packet) } - CommandCode::BitmapLinearAnd => { - packet_to_command_case!(BitmapLinearAnd, packet) - } - CommandCode::BitmapLinearOr => { - packet_to_command_case!(BitmapLinearOr, packet) - } - CommandCode::BitmapLinearXor => { - packet_to_command_case!(BitmapLinearXor, packet) - } CommandCode::BitmapLinearWinUncompressed => { packet_to_command_case!(BitmapLinearWin, packet) } @@ -239,9 +221,6 @@ impl From for Packet { TypedCommand::GlobalBrightness(c) => c.into(), TypedCommand::CharBrightness(c) => c.into(), TypedCommand::BitmapLinear(c) => c.into(), - TypedCommand::BitmapLinearAnd(c) => c.into(), - TypedCommand::BitmapLinearOr(c) => c.into(), - TypedCommand::BitmapLinearXor(c) => c.into(), TypedCommand::HardReset(c) => c.into(), TypedCommand::FadeOut(c) => c.into(), #[allow(deprecated)] @@ -250,20 +229,25 @@ impl From for Packet { } } -pub(self) fn check_command_code_only(packet: Packet, code: CommandCode) -> Option { +pub(self) fn check_command_code_only( + packet: Packet, + code: CommandCode, +) -> Option { let Packet { header: - Header { - command_code: _, - a, - b, - c, - d, - }, + Header { + command_code: _, + a, + b, + c, + d, + }, payload, } = packet; if packet.header.command_code != u16::from(code) { - Some(TryFromPacketError::InvalidCommand(packet.header.command_code)) + Some(TryFromPacketError::InvalidCommand( + packet.header.command_code, + )) } else if !payload.is_empty() { Some(TryFromPacketError::UnexpectedPayloadSize(0, payload.len())) } else if a != 0 || b != 0 || c != 0 || d != 0 { @@ -275,8 +259,8 @@ pub(self) fn check_command_code_only(packet: Packet, code: CommandCode) -> Optio #[cfg(test)] mod tests { - use crate::commands::{BitmapLinear, BitmapLinearWin, BitmapLinearXor, CharBrightness, GlobalBrightness, TryFromPacketError}; use crate::command_code::CommandCode; + use crate::commands::{BinaryOperation, TryFromPacketError}; use crate::*; fn round_trip(original: TypedCommand) { @@ -319,9 +303,11 @@ mod tests { #[test] fn round_trip_brightness() { - round_trip(TypedCommand::GlobalBrightness(GlobalBrightness { - brightness: Brightness::try_from(6).unwrap(), - })); + round_trip(TypedCommand::GlobalBrightness( + commands::GlobalBrightness { + brightness: Brightness::try_from(6).unwrap(), + }, + )); } #[test] @@ -332,7 +318,7 @@ mod tests { #[test] fn round_trip_char_brightness() { - round_trip(TypedCommand::CharBrightness(CharBrightness { + round_trip(TypedCommand::CharBrightness(commands::CharBrightness { origin: Origin::new(5, 2), grid: BrightnessGrid::new(7, 5), })); @@ -357,33 +343,28 @@ mod tests { #[test] fn round_trip_bitmap_linear() { for compression in all_compressions().iter().copied() { - round_trip(TypedCommand::BitmapLinear(BitmapLinear { - offset: 23, - bitvec: BitVec::repeat(false, 40), - compression, - })); - round_trip(TypedCommand::BitmapLinearAnd( - commands::BitmapLinearAnd { - offset: 23, - bitvec: BitVec::repeat(false, 40), + for operation in [ + BinaryOperation::Overwrite, + BinaryOperation::And, + BinaryOperation::Or, + BinaryOperation::Xor, + ] { + round_trip(TypedCommand::BitmapLinear( + commands::BitmapLinear { + offset: 23, + bitvec: BitVec::repeat(false, 40), + compression, + operation, + }, + )); + } + round_trip(TypedCommand::BitmapLinearWin( + commands::BitmapLinearWin { + origin: Origin::ZERO, + bitmap: Bitmap::max_sized(), compression, }, )); - round_trip(TypedCommand::BitmapLinearOr(commands::BitmapLinearOr { - offset: 23, - bitvec: BitVec::repeat(false, 40), - compression, - })); - round_trip(TypedCommand::BitmapLinearXor(BitmapLinearXor { - offset: 23, - bitvec: BitVec::repeat(false, 40), - compression, - })); - round_trip(TypedCommand::BitmapLinearWin(BitmapLinearWin { - origin: Origin::ZERO, - bitmap: Bitmap::max_sized(), - compression, - })); } } @@ -534,10 +515,11 @@ mod tests { #[test] fn error_decompression_failed_and() { for compression in all_compressions().iter().copied() { - let p: Packet = commands::BitmapLinearAnd { + let p: Packet = commands::BitmapLinear { offset: 0, bitvec: BitVec::repeat(false, 8), compression, + operation: BinaryOperation::Overwrite, } .into(); let Packet { @@ -598,6 +580,7 @@ mod tests { offset: 0, bitvec: BitVec::repeat(false, 8), compression: CompressionCode::Uncompressed, + operation: BinaryOperation::Or, } .into(); let Header { @@ -629,6 +612,7 @@ mod tests { offset: 0, bitvec: BitVec::repeat(false, 8), compression: CompressionCode::Uncompressed, + operation: BinaryOperation::And, } .into(); let Header { @@ -660,6 +644,7 @@ mod tests { offset: 0, bitvec: BitVec::repeat(false, 8), compression: CompressionCode::Uncompressed, + operation: BinaryOperation::Xor, } .into(); let Header { @@ -699,7 +684,10 @@ mod tests { #[test] fn packet_into_char_brightness_invalid() { let grid = BrightnessGrid::new(2, 2); - let command = commands::CharBrightness{origin: Origin::ZERO, grid}; + let command = commands::CharBrightness { + origin: Origin::ZERO, + grid, + }; let mut packet: Packet = command.into(); let slot = packet.payload.get_mut(1).unwrap(); *slot = 23; @@ -711,7 +699,10 @@ mod tests { #[test] fn packet_into_brightness_invalid() { - let mut packet: Packet = commands::GlobalBrightness{brightness: Brightness::MAX}.into(); + let mut packet: Packet = commands::GlobalBrightness { + brightness: Brightness::MAX, + } + .into(); let slot = packet.payload.get_mut(0).unwrap(); *slot = 42; assert_eq!( diff --git a/src/packet.rs b/src/packet.rs index 5f54f58..5c58c82 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -24,8 +24,7 @@ //! ``` use crate::command_code::CommandCode; -use crate::compression::into_compressed; -use crate::{CompressionCode, Grid, Offset, Origin, Tiles}; +use crate::{Grid, Origin, Tiles}; use std::mem::size_of; /// A raw header. @@ -136,28 +135,6 @@ impl TryFrom> for Packet { } impl Packet { - /// Helper method for `BitmapLinear*`-Commands into [Packet] - #[allow(clippy::cast_possible_truncation)] - pub(crate) fn bitmap_linear_into_packet( - command: CommandCode, - offset: Offset, - compression: CompressionCode, - payload: Vec, - ) -> Packet { - let length = payload.len() as u16; - let payload = into_compressed(compression, payload); - Packet { - header: Header { - command_code: command.into(), - a: offset as u16, - b: length, - c: compression.into(), - d: 0, - }, - payload, - } - } - fn u16_from_be_slice(slice: &[u8]) -> u16 { let mut bytes = [0u8; 2]; bytes[0] = slice[0];