commit
This commit is contained in:
parent
e1436733a5
commit
dbb492a72d
7 changed files with 226 additions and 33 deletions
|
@ -4,6 +4,7 @@ use color_eyre::eyre::ContextCompat;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
use loupedeck_serial::characteristics::LoupedeckButton;
|
use loupedeck_serial::characteristics::LoupedeckButton;
|
||||||
|
use loupedeck_serial::events::{LoupedeckEvent, RotationDirection};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let available_devices = loupedeck_serial::device::LoupedeckDevice::discover()?;
|
let available_devices = loupedeck_serial::device::LoupedeckDevice::discover()?;
|
||||||
|
@ -12,7 +13,7 @@ fn main() -> Result<()> {
|
||||||
.wrap_err("at least one device should be connected")?
|
.wrap_err("at least one device should be connected")?
|
||||||
.connect()?;
|
.connect()?;
|
||||||
|
|
||||||
device.set_brightness(0.5);
|
device.set_brightness(1.0);
|
||||||
|
|
||||||
let buttons = [
|
let buttons = [
|
||||||
LoupedeckButton::N0,
|
LoupedeckButton::N0,
|
||||||
|
@ -25,17 +26,49 @@ fn main() -> Result<()> {
|
||||||
LoupedeckButton::N7,
|
LoupedeckButton::N7,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let mut value = 0u8;
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
loop {
|
loop {
|
||||||
sleep(Duration::from_millis(50));
|
while !device.events_channel().is_empty() {
|
||||||
for (index, button) in buttons.iter().enumerate() {
|
let event = device.events_channel().recv().unwrap();
|
||||||
device.set_button_color(*button, RGB8::new(
|
|
||||||
((((start.elapsed().as_millis() as f32 + index as f32 * 100.0) / 1000.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
match event {
|
||||||
((((start.elapsed().as_millis() as f32 + index as f32 * 100.0) / 250.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
LoupedeckEvent::KnobRotate { direction: RotationDirection::Clockwise, .. } => {
|
||||||
((((start.elapsed().as_millis() as f32 + index as f32 * 100.0) / 500.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
value = value.wrapping_add(1);
|
||||||
));
|
println!("{}, {:#010b}, {:#018b}", value, value, ((value as u16) & 0b11111000) << 8);
|
||||||
}
|
}
|
||||||
|
LoupedeckEvent::KnobRotate { direction: RotationDirection::Counterclockwise, .. } => {
|
||||||
|
value = value.wrapping_sub(1);
|
||||||
|
println!("{}, {:#010b}, {:#018b}", value, value, ((value as u16) & 0b11111000) << 8);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
let ms = start.elapsed().as_millis() as u64;
|
||||||
|
|
||||||
|
for (index, button) in buttons.iter().enumerate() {
|
||||||
|
let t = (ms + (index * 100) as u64) as f32;
|
||||||
|
|
||||||
|
device.set_button_color(*button, RGB8::new(
|
||||||
|
(((t / 1000.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||||
|
(((t / 500.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||||
|
(((t / 250.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
device.replace_framebuffer_area(
|
||||||
|
&device.characteristics().key_grid_display,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
90,
|
||||||
|
90,
|
||||||
|
&[RGB8::new(value, 0, 0); 90 * 90]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
device.refresh_display(&device.characteristics().key_grid_display)?;
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(50));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use enum_ordinalize::Ordinalize;
|
use enum_ordinalize::Ordinalize;
|
||||||
use enumset::{enum_set, EnumSet, EnumSetType};
|
use enumset::{enum_set, EnumSet, EnumSetType};
|
||||||
|
use crate::util::Endianness;
|
||||||
|
|
||||||
#[derive(Debug, Ordinalize, EnumSetType)]
|
#[derive(Debug, Ordinalize, EnumSetType)]
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
|
@ -32,12 +33,7 @@ pub enum LoupedeckButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LoupedeckDeviceDisplayEndianness {
|
#[non_exhaustive]
|
||||||
BigEndian,
|
|
||||||
LittleEndian
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LoupedeckDeviceDisplayConfiguration {
|
pub struct LoupedeckDeviceDisplayConfiguration {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
@ -47,10 +43,11 @@ pub struct LoupedeckDeviceDisplayConfiguration {
|
||||||
pub local_offset_y: u16,
|
pub local_offset_y: u16,
|
||||||
pub global_offset_x: u16,
|
pub global_offset_x: u16,
|
||||||
pub global_offset_y: u16,
|
pub global_offset_y: u16,
|
||||||
pub endianness: LoupedeckDeviceDisplayEndianness,
|
pub endianness: Endianness,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct LoupedeckDeviceCharacteristics {
|
pub struct LoupedeckDeviceCharacteristics {
|
||||||
pub vendor_id: u16,
|
pub vendor_id: u16,
|
||||||
pub product_id: u16,
|
pub product_id: u16,
|
||||||
|
@ -60,10 +57,15 @@ pub struct LoupedeckDeviceCharacteristics {
|
||||||
pub key_grid_rows: u8,
|
pub key_grid_rows: u8,
|
||||||
pub key_grid_columns: u8,
|
pub key_grid_columns: u8,
|
||||||
pub key_grid_display: LoupedeckDeviceDisplayConfiguration,
|
pub key_grid_display: LoupedeckDeviceDisplayConfiguration,
|
||||||
pub additional_displays: &'static [LoupedeckDeviceDisplayConfiguration]
|
pub additional_displays: &'static [LoupedeckDeviceDisplayConfiguration],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LoupedeckDeviceCharacteristics {
|
impl LoupedeckDeviceCharacteristics {
|
||||||
|
pub fn key_size(&self) -> (u16, u16) {
|
||||||
|
// Assuming the sizes are integers
|
||||||
|
(self.key_grid_display.width / self.key_grid_columns as u16, self.key_grid_display.height / self.key_grid_rows as u16)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_display_at_coordinates(&self, x: u16, y: u16) -> Option<&LoupedeckDeviceDisplayConfiguration> {
|
pub fn get_display_at_coordinates(&self, x: u16, y: u16) -> Option<&LoupedeckDeviceDisplayConfiguration> {
|
||||||
let check = |display: &&LoupedeckDeviceDisplayConfiguration|
|
let check = |display: &&LoupedeckDeviceDisplayConfiguration|
|
||||||
x >= display.global_offset_x && x <= display.global_offset_x + display.width &&
|
x >= display.global_offset_x && x <= display.global_offset_x + display.width &&
|
||||||
|
@ -77,22 +79,21 @@ impl LoupedeckDeviceCharacteristics {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_key_at_coordinates(&self, x: u16, y: u16) -> Option<u8> {
|
pub fn get_key_at_coordinates(&self, x: u16, y: u16) -> Option<u8> {
|
||||||
let column_width = self.key_grid_display.width as f64 / self.key_grid_columns as f64;
|
let (column_width, row_height) = self.key_size();
|
||||||
let row_height = self.key_grid_display.height as f64 / self.key_grid_rows as f64;
|
|
||||||
|
|
||||||
if x < self.key_grid_display.global_offset_x || y < self.key_grid_display.global_offset_y {
|
if x < self.key_grid_display.global_offset_x || y < self.key_grid_display.global_offset_y {
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let local_x = x - self.key_grid_display.global_offset_x;
|
let local_x = x - self.key_grid_display.global_offset_x;
|
||||||
let local_y = y - self.key_grid_display.global_offset_y;
|
let local_y = y - self.key_grid_display.global_offset_y;
|
||||||
|
|
||||||
if local_x >= self.key_grid_display.width || local_y >= self.key_grid_display.height {
|
if local_x >= self.key_grid_display.width || local_y >= self.key_grid_display.height {
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let column = (local_x as f64 / column_width).floor() as u8;
|
let column = (local_x / column_width) as u8;
|
||||||
let row = (local_y as f64 / row_height).floor() as u8;
|
let row = (local_y / row_height) as u8;
|
||||||
|
|
||||||
Some(row * self.key_grid_columns + column)
|
Some(row * self.key_grid_columns + column)
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,7 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
||||||
local_offset_y: 0,
|
local_offset_y: 0,
|
||||||
global_offset_x: 60,
|
global_offset_x: 60,
|
||||||
global_offset_y: 0,
|
global_offset_y: 0,
|
||||||
endianness: LoupedeckDeviceDisplayEndianness::LittleEndian,
|
endianness: Endianness::LittleEndian,
|
||||||
},
|
},
|
||||||
additional_displays: &[
|
additional_displays: &[
|
||||||
LoupedeckDeviceDisplayConfiguration {
|
LoupedeckDeviceDisplayConfiguration {
|
||||||
|
@ -145,7 +146,7 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
||||||
local_offset_y: 0,
|
local_offset_y: 0,
|
||||||
global_offset_x: 0,
|
global_offset_x: 0,
|
||||||
global_offset_y: 0,
|
global_offset_y: 0,
|
||||||
endianness: LoupedeckDeviceDisplayEndianness::LittleEndian,
|
endianness: Endianness::LittleEndian,
|
||||||
},
|
},
|
||||||
LoupedeckDeviceDisplayConfiguration {
|
LoupedeckDeviceDisplayConfiguration {
|
||||||
id: 0x4d,
|
id: 0x4d,
|
||||||
|
@ -156,9 +157,9 @@ static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = Loupedeck
|
||||||
local_offset_y: 0,
|
local_offset_y: 0,
|
||||||
global_offset_x: 420,
|
global_offset_x: 420,
|
||||||
global_offset_y: 0,
|
global_offset_y: 0,
|
||||||
endianness: LoupedeckDeviceDisplayEndianness::LittleEndian,
|
endianness: Endianness::LittleEndian,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static CHARACTERISTICS: [&LoupedeckDeviceCharacteristics; 1] = [
|
pub static CHARACTERISTICS: [&LoupedeckDeviceCharacteristics; 1] = [
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use bytes::Bytes;
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
use crate::characteristics::LoupedeckButton;
|
use crate::characteristics::LoupedeckButton;
|
||||||
|
|
||||||
|
@ -6,6 +7,17 @@ pub(crate) enum LoupedeckCommand {
|
||||||
SetBrightness(f32),
|
SetBrightness(f32),
|
||||||
SetButtonColor {
|
SetButtonColor {
|
||||||
button: LoupedeckButton,
|
button: LoupedeckButton,
|
||||||
color: RGB8
|
color: RGB8,
|
||||||
|
},
|
||||||
|
ReplaceFramebufferArea {
|
||||||
|
display_id: u8,
|
||||||
|
x: u16,
|
||||||
|
y: u16,
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
buffer: Bytes,
|
||||||
|
},
|
||||||
|
RefreshDisplay {
|
||||||
|
display_id: u8,
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,14 +3,16 @@ use std::io::{Read, Write};
|
||||||
use std::sync::mpsc::{channel, Sender};
|
use std::sync::mpsc::{channel, Sender};
|
||||||
use std::thread::{sleep, spawn};
|
use std::thread::{sleep, spawn};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use bytes::Bytes;
|
||||||
use crossbeam_channel::{Receiver};
|
use crossbeam_channel::{Receiver};
|
||||||
use rgb::RGB8;
|
use rgb::{ComponentSlice, RGB8};
|
||||||
use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPortType, StopBits};
|
use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPortType, StopBits};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use crate::characteristics::{CHARACTERISTICS, LoupedeckButton, LoupedeckDeviceCharacteristics};
|
use crate::characteristics::{CHARACTERISTICS, LoupedeckButton, LoupedeckDeviceCharacteristics, LoupedeckDeviceDisplayConfiguration};
|
||||||
use crate::commands::LoupedeckCommand;
|
use crate::commands::LoupedeckCommand;
|
||||||
use crate::events::LoupedeckEvent;
|
use crate::events::LoupedeckEvent;
|
||||||
use crate::messages::{read_messages_worker, write_messages_worker, WS_UPGRADE_REQUEST, WS_UPGRADE_RESPONSE_START};
|
use crate::messages::{read_messages_worker, write_messages_worker, WS_UPGRADE_REQUEST, WS_UPGRADE_RESPONSE_START};
|
||||||
|
use crate::util::convert_rgb888_to_rgb565;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AvailableLoupedeckDevice {
|
pub struct AvailableLoupedeckDevice {
|
||||||
|
@ -28,7 +30,7 @@ impl AvailableLoupedeckDevice {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connect(&self) -> Result<LoupedeckDevice, ConnectError> {
|
pub fn connect(&self) -> Result<LoupedeckDevice, ConnectError> {
|
||||||
LoupedeckDevice::connect(&self)
|
LoupedeckDevice::connect(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +56,27 @@ pub enum ConnectError {
|
||||||
AlreadyConnected,
|
AlreadyConnected,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RefreshDisplayError {
|
||||||
|
#[error("The specified display is not available for this device.")]
|
||||||
|
UnknownDisplay,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ReplaceFramebufferAreaError {
|
||||||
|
#[error("The specified display is not available for this device.")]
|
||||||
|
UnknownDisplay,
|
||||||
|
|
||||||
|
#[error("The area is not (fully) within the bounds of the display.")]
|
||||||
|
OutOfBounds,
|
||||||
|
|
||||||
|
#[error("Given the specified dimensions, the buffer size must be {expected} but it was {actual}.")]
|
||||||
|
WrongBufferSize {
|
||||||
|
expected: usize,
|
||||||
|
actual: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
impl LoupedeckDevice {
|
impl LoupedeckDevice {
|
||||||
pub fn characteristics(&self) -> &'static LoupedeckDeviceCharacteristics {
|
pub fn characteristics(&self) -> &'static LoupedeckDeviceCharacteristics {
|
||||||
self.characteristics
|
self.characteristics
|
||||||
|
@ -71,6 +94,75 @@ impl LoupedeckDevice {
|
||||||
self.commands_sender.send(LoupedeckCommand::SetButtonColor { button, color }).unwrap();
|
self.commands_sender.send(LoupedeckCommand::SetButtonColor { button, color }).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replaces the specified framebuffer area of the display with `buffer`.
|
||||||
|
///
|
||||||
|
/// `buffer` must contain exactly as many pixels as required.
|
||||||
|
///
|
||||||
|
/// Please note that the internal color format of all currently known devices is RGB565 (16 bits in total).
|
||||||
|
pub fn replace_framebuffer_area(
|
||||||
|
&self,
|
||||||
|
display: &LoupedeckDeviceDisplayConfiguration,
|
||||||
|
x: u16,
|
||||||
|
y: u16,
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
buffer: &[RGB8]
|
||||||
|
) -> Result<(), ReplaceFramebufferAreaError> {
|
||||||
|
if !std::ptr::eq(display, &self.characteristics.key_grid_display) &&
|
||||||
|
!self.characteristics.additional_displays.iter().any(|d| std::ptr::eq(display, d)) {
|
||||||
|
return Err(ReplaceFramebufferAreaError::UnknownDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if x + width > display.width || y + height > display.height {
|
||||||
|
return Err(ReplaceFramebufferAreaError::OutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
let expected_buffer_size = (height * width) as usize;
|
||||||
|
if buffer.len() != expected_buffer_size {
|
||||||
|
return Err(ReplaceFramebufferAreaError::WrongBufferSize {
|
||||||
|
expected: expected_buffer_size,
|
||||||
|
actual: buffer.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer = Bytes::copy_from_slice(buffer.as_slice());
|
||||||
|
|
||||||
|
// For some color values x, the pixel brightness is lower than for x - 1.
|
||||||
|
// I don’t understand why and what is the pattern of these values.
|
||||||
|
let converted_buffer = convert_rgb888_to_rgb565(buffer, display.endianness);
|
||||||
|
|
||||||
|
let local_x = display.local_offset_x + x;
|
||||||
|
let local_y = display.local_offset_y + y;
|
||||||
|
|
||||||
|
self.commands_sender.send(LoupedeckCommand::ReplaceFramebufferArea {
|
||||||
|
display_id: display.id,
|
||||||
|
x: local_x,
|
||||||
|
y: local_y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
buffer: converted_buffer.freeze(),
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_display(&self, display: &LoupedeckDeviceDisplayConfiguration) -> Result<(), RefreshDisplayError>{
|
||||||
|
if !std::ptr::eq(display, &self.characteristics.key_grid_display) &&
|
||||||
|
!self.characteristics.additional_displays.iter().any(|d| std::ptr::eq(display, d)) {
|
||||||
|
return Err(RefreshDisplayError::UnknownDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.commands_sender.send(LoupedeckCommand::RefreshDisplay {
|
||||||
|
display_id: display.id
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn discover() -> Result<Vec<AvailableLoupedeckDevice>, serialport::Error> {
|
pub fn discover() -> Result<Vec<AvailableLoupedeckDevice>, serialport::Error> {
|
||||||
let ports = serialport::available_ports()?;
|
let ports = serialport::available_ports()?;
|
||||||
|
|
||||||
|
@ -128,7 +220,7 @@ impl LoupedeckDevice {
|
||||||
Ok(LoupedeckDevice {
|
Ok(LoupedeckDevice {
|
||||||
characteristics,
|
characteristics,
|
||||||
events_channel: events_receiver,
|
events_channel: events_receiver,
|
||||||
commands_sender
|
commands_sender,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,3 +3,4 @@ pub mod device;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
mod messages;
|
mod messages;
|
||||||
mod commands;
|
mod commands;
|
||||||
|
mod util;
|
|
@ -116,7 +116,7 @@ fn parse_message(command: u8, mut message: Bytes) -> Option<LoupedeckEvent> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
println!("Unknown command: {}", command);
|
// println!("Unknown command: {}", command);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,6 +171,28 @@ pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: Rec
|
||||||
LoupedeckCommand::SetButtonColor { button, color } => {
|
LoupedeckCommand::SetButtonColor { button, color } => {
|
||||||
send(0x02, Bytes::copy_from_slice(&[button.ordinal() as u8, color.r, color.g, color.b]));
|
send(0x02, Bytes::copy_from_slice(&[button.ordinal() as u8, color.r, color.g, color.b]));
|
||||||
}
|
}
|
||||||
|
LoupedeckCommand::ReplaceFramebufferArea {
|
||||||
|
display_id,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
buffer
|
||||||
|
} => {
|
||||||
|
let mut data = BytesMut::with_capacity(10 + buffer.len());
|
||||||
|
data.put_u8(0);
|
||||||
|
data.put_u8(display_id);
|
||||||
|
data.put_u16(x);
|
||||||
|
data.put_u16(y);
|
||||||
|
data.put_u16(width);
|
||||||
|
data.put_u16(height);
|
||||||
|
data.put(buffer);
|
||||||
|
|
||||||
|
send(0x10, data.freeze());
|
||||||
|
}
|
||||||
|
LoupedeckCommand::RefreshDisplay { display_id } => {
|
||||||
|
send(0x0f, Bytes::copy_from_slice(&[0, display_id]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
32
loupedeck_serial/src/util.rs
Normal file
32
loupedeck_serial/src/util.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
|
pub(crate) fn convert_rgb888_to_rgb565(original: Bytes, endianness: Endianness) -> BytesMut {
|
||||||
|
let pixel_count = original.len() / 3;
|
||||||
|
let excess_bytes = original.len() % 3;
|
||||||
|
|
||||||
|
if excess_bytes != 0 {
|
||||||
|
panic!("Found {} excess bytes at the end of the original buffer", excess_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = BytesMut::with_capacity(pixel_count * 2);
|
||||||
|
|
||||||
|
for index in 0..pixel_count {
|
||||||
|
let red = original[index * 3] as u16;
|
||||||
|
let green = original[index * 3 + 1] as u16;
|
||||||
|
let blue = original[index * 3 + 2] as u16;
|
||||||
|
let color = ((red & 0b11111000) << 8) | ((green & 0b11111100) << 3) | (blue.wrapping_shr(3));
|
||||||
|
|
||||||
|
match endianness {
|
||||||
|
Endianness::LittleEndian => result.put_u16_le(color),
|
||||||
|
Endianness::BigEndian => result.put_u16(color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Endianness {
|
||||||
|
BigEndian,
|
||||||
|
LittleEndian
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue