use skia_safe::{images, AlphaType, ColorType, Data, Image, ImageInfo};
const EMR_HEADER: u32 = 0x00000001;
const EMR_EOF: u32 = 0x0000000E;
const EMR_BITBLT: u32 = 0x0000004C;
const EMR_STRETCHDIBITS: u32 = 0x00000051;
const EMF_SIGNATURE: u32 = 0x464D4520;
const DIB_RGB_COLORS: u32 = 0;
const SRCCOPY: u32 = 0x00CC0020;
const BITMAPINFOHEADER_SIZE: u32 = 40;
const BI_RGB: u32 = 0;
const BI_BITFIELDS: u32 = 3;
pub fn decode_emf_bitmap(emf_data: &[u8]) -> Option<Image> {
validate_emf_header(emf_data)?;
let (width, height, rgba) = extract_bitmap(emf_data)?;
let info = ImageInfo::new(
(width as i32, height as i32),
ColorType::RGBA8888,
AlphaType::Premul,
None,
);
images::raster_from_data(&info, Data::new_copy(&rgba), width as usize * 4)
}
fn validate_emf_header(data: &[u8]) -> Option<()> {
if data.len() < 88 {
return None;
}
let record_type = read_u32(data, 0)?;
if record_type != EMR_HEADER {
return None;
}
let signature = read_u32(data, 40)?;
if signature != EMF_SIGNATURE {
return None;
}
Some(())
}
fn extract_bitmap(data: &[u8]) -> Option<(u32, u32, Vec<u8>)> {
let mut offset: usize = 0;
while offset + 8 <= data.len() {
let record_type = read_u32(data, offset)?;
let record_size = read_u32(data, offset + 4)? as usize;
if record_size < 8 || offset + record_size > data.len() {
break;
}
match record_type {
EMR_STRETCHDIBITS => {
if let Some(result) = parse_stretchdibits(data, offset, record_size) {
return Some(result);
}
}
EMR_BITBLT => {
if let Some(result) = parse_bitblt(data, offset, record_size) {
return Some(result);
}
}
EMR_EOF => break,
_ => {}
}
offset += record_size;
}
None
}
fn parse_stretchdibits(
data: &[u8],
record_start: usize,
record_size: usize,
) -> Option<(u32, u32, Vec<u8>)> {
if record_size < 80 {
return None;
}
let off_bmi = read_u32(data, record_start + 48)? as usize;
let cb_bmi = read_u32(data, record_start + 52)? as usize;
let off_bits = read_u32(data, record_start + 56)? as usize;
let cb_bits = read_u32(data, record_start + 60)? as usize;
let usage = read_u32(data, record_start + 64)?;
let rop = read_u32(data, record_start + 68)?;
if usage != DIB_RGB_COLORS || rop != SRCCOPY {
return None;
}
if cb_bmi == 0 || cb_bits == 0 {
return None;
}
let bmi_abs = record_start + off_bmi;
let bits_abs = record_start + off_bits;
if bmi_abs + cb_bmi > data.len() || bits_abs + cb_bits > data.len() {
return None;
}
decode_dib(
&data[bmi_abs..bmi_abs + cb_bmi],
&data[bits_abs..bits_abs + cb_bits],
)
}
fn parse_bitblt(
data: &[u8],
record_start: usize,
record_size: usize,
) -> Option<(u32, u32, Vec<u8>)> {
if record_size < 92 {
return None;
}
let rop = read_u32(data, record_start + 40)?;
let usage = read_u32(data, record_start + 72)?;
let off_bmi = read_u32(data, record_start + 76)? as usize;
let cb_bmi = read_u32(data, record_start + 80)? as usize;
let off_bits = read_u32(data, record_start + 84)? as usize;
let cb_bits = read_u32(data, record_start + 88)? as usize;
if usage != DIB_RGB_COLORS || rop != SRCCOPY {
return None;
}
if cb_bmi == 0 || cb_bits == 0 {
return None;
}
let bmi_abs = record_start + off_bmi;
let bits_abs = record_start + off_bits;
if bmi_abs + cb_bmi > data.len() || bits_abs + cb_bits > data.len() {
return None;
}
decode_dib(
&data[bmi_abs..bmi_abs + cb_bmi],
&data[bits_abs..bits_abs + cb_bits],
)
}
fn decode_dib(bmi: &[u8], bits: &[u8]) -> Option<(u32, u32, Vec<u8>)> {
if bmi.len() < BITMAPINFOHEADER_SIZE as usize {
return None;
}
let bi_size = read_u32(bmi, 0)?;
if bi_size < BITMAPINFOHEADER_SIZE {
return None;
}
let bi_width = read_i32(bmi, 4)?;
let bi_height = read_i32(bmi, 8)?; let bi_bit_count = read_u16(bmi, 14)?;
let bi_compression = read_u32(bmi, 16)?;
if bi_width <= 0 {
return None;
}
let width = bi_width as u32;
let height = bi_height.unsigned_abs();
let bottom_up = bi_height > 0;
if height == 0 || width == 0 {
return None;
}
match (bi_bit_count, bi_compression) {
(32, BI_RGB | BI_BITFIELDS) => decode_32bpp(bits, width, height, bottom_up),
(24, BI_RGB) => decode_24bpp(bits, width, height, bottom_up),
_ => None,
}
}
fn decode_32bpp(
bits: &[u8],
width: u32,
height: u32,
bottom_up: bool,
) -> Option<(u32, u32, Vec<u8>)> {
let row_bytes = width as usize * 4;
let total = row_bytes * height as usize;
if bits.len() < total {
return None;
}
let mut rgba = vec![0u8; total];
for y in 0..height as usize {
let src_row = if bottom_up {
height as usize - 1 - y
} else {
y
};
let src = &bits[src_row * row_bytes..(src_row + 1) * row_bytes];
let dst = &mut rgba[y * row_bytes..(y + 1) * row_bytes];
for x in 0..width as usize {
dst[x * 4] = src[x * 4 + 2]; dst[x * 4 + 1] = src[x * 4 + 1]; dst[x * 4 + 2] = src[x * 4]; dst[x * 4 + 3] = src[x * 4 + 3]; }
}
Some((width, height, rgba))
}
fn decode_24bpp(
bits: &[u8],
width: u32,
height: u32,
bottom_up: bool,
) -> Option<(u32, u32, Vec<u8>)> {
let src_row_bytes = ((width as usize * 3) + 3) & !3;
let dst_row_bytes = width as usize * 4;
let total_src = src_row_bytes * height as usize;
let total_dst = dst_row_bytes * height as usize;
if bits.len() < total_src {
return None;
}
let mut rgba = vec![0u8; total_dst];
for y in 0..height as usize {
let src_row = if bottom_up {
height as usize - 1 - y
} else {
y
};
let src = &bits[src_row * src_row_bytes..(src_row * src_row_bytes) + width as usize * 3];
let dst = &mut rgba[y * dst_row_bytes..(y + 1) * dst_row_bytes];
for x in 0..width as usize {
dst[x * 4] = src[x * 3 + 2]; dst[x * 4 + 1] = src[x * 3 + 1]; dst[x * 4 + 2] = src[x * 3]; dst[x * 4 + 3] = 0xFF; }
}
Some((width, height, rgba))
}
#[inline]
fn read_u32(data: &[u8], offset: usize) -> Option<u32> {
data.get(offset..offset + 4)
.map(|b| u32::from_le_bytes(b.try_into().unwrap()))
}
#[inline]
fn read_i32(data: &[u8], offset: usize) -> Option<i32> {
data.get(offset..offset + 4)
.map(|b| i32::from_le_bytes(b.try_into().unwrap()))
}
#[inline]
fn read_u16(data: &[u8], offset: usize) -> Option<u16> {
data.get(offset..offset + 2)
.map(|b| u16::from_le_bytes(b.try_into().unwrap()))
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_emf_32bpp() -> Vec<u8> {
#[rustfmt::skip]
let pixels: Vec<u8> = vec![
0x10, 0x20, 0x30, 0xFF, 0x40, 0x50, 0x60, 0xFF,
0x70, 0x80, 0x90, 0xFF, 0xA0, 0xB0, 0xC0, 0xFF,
];
let bmi: Vec<u8> = {
let mut v = vec![0u8; 40];
v[0..4].copy_from_slice(&40u32.to_le_bytes()); v[4..8].copy_from_slice(&2i32.to_le_bytes()); v[8..12].copy_from_slice(&2i32.to_le_bytes()); v[12..14].copy_from_slice(&1u16.to_le_bytes()); v[14..16].copy_from_slice(&32u16.to_le_bytes()); v
};
let mut record = vec![0u8; 80];
record[0..4].copy_from_slice(&EMR_STRETCHDIBITS.to_le_bytes()); record[4..8].copy_from_slice(&(80u32 + 40 + 16).to_le_bytes()); record[48..52].copy_from_slice(&80u32.to_le_bytes()); record[52..56].copy_from_slice(&40u32.to_le_bytes()); record[56..60].copy_from_slice(&120u32.to_le_bytes()); record[60..64].copy_from_slice(&16u32.to_le_bytes()); record[64..68].copy_from_slice(&DIB_RGB_COLORS.to_le_bytes()); record[68..72].copy_from_slice(&SRCCOPY.to_le_bytes());
record.extend_from_slice(&bmi);
record.extend_from_slice(&pixels);
let mut header_rec = vec![0u8; 88];
header_rec[0..4].copy_from_slice(&EMR_HEADER.to_le_bytes());
header_rec[4..8].copy_from_slice(&88u32.to_le_bytes());
header_rec[40..44].copy_from_slice(&EMF_SIGNATURE.to_le_bytes());
let mut eof_rec = vec![0u8; 20];
eof_rec[0..4].copy_from_slice(&EMR_EOF.to_le_bytes());
eof_rec[4..8].copy_from_slice(&20u32.to_le_bytes());
let mut emf = Vec::new();
emf.extend_from_slice(&header_rec);
emf.extend_from_slice(&record);
emf.extend_from_slice(&eof_rec);
emf
}
#[test]
fn extracts_32bpp_bitmap() {
let emf = make_test_emf_32bpp();
let (w, h, rgba) = extract_bitmap(&emf).expect("should extract bitmap");
assert_eq!(w, 2);
assert_eq!(h, 2);
assert_eq!(rgba.len(), 2 * 2 * 4);
assert_eq!(&rgba[0..4], &[0x90, 0x80, 0x70, 0xFF]); assert_eq!(&rgba[4..8], &[0xC0, 0xB0, 0xA0, 0xFF]); assert_eq!(&rgba[8..12], &[0x30, 0x20, 0x10, 0xFF]); }
#[test]
fn rejects_invalid_header() {
let mut bad = make_test_emf_32bpp();
bad[40..44].copy_from_slice(&0xDEADBEEFu32.to_le_bytes()); assert!(validate_emf_header(&bad).is_none());
}
#[test]
fn rejects_truncated_data() {
assert!(extract_bitmap(&[0u8; 10]).is_none());
}
#[test]
fn decode_emf_bitmap_returns_skia_image() {
let emf = make_test_emf_32bpp();
let image = decode_emf_bitmap(&emf);
assert!(image.is_some(), "should produce a Skia image");
let img = image.unwrap();
assert_eq!(img.width(), 2);
assert_eq!(img.height(), 2);
}
}