#![no_std]
struct Buffer<'a> {
data: &'a [u8],
current: usize,
}
impl<'a> Buffer<'a> {
fn new(data: &'a [u8]) -> Buffer<'a> {
Buffer { data, current: 0 }
}
}
struct BufferMut<'a> {
data: &'a mut [u8],
current: usize,
}
impl<'a> BufferMut<'a> {
fn new(data: &'a mut [u8]) -> BufferMut<'a> {
BufferMut { data, current: 0 }
}
}
struct Packet {
slope: bool,
length: u8,
data: u16,
}
#[derive(Debug)]
pub enum AztecError {
MaxLineLenTooLong,
MaxSlopeLenTooLong,
MaxSlopeLineLenTooLong,
ProvidedBufferIsNotLongEnough,
}
const NOT_LONG: AztecError = AztecError::ProvidedBufferIsNotLongEnough;
impl Packet {
fn line(length: u8, data: u16) -> Self {
Self {
slope: false,
length,
data,
}
}
fn slope(length: u8, data: u16) -> Self {
Self {
slope: true,
length,
data,
}
}
fn write(&self, buf: &mut BufferMut) -> Result<(), AztecError> {
let i = buf.current;
let val = ((self.slope as u8) << 7) | self.length;
let [byte1, byte2] = self.data.to_le_bytes();
*buf.data.get_mut(i).ok_or(NOT_LONG)? = val;
*buf.data.get_mut(i + 1).ok_or(NOT_LONG)? = byte1;
*buf.data.get_mut(i + 2).ok_or(NOT_LONG)? = byte2;
buf.current += 3;
Ok(())
}
fn from_buffer(buf: &mut Buffer) -> Result<Self, AztecError> {
let i = buf.current;
let x = buf.data.get(i).ok_or(NOT_LONG)?;
let byte1 = buf.data.get(i + 1).ok_or(NOT_LONG)?;
let byte2 = buf.data.get(i + 2).ok_or(NOT_LONG)?;
let slope = (x >> 7) == 1;
let length = x & 0b111_1111;
let data = u16::from_le_bytes([*byte1, *byte2]);
buf.current += 3;
Ok(Self {
slope,
length,
data,
})
}
}
pub struct AztecConfig {
threshold: u16,
max_line_len: u8,
max_slope_len: u8,
max_slope_line_len: u8,
}
impl AztecConfig {
pub fn threshold(&self) -> u16 {
self.threshold
}
pub fn max_line_len(&self) -> u8 {
self.max_line_len
}
pub fn max_slope_len(&self) -> u8 {
self.max_slope_len
}
pub fn max_slope_line_len(&self) -> u8 {
self.max_slope_line_len
}
}
pub struct AztecConfigBuilder {
config: AztecConfig,
}
impl AztecConfigBuilder {
pub fn new(threshold: u16) -> Self {
Self {
config: AztecConfig {
threshold,
max_line_len: 63,
max_slope_len: 63,
max_slope_line_len: 4,
},
}
}
pub fn threshold(mut self, threshold: u16) -> Self {
self.config.threshold = threshold;
self
}
pub fn max_line_len(mut self, max_line_len: u8) -> Self {
self.config.max_line_len = max_line_len;
self
}
pub fn max_slope_len(mut self, max_slope_len: u8) -> Self {
self.config.max_slope_len = max_slope_len;
self
}
pub fn max_slope_line_len(mut self, max_slope_line_len: u8) -> Self {
self.config.max_slope_line_len = max_slope_line_len;
self
}
pub fn build(self) -> Result<AztecConfig, AztecError> {
if self.config.max_line_len > 63 {
return Err(AztecError::MaxLineLenTooLong);
}
if self.config.max_slope_len > 63 {
return Err(AztecError::MaxSlopeLenTooLong);
}
if self.config.max_slope_line_len >= self.config.max_slope_len {
return Err(AztecError::MaxSlopeLineLenTooLong);
}
Ok(self.config)
}
}
enum SlopeState {
NoSlope,
OngoingSlope(Option<bool>),
SlopeDone,
}
pub fn compress(
data: &[u16],
result: &mut [u8],
config: &AztecConfig,
) -> Result<usize, AztecError> {
let mut buf = BufferMut::new(result);
let mut x = data[0];
let mut min = x;
let mut max = x;
let mut old_min = x;
let mut old_max = x;
Packet::line(1, x).write(&mut buf)?;
let mut slope_state = SlopeState::NoSlope;
let mut slope_length = 0;
let mut line_done = false;
let mut line_length = 1;
let mut line_value;
let mut previous_line_value = 0;
let mut i = 1;
while i < data.len() {
let mut increment = true;
x = data[i];
if x < min {
min = x;
}
if x > max {
max = x;
}
line_value = ((old_max as u32 + old_min as u32) / 2) as u16;
if line_length >= config.max_line_len || max - min > config.threshold {
line_done = true;
}
let last_sample = i == data.len() - 1;
if last_sample {
line_done = true;
if let SlopeState::OngoingSlope(_) = slope_state {
slope_state = SlopeState::SlopeDone;
}
}
if line_done {
if !last_sample {
if line_length <= config.max_slope_line_len {
match slope_state {
SlopeState::NoSlope => {
slope_state = SlopeState::OngoingSlope(None);
}
SlopeState::OngoingSlope(slope_dir) => {
let diff = line_value as i32 - previous_line_value as i32;
let mut sign_changed = false;
let current_slope_dir = if diff == 0 { None } else { Some(diff > 0) };
if let Some(current_slope_dir) = current_slope_dir {
if let Some(slope_dir) = slope_dir {
if current_slope_dir != slope_dir {
sign_changed = true;
}
}
if sign_changed {
slope_state = SlopeState::SlopeDone;
} else {
slope_state = SlopeState::OngoingSlope(Some(current_slope_dir));
}
}
}
SlopeState::SlopeDone => {}
}
} else {
if let SlopeState::OngoingSlope(_) = slope_state {
slope_state = SlopeState::SlopeDone;
}
}
}
if let SlopeState::OngoingSlope(_) = slope_state {
if slope_length as u16 + line_length as u16 >= config.max_slope_len as u16 {
slope_state = SlopeState::SlopeDone;
}
}
match slope_state {
SlopeState::NoSlope => {
Packet::line(line_length, line_value).write(&mut buf)?;
}
SlopeState::OngoingSlope(_) => {
slope_length += line_length;
}
SlopeState::SlopeDone => {
Packet::slope(slope_length, previous_line_value).write(&mut buf)?;
increment = false;
slope_state = SlopeState::NoSlope;
slope_length = 0;
}
}
if increment {
previous_line_value = line_value;
min = x;
max = x;
line_done = false;
line_length = 1;
}
} else {
line_length += 1;
}
if increment {
old_min = min;
old_max = max;
i += 1;
}
}
Ok(buf.current + 1)
}
pub fn decompress(data: &[u8], output: &mut [u16]) -> Result<usize, AztecError> {
let mut buf = Buffer::new(data);
let mut output_i = 0;
let mut previous_line_value: u16 = 0;
for _ in 0..data.len() / 3 {
let packet = Packet::from_buffer(&mut buf)?;
if packet.slope {
if packet.length == 1 {
output[output_i] = packet.data;
output_i += 1;
} else {
for i in 0..packet.length {
let value = previous_line_value as i32
+ ((i as i32) * (packet.data as i32 - previous_line_value as i32))
/ (packet.length as i32 - 1);
output[output_i] = value as u16;
output_i += 1;
}
}
} else {
for _ in 0..packet.length {
output[output_i] = packet.data;
output_i += 1;
}
}
previous_line_value = packet.data;
}
Ok(output_i)
}
#[cfg(test)]
mod tests {
use crate::*;
const LEN: usize = 128733;
const RES_LEN: usize = LEN * 3;
#[test]
fn straight_line() {
let data = [128; LEN];
let mut decompressed = [0; LEN];
let mut result = [0; RES_LEN];
let config = AztecConfigBuilder::new(10)
.max_slope_line_len(4)
.build()
.unwrap();
let len = compress(&data, &mut result, &config).unwrap();
let (compressed, _) = result.split_at(len);
let len = decompress(&compressed, &mut decompressed).unwrap();
assert_eq!(len, LEN);
assert_eq!(data, decompressed);
}
#[test]
fn straight_line_long_slope() {
let data = [128; LEN];
let mut decompressed = [0; LEN];
let mut result = [0; RES_LEN];
let config = AztecConfigBuilder::new(10)
.max_slope_line_len(62)
.build()
.unwrap();
let len = compress(&data, &mut result, &config).unwrap();
let (compressed, _) = result.split_at(len);
let len = decompress(&compressed, &mut decompressed).unwrap();
assert_eq!(len, LEN);
assert_eq!(data, decompressed);
}
#[test]
fn triangle() {
let mut data = [0; LEN];
let mut val: u16 = 0;
for i in 0..LEN {
data[i] = val;
if val == u16::MAX {
val = 0;
} else {
val += 1;
}
}
let mut decompressed = [0; LEN * 3];
let mut result = [0; RES_LEN];
let config = AztecConfigBuilder::new(10)
.max_slope_line_len(4)
.build()
.unwrap();
let len = compress(&data, &mut result, &config).unwrap();
let (compressed, _) = result.split_at(len);
let len = decompress(&compressed, &mut decompressed).unwrap();
assert_eq!(len, LEN);
}
#[test]
fn multiple_random() {
const RANDOM_TESTS: usize = 20;
let mut data = [0; LEN];
for _ in 0..RANDOM_TESTS {
for x in &mut data {
*x = rand::random();
}
let mut decompressed = [0; LEN * 3];
let mut result = [0; RES_LEN];
let config = AztecConfigBuilder::new(10)
.max_slope_line_len(4)
.build()
.unwrap();
let len = compress(&data, &mut result, &config).unwrap();
let (compressed, _) = result.split_at(len);
let len = decompress(&compressed, &mut decompressed).unwrap();
assert_eq!(len, LEN);
}
}
}