use super::opcode::{Sub, PTP_OC_CHDK};
use crate::ptp::{DataPhase, PtpSession};
use crate::{Error, Result};
pub const LV_TFR_VIEWPORT: u32 = 0x01;
pub const LV_TFR_PALETTE: u32 = 0x02;
pub const LV_TFR_BITMAP: u32 = 0x04;
pub const LV_TFR_BITMAP_OPACITY: u32 = 0x08;
pub const LV_FB_YUV8: u32 = 0;
pub const LV_FB_PAL8: u32 = 1;
pub const LV_FB_YUV8B: u32 = 2;
pub const LV_FB_PAL8_OPACITY: u32 = 3;
pub const LV_FB_YUV8C: u32 = 4;
pub const LV_FB_OPACITY8: u32 = 5;
#[derive(Debug, Clone, Copy)]
pub struct LvDataHeader {
pub version_major: u32,
pub version_minor: u32,
pub lcd_aspect_ratio: u32,
pub palette_type: u32,
pub palette_data_start: u32,
pub vp_desc_start: u32,
pub bm_desc_start: u32,
pub bm_opacity_desc_start: u32,
}
impl LvDataHeader {
pub const SIZE: usize = 32;
fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::SIZE {
return Err(Error::Codec(format!(
"lv_data_header: need {} bytes, have {}",
Self::SIZE,
buf.len()
)));
}
let g = |off: usize| u32::from_le_bytes(buf[off..off + 4].try_into().unwrap());
Ok(Self {
version_major: g(0),
version_minor: g(4),
lcd_aspect_ratio: g(8),
palette_type: g(12),
palette_data_start: g(16),
vp_desc_start: g(20),
bm_desc_start: g(24),
bm_opacity_desc_start: g(28),
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct FramebufferDesc {
pub fb_type: u32,
pub data_start: u32,
pub buffer_width: u32,
pub visible_width: u32,
pub visible_height: u32,
pub margin_top: u32,
pub margin_left: u32,
pub margin_bot: u32,
pub margin_right: u32,
}
impl FramebufferDesc {
pub const SIZE: usize = 36;
fn parse_at(buf: &[u8], offset: u32) -> Result<Self> {
let off = offset as usize;
if buf.len() < off + Self::SIZE {
return Err(Error::Codec(format!(
"framebuffer_desc at {off}: need {} bytes, buffer has {}",
Self::SIZE,
buf.len()
)));
}
let g = |i: usize| u32::from_le_bytes(buf[off + i..off + i + 4].try_into().unwrap());
Ok(Self {
fb_type: g(0),
data_start: g(4),
buffer_width: g(8),
visible_width: g(12),
visible_height: g(16),
margin_top: g(20),
margin_left: g(24),
margin_bot: g(28),
margin_right: g(32),
})
}
}
#[derive(Debug, Clone)]
pub struct LiveViewFrame {
pub header: LvDataHeader,
pub raw: Vec<u8>,
}
impl LiveViewFrame {
pub fn viewport_desc(&self) -> Result<Option<FramebufferDesc>> {
if self.header.vp_desc_start == 0 {
return Ok(None);
}
FramebufferDesc::parse_at(&self.raw, self.header.vp_desc_start).map(Some)
}
pub fn bitmap_desc(&self) -> Result<Option<FramebufferDesc>> {
if self.header.bm_desc_start == 0 {
return Ok(None);
}
FramebufferDesc::parse_at(&self.raw, self.header.bm_desc_start).map(Some)
}
pub fn decode_viewport_rgb(&self) -> Result<(u32, u32, Vec<u8>)> {
let desc = self
.viewport_desc()?
.ok_or_else(|| Error::Codec("viewport not present in response".into()))?;
let width = desc.visible_width as usize;
let height = desc.visible_height as usize;
let row_bytes = (desc.buffer_width as usize) * 3 / 2;
let start = desc.data_start as usize;
let total_needed = start + row_bytes * height;
if self.raw.len() < total_needed {
return Err(Error::Codec(format!(
"viewport plane truncated: need {total_needed} bytes, have {}",
self.raw.len()
)));
}
match desc.fb_type {
LV_FB_YUV8 => {
let mut rgb = Vec::with_capacity(width * height * 3);
for row in 0..height {
let row_start = start + row * row_bytes;
let line = &self.raw[row_start..row_start + width * 3 / 2];
decode_y411_row(line, width, &mut rgb);
}
Ok((width as u32, height as u32, rgb))
}
other => Err(Error::Codec(format!(
"viewport fb_type {other} — only LV_FB_YUV8 (Y411) decoded today"
))),
}
}
}
fn decode_y411_row(line: &[u8], visible_width: usize, out: &mut Vec<u8>) {
let mut x = 0usize;
let mut p = 0usize;
while x + 4 <= visible_width && p + 6 <= line.len() {
let u = line[p] as i8 as i32;
let y0 = line[p + 1] as i32;
let v = line[p + 2] as i8 as i32;
let y1 = line[p + 3] as i32;
let y2 = line[p + 4] as i32;
let y3 = line[p + 5] as i32;
for &y in &[y0, y1, y2, y3] {
let (r, g, b) = yuv_to_rgb(y, u, v);
out.extend_from_slice(&[r, g, b]);
}
x += 4;
p += 6;
}
}
#[inline]
fn yuv_to_rgb(y: i32, u: i32, v: i32) -> (u8, u8, u8) {
let r = ((y << 12) + v * 5743 + 2048) >> 12;
let g = ((y << 12) - u * 1411 - v * 2925 + 2048) >> 12;
let b = ((y << 12) + u * 7258 + 2048) >> 12;
(
r.clamp(0, 255) as u8,
g.clamp(0, 255) as u8,
b.clamp(0, 255) as u8,
)
}
impl PtpSession {
pub async fn get_display_data(&mut self, flags: u32) -> Result<LiveViewFrame> {
let resp = self
.command(
PTP_OC_CHDK,
&[Sub::GetDisplayData.as_u32(), flags],
DataPhase::In,
)
.await?;
resp.ok()?;
let header = LvDataHeader::parse(&resp.data)?;
Ok(LiveViewFrame {
header,
raw: resp.data,
})
}
}