use crate::memory::{MacMemoryBus, MemoryBus};
pub(crate) type DstClipRect = (i32, i32, i32, i32);
#[derive(Clone, Debug)]
pub(crate) struct DstClipRegion {
top: i32,
left: i32,
bottom: i32,
right: i32,
rows: Option<Vec<Vec<i32>>>,
}
impl DstClipRegion {
pub(crate) fn rectangular(top: i32, left: i32, bottom: i32, right: i32) -> Self {
Self {
top,
left,
bottom,
right,
rows: None,
}
}
pub(crate) fn complex(
top: i32,
left: i32,
bottom: i32,
right: i32,
rows: Vec<Vec<i32>>,
) -> Self {
Self {
top,
left,
bottom,
right,
rows: Some(rows),
}
}
fn contains(&self, y: i32, x: i32) -> bool {
if y < self.top || y >= self.bottom || x < self.left || x >= self.right {
return false;
}
let Some(rows) = self.rows.as_ref() else {
return true;
};
let row_index = (y - self.top) as usize;
let Some(row) = rows.get(row_index) else {
return false;
};
let mut in_region = false;
for &edge in row {
if edge > x {
break;
}
in_region = !in_region;
}
in_region
}
}
#[derive(Clone, Debug)]
pub(crate) struct DstClip {
rect: DstClipRect,
regions: Vec<DstClipRegion>,
}
impl DstClip {
pub(crate) fn new(rect: DstClipRect, regions: Vec<DstClipRegion>) -> Self {
Self { rect, regions }
}
pub(crate) fn rect(&self) -> DstClipRect {
self.rect
}
fn contains(&self, x: i32, y: i32) -> bool {
let (top, left, bottom, right) = self.rect;
if y < top || y >= bottom || x < left || x >= right {
return false;
}
self.regions.iter().all(|region| region.contains(y, x))
}
}
use std::sync::{Mutex, OnceLock};
static TRACE_PICT: OnceLock<bool> = OnceLock::new();
static TRACE_PICT_PALETTE: OnceLock<bool> = OnceLock::new();
static TRACE_PICT_SAMPLES: OnceLock<bool> = OnceLock::new();
static CLUT_MATCH_ITABLE: OnceLock<bool> = OnceLock::new();
static CLUT_MATCH_LEGACY_GRAY: OnceLock<bool> = OnceLock::new();
static CLUT_MATCH_DEVICE_ITABLE: OnceLock<bool> = OnceLock::new();
static PICT_IDENTITY_REMAP: OnceLock<bool> = OnceLock::new();
static SRC_TO_DST_TABLE_CACHE: OnceLock<Mutex<Vec<SrcToDstTableCacheEntry>>> = OnceLock::new();
const SRC_TO_DST_TABLE_CACHE_LIMIT: usize = 16;
#[derive(Clone)]
struct SrcToDstTableCacheEntry {
src_clut: Vec<[u16; 3]>,
dst_clut: [[u16; 3]; 256],
table: [u8; 256],
}
fn trace_pict_enabled() -> bool {
*TRACE_PICT.get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_PICT").is_some())
}
fn trace_pict_palette_enabled() -> bool {
*TRACE_PICT_PALETTE.get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_PICT_PALETTE").is_some())
}
fn trace_pict_samples_enabled() -> bool {
*TRACE_PICT_SAMPLES.get_or_init(|| std::env::var_os("SYSTEMLESS_TRACE_PICT_SAMPLES").is_some())
}
fn clut_match_itable_enabled() -> bool {
*CLUT_MATCH_ITABLE.get_or_init(|| std::env::var_os("SYSTEMLESS_CLUT_MATCH_ITABLE").is_some())
}
fn clut_match_legacy_gray_enabled() -> bool {
*CLUT_MATCH_LEGACY_GRAY
.get_or_init(|| std::env::var_os("SYSTEMLESS_CLUT_MATCH_LEGACY_GRAY").is_some())
}
fn clut_match_device_itable_enabled() -> bool {
*CLUT_MATCH_DEVICE_ITABLE
.get_or_init(|| std::env::var_os("SYSTEMLESS_CLUT_MATCH_DEVICE_ITABLE").is_some())
}
fn pict_identity_remap_enabled() -> bool {
*PICT_IDENTITY_REMAP
.get_or_init(|| std::env::var_os("SYSTEMLESS_PICT_IDENTITY_REMAP").is_some())
}
fn align_pict_pos(pos: u32) -> u32 {
if pos.is_multiple_of(2) {
pos
} else {
pos + 1
}
}
fn skip_reserved_v1_opcode(bus: &MacMemoryBus, opcode: u16, pos: u32) -> Option<u32> {
match opcode {
0x35..=0x37 | 0x45..=0x47 | 0x55..=0x57 => Some(pos + 8),
0x3D..=0x3F | 0x4D..=0x4F | 0x5D..=0x5F | 0x7D..=0x7F | 0x8D..=0x8F => Some(pos),
0x65..=0x67 => Some(pos + 12),
0x6D..=0x6F => Some(pos + 4),
0x75..=0x77 | 0x85..=0x87 => Some(pos + u32::from(bus.read_word(pos))),
_ => None,
}
}
const REGION_HEADER_SIZE: u32 = 10;
const REGION_STOP: i16 = i16::MAX;
struct PictureRegion {
top: i16,
left: i16,
bottom: i16,
right: i16,
rows: Vec<Vec<i16>>,
}
impl PictureRegion {
fn contains(&self, y: i32, x: i32) -> bool {
if y < i32::from(self.top)
|| y >= i32::from(self.bottom)
|| x < i32::from(self.left)
|| x >= i32::from(self.right)
{
return false;
}
if self.rows.is_empty() {
return true;
}
let row_index = (y - i32::from(self.top)) as usize;
let Some(row) = self.rows.get(row_index) else {
return false;
};
let mut in_region = false;
for &edge in row {
if i32::from(edge) > x {
break;
}
in_region = !in_region;
}
in_region
}
}
fn merge_region_endpoints(lhs: &[i16], rhs: &[i16]) -> Vec<i16> {
let mut merged = Vec::with_capacity(lhs.len() + rhs.len());
let mut lhs_index = 0usize;
let mut rhs_index = 0usize;
while lhs_index < lhs.len() || rhs_index < rhs.len() {
match (lhs.get(lhs_index), rhs.get(rhs_index)) {
(Some(&lhs_value), Some(&rhs_value)) if lhs_value < rhs_value => {
merged.push(lhs_value);
lhs_index += 1;
}
(Some(&lhs_value), Some(&rhs_value)) if rhs_value < lhs_value => {
merged.push(rhs_value);
rhs_index += 1;
}
(Some(_), Some(_)) => {
lhs_index += 1;
rhs_index += 1;
}
(Some(&lhs_value), None) => {
merged.push(lhs_value);
lhs_index += 1;
}
(None, Some(&rhs_value)) => {
merged.push(rhs_value);
rhs_index += 1;
}
(None, None) => break,
}
}
merged
}
fn parse_picture_region(bus: &MacMemoryBus, region_ptr: u32) -> Option<PictureRegion> {
let region_size = u32::from(bus.read_word(region_ptr));
if region_size < REGION_HEADER_SIZE {
return None;
}
let top = bus.read_word(region_ptr + 2) as i16;
let left = bus.read_word(region_ptr + 4) as i16;
let bottom = bus.read_word(region_ptr + 6) as i16;
let right = bus.read_word(region_ptr + 8) as i16;
if bottom <= top || right <= left {
return None;
}
if region_size == REGION_HEADER_SIZE {
return Some(PictureRegion {
top,
left,
bottom,
right,
rows: Vec::new(),
});
}
let region_end = region_ptr + region_size;
let mut cursor = region_ptr + REGION_HEADER_SIZE;
if cursor + 2 > region_end {
return None;
}
let mut next_change_y = bus.read_word(cursor) as i16;
cursor += 2;
let mut active = Vec::new();
let mut rows = Vec::with_capacity((bottom - top) as usize);
for y in top..bottom {
while next_change_y != REGION_STOP && next_change_y <= y {
let mut delta = Vec::new();
loop {
if cursor + 2 > region_end {
return None;
}
let value = bus.read_word(cursor) as i16;
cursor += 2;
if value == REGION_STOP {
break;
}
delta.push(value);
}
active = merge_region_endpoints(&active, &delta);
if cursor + 2 > region_end {
return None;
}
next_change_y = bus.read_word(cursor) as i16;
cursor += 2;
}
rows.push(active.clone());
}
Some(PictureRegion {
top,
left,
bottom,
right,
rows,
})
}
pub fn draw_picture(
bus: &mut MacMemoryBus,
pic_ptr: u32,
dst_top: i16,
dst_left: i16,
dst_bottom: i16,
dst_right: i16,
screen_mode: (u32, u32, u16, u16, u16), device_clut: &[[u16; 3]; 256],
device_ct_seed: u32,
dst_clip: Option<&DstClip>,
) -> (bool, Option<Vec<[u16; 3]>>) {
if pic_ptr == 0 {
return (false, None);
}
let _pic_size = bus.read_word(pic_ptr) as u32;
let frame_top = bus.read_word(pic_ptr + 2) as i16;
let frame_left = bus.read_word(pic_ptr + 4) as i16;
let frame_bottom = bus.read_word(pic_ptr + 6) as i16;
let frame_right = bus.read_word(pic_ptr + 8) as i16;
if trace_pict_enabled() {
eprintln!(
"[PICT] draw_picture picPtr=${:08X} picFrame=({},{}..{},{}) dst=({},{}..{},{}) dstBase=${:08X}",
pic_ptr, frame_top, frame_left, frame_bottom, frame_right,
dst_top, dst_left, dst_bottom, dst_right, screen_mode.0,
);
}
let frame_w = (frame_right - frame_left) as f64;
let frame_h = (frame_bottom - frame_top) as f64;
let dst_w = (dst_right - dst_left) as f64;
let dst_h = (dst_bottom - dst_top) as f64;
if frame_w <= 0.0 || frame_h <= 0.0 {
return (false, None);
}
let scale_x = dst_w / frame_w;
let scale_y = dst_h / frame_h;
let mut pos = pic_ptr + 10;
let mut opcount = 0;
let mut last_clut: Option<Vec<[u16; 3]>> = None;
let mut preferred_clut: Option<Vec<[u16; 3]>> = None;
let mut clip_region: Option<PictureRegion> = None;
let mut last_shape_rect: Option<(i16, i16, i16, i16)> = None;
let mut pict_font_id: i16 = 3;
let mut pict_font_size: i16 = 12;
let mut pen_v: i16 = 0;
let mut pen_h: i16 = 0;
let mut pen_size: (i16, i16) = (1, 1);
let mut pn_pat: [u8; 8] = [0xFF; 8];
let mut bk_pat: [u8; 8] = [0x00; 8];
let mut fill_pat: [u8; 8] = [0xFF; 8];
let (black_idx, white_idx) = if screen_mode.4 == 1 {
(255, 0)
} else {
clut_black_white_indices(device_clut)
};
let mut fg_idx: u8 = black_idx;
let mut bg_idx: u8 = white_idx;
let mut tx_mode: i16 = 1;
let mut is_v2 = false;
loop {
if opcount > 10000 {
eprintln!("[PICT] Too many opcodes, stopping");
break;
}
opcount += 1;
if is_v2 && !pos.is_multiple_of(2) {
pos += 1;
}
let opcode: u16 = if is_v2 {
let op = bus.read_word(pos);
pos += 2;
op
} else {
let op = bus.read_byte(pos) as u16;
pos += 1;
op
};
match opcode {
0x00 => {
}
0x01 => {
let rgn_size = bus.read_word(pos) as u32;
clip_region = parse_picture_region(bus, pos);
pos += rgn_size;
}
0x02 => {
bus.read_bytes_into(pos, &mut bk_pat);
pos += 8;
}
0x03 => {
pict_font_id = bus.read_word(pos) as i16;
pos += 2;
}
0x04 => {
pos += if is_v2 { 2 } else { 1 };
}
0x05 => {
tx_mode = bus.read_word(pos) as i16;
pos += 2;
}
0x06 => {
pos += 4;
}
0x07 => {
pen_size = (bus.read_word(pos) as i16, bus.read_word(pos + 2) as i16);
pos += 4;
}
0x08 => {
pos += 2;
}
0x09 => {
bus.read_bytes_into(pos, &mut pn_pat);
pos += 8;
}
0x0A => {
bus.read_bytes_into(pos, &mut fill_pat);
pos += 8;
}
0x0B => {
pos += 4;
}
0x0C => {
pos += 4;
}
0x0D => {
pict_font_size = bus.read_word(pos) as i16;
pos += 2;
}
0x0E => {
let c = bus.read_long(pos);
fg_idx = pict_qd_color_to_clut_index(c, fg_idx, black_idx, white_idx);
pos += 4;
}
0x0F => {
let c = bus.read_long(pos);
bg_idx = pict_qd_color_to_clut_index(c, bg_idx, black_idx, white_idx);
pos += 4;
}
0x10 => {
pos += 8;
}
0x11 => {
let version = bus.read_byte(pos);
pos += 1;
if version == 0x02 {
pos += 1;
is_v2 = true;
}
}
0x12..=0x14 => {
pos = skip_pixpat(bus, pos);
}
0x15 | 0x16 => {
pos += 2;
}
0x1A => {
let r = bus.read_word(pos);
let g = bus.read_word(pos + 2);
let b = bus.read_word(pos + 4);
pos += 6;
let clut = super::TrapDispatcher::standard_mac_8bpp_clut();
fg_idx = closest_clut_index(r, g, b, &clut);
}
0x1B => {
let r = bus.read_word(pos);
let g = bus.read_word(pos + 2);
let b = bus.read_word(pos + 4);
pos += 6;
let clut = super::TrapDispatcher::standard_mac_8bpp_clut();
bg_idx = closest_clut_index(r, g, b, &clut);
}
0x1C => {
}
0x1D => {
pos += 6;
}
0x1E => {
}
0x1F => {
pos += 6;
}
0x20 => {
let pn_v = bus.read_word(pos) as i16;
let pn_h = bus.read_word(pos + 2) as i16;
let new_v = bus.read_word(pos + 4) as i16;
let new_h = bus.read_word(pos + 6) as i16;
pos += 8;
draw_picture_line(
bus,
screen_mode,
pn_v,
pn_h,
new_v,
new_h,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
fg_idx,
);
pen_v = new_v;
pen_h = new_h;
}
0x21 => {
let new_v = bus.read_word(pos) as i16;
let new_h = bus.read_word(pos + 2) as i16;
pos += 4;
draw_picture_line(
bus,
screen_mode,
pen_v,
pen_h,
new_v,
new_h,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
fg_idx,
);
pen_v = new_v;
pen_h = new_h;
}
0x22 => {
let pn_v = bus.read_word(pos) as i16;
let pn_h = bus.read_word(pos + 2) as i16;
let dh = bus.read_byte(pos + 4) as i8 as i16;
let dv = bus.read_byte(pos + 5) as i8 as i16;
pos += 6;
let new_v = pn_v.saturating_add(dv);
let new_h = pn_h.saturating_add(dh);
draw_picture_line(
bus,
screen_mode,
pn_v,
pn_h,
new_v,
new_h,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
fg_idx,
);
pen_v = new_v;
pen_h = new_h;
}
0x23 => {
let dh = bus.read_byte(pos) as i8 as i16;
let dv = bus.read_byte(pos + 1) as i8 as i16;
pos += 2;
let new_v = pen_v.saturating_add(dv);
let new_h = pen_h.saturating_add(dh);
draw_picture_line(
bus,
screen_mode,
pen_v,
pen_h,
new_v,
new_h,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
fg_idx,
);
pen_v = new_v;
pen_h = new_h;
}
0x28 => {
pen_v = bus.read_word(pos) as i16;
pen_h = bus.read_word(pos + 2) as i16;
pos += 4;
let len = bus.read_byte(pos) as u32;
pos += 1;
let text_start = pos;
pos += len;
draw_picture_text(
bus,
screen_mode,
pen_v,
pen_h,
text_start,
len,
pict_font_id,
pict_font_size,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
fg_idx,
bg_idx,
tx_mode,
);
pen_h = pen_h.saturating_add(text_advance(
bus,
text_start,
len,
pict_font_id,
pict_font_size,
));
if is_v2 && !(1 + len).is_multiple_of(2) {
pos += 1;
}
}
0x29 => {
let dh = bus.read_byte(pos) as i8 as i16;
pos += 1;
let len = bus.read_byte(pos) as u32;
pos += 1;
let text_start = pos;
pos += len;
pen_h = pen_h.saturating_add(dh);
draw_picture_text(
bus,
screen_mode,
pen_v,
pen_h,
text_start,
len,
pict_font_id,
pict_font_size,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
fg_idx,
bg_idx,
tx_mode,
);
pen_h = pen_h.saturating_add(text_advance(
bus,
text_start,
len,
pict_font_id,
pict_font_size,
));
if is_v2 && len.is_multiple_of(2) {
pos += 1;
}
}
0x2A => {
let dv = bus.read_byte(pos) as i8 as i16;
pos += 1;
let len = bus.read_byte(pos) as u32;
pos += 1;
let text_start = pos;
pos += len;
pen_v = pen_v.saturating_add(dv);
draw_picture_text(
bus,
screen_mode,
pen_v,
pen_h,
text_start,
len,
pict_font_id,
pict_font_size,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
fg_idx,
bg_idx,
tx_mode,
);
pen_h = pen_h.saturating_add(text_advance(
bus,
text_start,
len,
pict_font_id,
pict_font_size,
));
if is_v2 && len.is_multiple_of(2) {
pos += 1;
}
}
0x2B => {
let dh = bus.read_byte(pos) as i8 as i16;
let dv = bus.read_byte(pos + 1) as i8 as i16;
pos += 2;
let len = bus.read_byte(pos) as u32;
pos += 1;
let text_start = pos;
pos += len;
pen_h = pen_h.saturating_add(dh);
pen_v = pen_v.saturating_add(dv);
draw_picture_text(
bus,
screen_mode,
pen_v,
pen_h,
text_start,
len,
pict_font_id,
pict_font_size,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
fg_idx,
bg_idx,
tx_mode,
);
pen_h = pen_h.saturating_add(text_advance(
bus,
text_start,
len,
pict_font_id,
pict_font_size,
));
if is_v2 && !(1 + len).is_multiple_of(2) {
pos += 1;
}
}
0x2C => {
let data_len = bus.read_word(pos) as u32;
pos += 2 + data_len;
pos = align_pict_pos(pos);
}
0x2D | 0x2E => {
let data_len = bus.read_word(pos) as u32;
pos += 2 + data_len;
pos = align_pict_pos(pos);
}
0x24..=0x27 | 0x2F => {
let data_len = bus.read_word(pos) as u32;
pos += 2 + data_len;
pos = align_pict_pos(pos);
}
0x30..=0x34 => {
let (t, l, b, r) = read_shape_rect(bus, pos);
pos += 8;
last_shape_rect = Some((t, l, b, r));
draw_shape_rect(
bus,
opcode as u8,
t,
l,
b,
r,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
0x38..=0x3C => {
if let Some((t, l, b, r)) = last_shape_rect {
draw_shape_rect(
bus,
(opcode - 0x38 + 0x30) as u8,
t,
l,
b,
r,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
}
0x40..=0x44 => {
let (t, l, b, r) = read_shape_rect(bus, pos);
pos += 8;
last_shape_rect = Some((t, l, b, r));
draw_shape_rect(
bus,
(opcode - 0x40 + 0x30) as u8,
t,
l,
b,
r,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
0x48..=0x4C => {
if let Some((t, l, b, r)) = last_shape_rect {
draw_shape_rect(
bus,
(opcode - 0x48 + 0x30) as u8,
t,
l,
b,
r,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
}
0x50..=0x54 => {
let (t, l, b, r) = read_shape_rect(bus, pos);
pos += 8;
last_shape_rect = Some((t, l, b, r));
draw_shape_oval(
bus,
(opcode - 0x50) as u8,
t,
l,
b,
r,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
0x58..=0x5C => {
if let Some((t, l, b, r)) = last_shape_rect {
draw_shape_oval(
bus,
(opcode - 0x58) as u8,
t,
l,
b,
r,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
}
0x60..=0x64 => {
let (t, l, b, r) = read_shape_rect(bus, pos);
let start_angle = bus.read_word(pos + 8) as i16;
let arc_angle = bus.read_word(pos + 10) as i16;
pos += 12;
last_shape_rect = Some((t, l, b, r));
draw_shape_oval_or_arc(
bus,
(opcode - 0x60) as u8,
t,
l,
b,
r,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
Some((start_angle, arc_angle)),
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
0x68..=0x6C => {
let start_angle = bus.read_word(pos) as i16;
let arc_angle = bus.read_word(pos + 2) as i16;
pos += 4;
if let Some((t, l, b, r)) = last_shape_rect {
draw_shape_oval_or_arc(
bus,
(opcode - 0x68) as u8,
t,
l,
b,
r,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
Some((start_angle, arc_angle)),
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
}
0x70..=0x74 => {
let poly_size = bus.read_word(pos) as u32;
let poly_ptr = pos;
pos += poly_size;
render_pict_polygon(
bus,
poly_ptr,
(opcode - 0x70) as u8,
pen_size,
pn_pat,
bk_pat,
fill_pat,
screen_mode,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region.as_ref(),
dst_clip,
fg_idx,
bg_idx,
);
}
0x78..=0x7C => {}
0x80..=0x84 => {
let rgn_size = bus.read_word(pos) as u32;
let rgn_top = bus.read_word(pos + 2) as i16;
let rgn_left = bus.read_word(pos + 4) as i16;
let rgn_bottom = bus.read_word(pos + 6) as i16;
let rgn_right = bus.read_word(pos + 8) as i16;
pos += rgn_size;
let kind = (opcode - 0x80) as u8; draw_shape_rect(
bus,
kind,
rgn_top,
rgn_left,
rgn_bottom,
rgn_right,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region.as_ref(),
dst_clip,
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
0x88..=0x8C => {}
0x90 | 0x91 => {
pos = parse_bits_rect(
bus,
pos,
opcode == 0x91,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
device_clut,
fg_idx,
bg_idx,
clip_region.as_ref(),
dst_clip,
);
}
0x98 | 0x99 => {
let (new_pos, clut16) = parse_pack_bits_rect(
bus,
pos,
opcode == 0x99,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
device_clut,
device_ct_seed,
fg_idx,
bg_idx,
clip_region.as_ref(),
dst_clip,
);
pos = new_pos;
if let Some(ct) = clut16 {
if preferred_clut.is_none() {
preferred_clut = Some(ct.clone());
} else if !clut_resembles_canonical_8bpp(&ct) {
if let Some(ref existing) = preferred_clut {
if clut_resembles_canonical_8bpp(existing) {
preferred_clut = Some(ct.clone());
}
}
}
last_clut = Some(ct);
}
}
0x9A | 0x9B => {
pos = parse_direct_bits_rect(
bus,
pos,
opcode == 0x9B,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
device_clut,
clip_region.as_ref(),
dst_clip,
);
}
0xA0 => {
pos += 2;
}
0xA1 => {
pos += 2;
let data_len = bus.read_word(pos) as u32;
pos += 2 + data_len;
if is_v2 && !data_len.is_multiple_of(2) {
pos += 1;
}
}
0xFF => {
break;
}
0x0C00 => {
pos += 24;
}
0x02FF => {
pos += 2;
}
_ => {
if is_v2 {
if (0x00A2..=0x00AF).contains(&opcode) {
let data_len = bus.read_word(pos) as u32;
pos += 2 + data_len;
pos = align_pict_pos(pos);
} else if (0x00B0..=0x00CF).contains(&opcode)
|| (0x8000..=0x80FF).contains(&opcode)
{
} else if (0x00D0..=0x00FE).contains(&opcode) || opcode >= 0x8100 {
let data_len = bus.read_long(pos);
pos += 4 + data_len;
pos = align_pict_pos(pos);
} else if (0x0100..=0x7FFF).contains(&opcode) {
pos += u32::from(opcode >> 8) * 2;
} else {
eprintln!(
"[PICT] Unknown v2 opcode 0x{:04X} at offset {} - stopping",
opcode,
pos - 2 - pic_ptr
);
break;
}
} else {
if let Some(new_pos) = skip_reserved_v1_opcode(bus, opcode, pos) {
pos = new_pos;
continue;
}
eprintln!(
"[PICT] Unknown v1 opcode 0x{:02X} at offset {} - stopping",
opcode,
pos - 1 - pic_ptr
);
break;
}
}
}
}
let returned_clut = preferred_clut.or(last_clut);
(true, returned_clut)
}
pub(crate) fn peek_initial_packbits_clut(
bus: &MacMemoryBus,
pic_ptr: u32,
) -> Option<Vec<[u16; 3]>> {
if pic_ptr == 0 {
return None;
}
let frame_top = bus.read_word(pic_ptr + 2) as i16;
let frame_left = bus.read_word(pic_ptr + 4) as i16;
let frame_bottom = bus.read_word(pic_ptr + 6) as i16;
let frame_right = bus.read_word(pic_ptr + 8) as i16;
if frame_bottom <= frame_top || frame_right <= frame_left {
return None;
}
let mut pos = pic_ptr + 10;
let mut is_v2 = false;
for _ in 0..10000 {
if is_v2 && !pos.is_multiple_of(2) {
pos += 1;
}
let opcode = if is_v2 {
let opcode = bus.read_word(pos);
pos += 2;
opcode
} else {
let opcode = bus.read_byte(pos) as u16;
pos += 1;
opcode
};
match opcode {
0x00 => {}
0x01 => {
let rgn_size = bus.read_word(pos) as u32;
pos += rgn_size;
}
0x02 | 0x09 | 0x0A | 0x10 => pos += 8,
0x03 | 0x05 | 0x08 | 0x0D | 0x15 | 0x16 | 0xA0 | 0x02FF => pos += 2,
0x04 => pos += if is_v2 { 2 } else { 1 },
0x06 | 0x07 | 0x0B | 0x0C | 0x0E | 0x0F => pos += 4,
0x11 => {
let version = bus.read_byte(pos);
pos += 1;
if version == 0x02 {
pos += 1;
is_v2 = true;
}
}
0x12..=0x14 => pos = skip_pixpat(bus, pos),
0x1A | 0x1B | 0x1D | 0x1F => pos += 6,
0x1C | 0x1E => {}
0x24..=0x27 | 0x2C..=0x2F => {
let data_len = bus.read_word(pos) as u32;
pos += 2 + data_len;
pos = align_pict_pos(pos);
}
0x98 | 0x99 => return peek_pack_bits_rect_clut(bus, pos),
0xA1 => {
pos += 2;
let data_len = bus.read_word(pos) as u32;
pos += 2 + data_len;
if is_v2 && !data_len.is_multiple_of(2) {
pos += 1;
}
}
0xFF => return None,
0x0C00 => pos += 24,
_ => {
if is_v2 {
if (0x00A2..=0x00AF).contains(&opcode) {
let data_len = bus.read_word(pos) as u32;
pos += 2 + data_len;
pos = align_pict_pos(pos);
} else if (0x00B0..=0x00CF).contains(&opcode)
|| (0x8000..=0x80FF).contains(&opcode)
{
} else if (0x00D0..=0x00FE).contains(&opcode) || opcode >= 0x8100 {
let data_len = bus.read_long(pos);
pos += 4 + data_len;
pos = align_pict_pos(pos);
} else if (0x0100..=0x7FFF).contains(&opcode) {
pos += u32::from(opcode >> 8) * 2;
} else {
return None;
}
} else if let Some(new_pos) = skip_reserved_v1_opcode(bus, opcode, pos) {
pos = new_pos;
} else {
return None;
}
}
}
}
None
}
fn peek_pack_bits_rect_clut(bus: &MacMemoryBus, pos: u32) -> Option<Vec<[u16; 3]>> {
let row_bytes_raw = bus.read_word(pos);
if (row_bytes_raw & 0x8000) == 0 {
return None;
}
let (pos, pm) = read_pixmap(bus, pos);
let (pos, colors16, _ct_seed) = read_color_table(bus, pos);
let mode = bus.read_word(pos + 16);
if pm.pixel_size == 8 && pm.cmp_count == 1 && (mode & 0x003F) == 0 {
Some(colors16)
} else {
None
}
}
struct PixMapInfo {
row_bytes: u16,
bounds_top: i16,
bounds_left: i16,
bounds_bottom: i16,
bounds_right: i16,
pixel_size: u16,
cmp_count: u16,
pack_type: u16,
}
fn read_pixmap_with_base(bus: &MacMemoryBus, mut pos: u32) -> (u32, PixMapInfo) {
let _base_addr = bus.read_long(pos);
pos += 4;
read_pixmap(bus, pos)
}
fn read_pixmap(bus: &MacMemoryBus, mut pos: u32) -> (u32, PixMapInfo) {
let row_bytes_raw = bus.read_word(pos);
pos += 2;
let row_bytes = row_bytes_raw & 0x3FFF;
let bounds_top = bus.read_word(pos) as i16;
pos += 2;
let bounds_left = bus.read_word(pos) as i16;
pos += 2;
let bounds_bottom = bus.read_word(pos) as i16;
pos += 2;
let bounds_right = bus.read_word(pos) as i16;
pos += 2;
let _version = bus.read_word(pos);
pos += 2;
let pack_type = bus.read_word(pos);
pos += 2;
let _pack_size = bus.read_long(pos);
pos += 4;
let _h_res = bus.read_long(pos);
pos += 4;
let _v_res = bus.read_long(pos);
pos += 4;
let _pixel_type = bus.read_word(pos);
pos += 2;
let pixel_size = bus.read_word(pos);
pos += 2;
let cmp_count = bus.read_word(pos);
pos += 2;
let _cmp_size = bus.read_word(pos);
pos += 2;
let _plane_bytes = bus.read_long(pos);
pos += 4;
let _pm_table = bus.read_long(pos);
pos += 4;
let _pm_reserved = bus.read_long(pos);
pos += 4;
(
pos,
PixMapInfo {
row_bytes,
bounds_top,
bounds_left,
bounds_bottom,
bounds_right,
pixel_size,
cmp_count,
pack_type,
},
)
}
fn skip_pixpat(bus: &MacMemoryBus, mut pos: u32) -> u32 {
let pat_type = bus.read_word(pos);
pos += 2;
pos += 8;
if pat_type == 2 {
return pos + 6;
}
if pat_type != 1 {
return pos;
}
let (new_pos, pm) = read_pixmap(bus, pos);
pos = new_pos;
let (new_pos, _colors16, _ct_seed) = read_color_table(bus, pos);
pos = new_pos;
skip_pixdata(bus, pos, &pm)
}
fn skip_pixdata(bus: &MacMemoryBus, mut pos: u32, pm: &PixMapInfo) -> u32 {
let height = (pm.bounds_bottom - pm.bounds_top).max(0) as u32;
let row_bytes = u32::from(pm.row_bytes);
if pm.pack_type == 1 || pm.row_bytes < 8 {
return pos + row_bytes.saturating_mul(height);
}
if pm.pack_type == 2 {
let data_bytes = if pm.pixel_size == 32 {
row_bytes.saturating_mul(height).saturating_mul(3) / 4
} else {
row_bytes.saturating_mul(height)
};
return pos + data_bytes;
}
for _ in 0..height {
let byte_count = if pm.row_bytes > 250 {
let count = u32::from(bus.read_word(pos));
pos += 2;
count
} else {
let count = u32::from(bus.read_byte(pos));
pos += 1;
count
};
pos += byte_count;
}
pos
}
fn read_pict_u16(bytes: &[u8], pos: usize) -> Option<u16> {
let hi = *bytes.get(pos)?;
let lo = *bytes.get(pos + 1)?;
Some(u16::from_be_bytes([hi, lo]))
}
fn read_pict_u32(bytes: &[u8], pos: usize) -> Option<u32> {
let b0 = *bytes.get(pos)?;
let b1 = *bytes.get(pos + 1)?;
let b2 = *bytes.get(pos + 2)?;
let b3 = *bytes.get(pos + 3)?;
Some(u32::from_be_bytes([b0, b1, b2, b3]))
}
fn pict_add(pos: usize, amount: usize, len: usize) -> Option<usize> {
let next = pos.checked_add(amount)?;
if next <= len {
Some(next)
} else {
None
}
}
fn pict_align_index(pos: usize, len: usize) -> Option<usize> {
pict_add(pos, usize::from(!pos.is_multiple_of(2)), len)
}
fn read_pixmap_bytes(bytes: &[u8], mut pos: usize) -> Option<(usize, PixMapInfo)> {
let row_bytes_raw = read_pict_u16(bytes, pos)?;
pos += 2;
let row_bytes = row_bytes_raw & 0x3FFF;
let bounds_top = read_pict_u16(bytes, pos)? as i16;
pos += 2;
let bounds_left = read_pict_u16(bytes, pos)? as i16;
pos += 2;
let bounds_bottom = read_pict_u16(bytes, pos)? as i16;
pos += 2;
let bounds_right = read_pict_u16(bytes, pos)? as i16;
pos += 2;
pos = pict_add(pos, 2, bytes.len())?; let pack_type = read_pict_u16(bytes, pos)?;
pos += 2;
pos = pict_add(pos, 4 + 4 + 4 + 2, bytes.len())?; let pixel_size = read_pict_u16(bytes, pos)?;
pos += 2;
let cmp_count = read_pict_u16(bytes, pos)?;
pos += 2;
pos = pict_add(pos, 2 + 4 + 4 + 4, bytes.len())?;
Some((
pos,
PixMapInfo {
row_bytes,
bounds_top,
bounds_left,
bounds_bottom,
bounds_right,
pixel_size,
cmp_count,
pack_type,
},
))
}
fn skip_color_table_bytes(bytes: &[u8], pos: usize) -> Option<usize> {
let ct_size = usize::from(read_pict_u16(bytes, pos + 6)?);
pict_add(
pos,
8usize.checked_add((ct_size + 1).checked_mul(8)?)?,
bytes.len(),
)
}
fn skip_pixpat_bytes(bytes: &[u8], mut pos: usize) -> Option<usize> {
let pat_type = read_pict_u16(bytes, pos)?;
pos = pict_add(pos, 2 + 8, bytes.len())?;
if pat_type == 2 {
return pict_add(pos, 6, bytes.len());
}
if pat_type != 1 {
return Some(pos);
}
let (new_pos, pm) = read_pixmap_bytes(bytes, pos)?;
let new_pos = skip_color_table_bytes(bytes, new_pos)?;
skip_pixdata_bytes(bytes, new_pos, &pm)
}
fn skip_pixdata_bytes(bytes: &[u8], mut pos: usize, pm: &PixMapInfo) -> Option<usize> {
let height = usize::try_from((pm.bounds_bottom - pm.bounds_top).max(0)).ok()?;
let row_bytes = usize::from(pm.row_bytes);
if pm.pack_type == 1 || pm.row_bytes < 8 {
return pict_add(pos, row_bytes.checked_mul(height)?, bytes.len());
}
if pm.pack_type == 2 {
let data_bytes = if pm.pixel_size == 32 {
row_bytes.checked_mul(height)?.checked_mul(3)? / 4
} else {
row_bytes.checked_mul(height)?
};
return pict_add(pos, data_bytes, bytes.len());
}
for _ in 0..height {
let byte_count = if pm.row_bytes > 250 {
let count = usize::from(read_pict_u16(bytes, pos)?);
pos = pict_add(pos, 2, bytes.len())?;
count
} else {
let count = usize::from(*bytes.get(pos)?);
pos = pict_add(pos, 1, bytes.len())?;
count
};
pos = pict_add(pos, byte_count, bytes.len())?;
}
Some(pos)
}
fn skip_bits_rect_bytes(bytes: &[u8], mut pos: usize, has_rgn: bool) -> Option<usize> {
let row_bytes = usize::from(read_pict_u16(bytes, pos)? & 0x3FFF);
let bounds_top = read_pict_u16(bytes, pos + 2)? as i16;
let bounds_bottom = read_pict_u16(bytes, pos + 6)? as i16;
pos = pict_add(pos, 10 + 18, bytes.len())?;
if has_rgn {
let rgn_size = usize::from(read_pict_u16(bytes, pos)?);
pos = pict_add(pos, rgn_size, bytes.len())?;
}
let height = usize::try_from((bounds_bottom - bounds_top).max(0)).ok()?;
pict_add(pos, row_bytes.checked_mul(height)?, bytes.len())
}
fn skip_pack_bits_rect_bytes(bytes: &[u8], mut pos: usize, has_rgn: bool) -> Option<usize> {
let row_bytes_raw = read_pict_u16(bytes, pos)?;
let is_pixmap = (row_bytes_raw & 0x8000) != 0;
let pm = if is_pixmap {
let (new_pos, pm) = read_pixmap_bytes(bytes, pos)?;
pos = skip_color_table_bytes(bytes, new_pos)?;
pm
} else {
let row_bytes = row_bytes_raw & 0x3FFF;
let bounds_top = read_pict_u16(bytes, pos + 2)? as i16;
let bounds_left = read_pict_u16(bytes, pos + 4)? as i16;
let bounds_bottom = read_pict_u16(bytes, pos + 6)? as i16;
let bounds_right = read_pict_u16(bytes, pos + 8)? as i16;
pos = pict_add(pos, 10, bytes.len())?;
PixMapInfo {
row_bytes,
bounds_top,
bounds_left,
bounds_bottom,
bounds_right,
pixel_size: 1,
cmp_count: 1,
pack_type: 0,
}
};
pos = pict_add(pos, 18, bytes.len())?;
if has_rgn {
let rgn_size = usize::from(read_pict_u16(bytes, pos)?);
pos = pict_add(pos, rgn_size, bytes.len())?;
}
skip_pixdata_bytes(bytes, pos, &pm)
}
fn skip_direct_bits_rect_bytes(bytes: &[u8], mut pos: usize, has_rgn: bool) -> Option<usize> {
pos = pict_add(pos, 4, bytes.len())?; let (new_pos, pm) = read_pixmap_bytes(bytes, pos)?;
pos = skip_color_table_bytes(bytes, new_pos)?;
pos = pict_add(pos, 18, bytes.len())?;
if has_rgn {
let rgn_size = usize::from(read_pict_u16(bytes, pos)?);
pos = pict_add(pos, rgn_size, bytes.len())?;
}
skip_pixdata_bytes(bytes, pos, &pm)
}
fn skip_v1_reserved_bytes(bytes: &[u8], opcode: u16, pos: usize) -> Option<usize> {
match opcode {
0x35..=0x37 | 0x45..=0x47 | 0x55..=0x57 => pict_add(pos, 8, bytes.len()),
0x3D..=0x3F | 0x4D..=0x4F | 0x5D..=0x5F | 0x7D..=0x7F | 0x8D..=0x8F => Some(pos),
0x65..=0x67 => pict_add(pos, 12, bytes.len()),
0x6D..=0x6F => pict_add(pos, 4, bytes.len()),
0x75..=0x77 | 0x85..=0x87 => {
let data_len = usize::from(read_pict_u16(bytes, pos)?);
pict_add(pos, data_len, bytes.len())
}
_ => None,
}
}
pub(crate) fn picture_stream_len(bytes: &[u8]) -> Option<usize> {
if bytes.len() < 10 {
return None;
}
let mut pos = 10usize;
let mut opcount = 0usize;
let mut is_v2 = false;
while pos < bytes.len() {
if opcount > 1_000_000 {
return None;
}
opcount += 1;
if is_v2 {
pos = pict_align_index(pos, bytes.len())?;
}
let opcode = if is_v2 {
let op = read_pict_u16(bytes, pos)?;
pos = pict_add(pos, 2, bytes.len())?;
op
} else {
let op = u16::from(*bytes.get(pos)?);
pos = pict_add(pos, 1, bytes.len())?;
op
};
pos = match opcode {
0x00
| 0x1C
| 0x1E
| 0x38..=0x3C
| 0x48..=0x4C
| 0x58..=0x5C
| 0x78..=0x7C
| 0x88..=0x8C => pos,
0x01 | 0x70..=0x74 | 0x80..=0x84 => {
let data_len = usize::from(read_pict_u16(bytes, pos)?);
pict_add(pos, data_len, bytes.len())?
}
0x02 | 0x09 | 0x0A | 0x10 => pict_add(pos, 8, bytes.len())?,
0x03 | 0x05 | 0x08 | 0x0D | 0x15 | 0x16 => pict_add(pos, 2, bytes.len())?,
0x04 => pict_add(pos, if is_v2 { 2 } else { 1 }, bytes.len())?,
0x06 | 0x07 | 0x0B | 0x0C | 0x0E | 0x0F | 0x21 | 0x68..=0x6C => {
pict_add(pos, 4, bytes.len())?
}
0x11 => {
let version = *bytes.get(pos)?;
let next = pict_add(pos, 1, bytes.len())?;
if version == 0x02 {
pos = pict_add(next, 1, bytes.len())?;
is_v2 = true;
pos
} else {
next
}
}
0x12..=0x14 => skip_pixpat_bytes(bytes, pos)?,
0x1A | 0x1B | 0x1D | 0x1F | 0x22 => pict_add(pos, 6, bytes.len())?,
0x20 | 0x30..=0x34 | 0x40..=0x44 | 0x50..=0x54 => pict_add(pos, 8, bytes.len())?,
0x23 | 0xA0 => pict_add(pos, 2, bytes.len())?,
0x28 => {
let len = usize::from(*bytes.get(pos + 4)?);
let mut next = pict_add(pos, 5usize.checked_add(len)?, bytes.len())?;
if is_v2 && !(1 + len).is_multiple_of(2) {
next = pict_add(next, 1, bytes.len())?;
}
next
}
0x29 | 0x2A => {
let len = usize::from(*bytes.get(pos + 1)?);
let mut next = pict_add(pos, 2usize.checked_add(len)?, bytes.len())?;
if is_v2 && len.is_multiple_of(2) {
next = pict_add(next, 1, bytes.len())?;
}
next
}
0x2B => {
let len = usize::from(*bytes.get(pos + 2)?);
let mut next = pict_add(pos, 3usize.checked_add(len)?, bytes.len())?;
if is_v2 && !(1 + len).is_multiple_of(2) {
next = pict_add(next, 1, bytes.len())?;
}
next
}
0x2C | 0x2D | 0x2E | 0x24..=0x27 | 0x2F => {
let data_len = usize::from(read_pict_u16(bytes, pos)?);
let next = pict_add(pos, 2usize.checked_add(data_len)?, bytes.len())?;
pict_align_index(next, bytes.len())?
}
0x60..=0x64 => pict_add(pos, 12, bytes.len())?,
0x90 => skip_bits_rect_bytes(bytes, pos, false)?,
0x91 => skip_bits_rect_bytes(bytes, pos, true)?,
0x98 => skip_pack_bits_rect_bytes(bytes, pos, false)?,
0x99 => skip_pack_bits_rect_bytes(bytes, pos, true)?,
0x9A => skip_direct_bits_rect_bytes(bytes, pos, false)?,
0x9B => skip_direct_bits_rect_bytes(bytes, pos, true)?,
0xA1 => {
let data_len = usize::from(read_pict_u16(bytes, pos + 2)?);
let mut next = pict_add(pos, 4usize.checked_add(data_len)?, bytes.len())?;
if is_v2 && !data_len.is_multiple_of(2) {
next = pict_add(next, 1, bytes.len())?;
}
next
}
0xFF => return Some(pos),
0x0C00 => pict_add(pos, 24, bytes.len())?,
0x02FF => pict_add(pos, 2, bytes.len())?,
_ if is_v2 => {
if (0x00A2..=0x00AF).contains(&opcode) {
let data_len = usize::from(read_pict_u16(bytes, pos)?);
let next = pict_add(pos, 2usize.checked_add(data_len)?, bytes.len())?;
pict_align_index(next, bytes.len())?
} else if (0x00B0..=0x00CF).contains(&opcode) || (0x8000..=0x80FF).contains(&opcode)
{
pos
} else if (0x00D0..=0x00FE).contains(&opcode) || opcode >= 0x8100 {
let data_len = usize::try_from(read_pict_u32(bytes, pos)?).ok()?;
let next = pict_add(pos, 4usize.checked_add(data_len)?, bytes.len())?;
pict_align_index(next, bytes.len())?
} else if (0x0100..=0x7FFF).contains(&opcode) {
pict_add(pos, usize::from(opcode >> 8).checked_mul(2)?, bytes.len())?
} else {
return None;
}
}
_ => skip_v1_reserved_bytes(bytes, opcode, pos)?,
};
}
None
}
fn read_color_table(bus: &MacMemoryBus, mut pos: u32) -> (u32, Vec<[u16; 3]>, u32) {
let ct_seed = bus.read_long(pos);
pos += 4;
let ct_flags = bus.read_word(pos);
pos += 2;
let ct_size = bus.read_word(pos) as u32;
pos += 2;
if trace_pict_enabled() {
eprintln!(
"[PICT] ColorTable ctSeed=${:08X} ctFlags=0x{:04X} ctSize={} at ${:08X}",
ct_seed,
ct_flags,
ct_size,
pos - 8
);
}
let mut colors16 = vec![[0u16; 3]; 256];
for i in 0..=ct_size {
let value = bus.read_word(pos) as usize;
pos += 2;
let r = bus.read_word(pos);
pos += 2;
let g = bus.read_word(pos);
pos += 2;
let b = bus.read_word(pos);
pos += 2;
let idx = if ct_flags & 0x8000 != 0 {
i as usize
} else {
value
};
if idx < 256 {
colors16[idx] = [r, g, b];
}
}
if trace_pict_palette_enabled() {
for index in [
0usize, 1, 2, 15, 16, 17, 32, 43, 50, 93, 100, 150, 185, 220, 245,
] {
if index < colors16.len() {
let [r, g, b] = colors16[index];
eprintln!("[PICT] clut[{}]=({:04X},{:04X},{:04X})", index, r, g, b);
}
}
}
(pos, colors16, ct_seed)
}
fn unpack_bits_chunk16_into(
bus: &MacMemoryBus,
mut pos: u32,
row_bytes: u16,
result: &mut Vec<u8>,
) -> u32 {
let byte_count = if row_bytes > 250 {
let bc = bus.read_word(pos) as u32;
pos += 2;
bc
} else {
let bc = bus.read_byte(pos) as u32;
pos += 1;
bc
};
let end_pos = pos + byte_count;
result.clear();
result.reserve(row_bytes as usize);
if end_pos <= bus.ram_size() {
unpack_bits_chunk16_data_into(bus.ram_slice(pos, byte_count), row_bytes, result);
return end_pos;
}
while pos < end_pos && result.len() < row_bytes as usize {
let flag = bus.read_byte(pos) as i8;
pos += 1;
if flag >= 0 {
let count = (flag as usize) + 1;
for _ in 0..count {
if pos + 1 < end_pos {
result.push(bus.read_byte(pos));
result.push(bus.read_byte(pos + 1));
pos += 2;
}
}
} else if flag != -128 {
let count = (-(flag as i16)) as usize + 1;
let hi = bus.read_byte(pos);
let lo = bus.read_byte(pos + 1);
pos += 2;
for _ in 0..count {
result.push(hi);
result.push(lo);
}
}
}
end_pos
}
fn unpack_bits_chunk16_data_into(data: &[u8], row_bytes: u16, result: &mut Vec<u8>) {
let mut pos = 0usize;
while pos < data.len() && result.len() < row_bytes as usize {
let flag = data[pos] as i8;
pos += 1;
if flag >= 0 {
let count = (flag as usize) + 1;
let byte_count = count.saturating_mul(2).min(data.len().saturating_sub(pos));
result.extend_from_slice(&data[pos..pos + byte_count]);
pos += byte_count;
} else if flag != -128 {
let count = (-(flag as i16)) as usize + 1;
if pos + 1 >= data.len() {
break;
}
let hi = data[pos];
let lo = data[pos + 1];
pos += 2;
for _ in 0..count {
result.push(hi);
result.push(lo);
}
}
}
}
fn unpack_bits_chunk16(bus: &MacMemoryBus, pos: u32, row_bytes: u16) -> (u32, Vec<u8>) {
let mut result = Vec::with_capacity(row_bytes as usize);
let end_pos = unpack_bits_chunk16_into(bus, pos, row_bytes, &mut result);
(end_pos, result)
}
fn unpack_bits_with_byte_count_row_bytes(
bus: &MacMemoryBus,
pos: u32,
decoded_row_bytes: u16,
byte_count_row_bytes: u16,
) -> (u32, Vec<u8>) {
let mut result = Vec::with_capacity(decoded_row_bytes as usize);
let end_pos = unpack_bits_with_byte_count_row_bytes_into(
bus,
pos,
decoded_row_bytes,
byte_count_row_bytes,
&mut result,
);
(end_pos, result)
}
fn unpack_bits_with_byte_count_row_bytes_into(
bus: &MacMemoryBus,
mut pos: u32,
decoded_row_bytes: u16,
byte_count_row_bytes: u16,
result: &mut Vec<u8>,
) -> u32 {
let byte_count = if byte_count_row_bytes > 250 {
let bc = bus.read_word(pos) as u32;
pos += 2;
bc
} else {
let bc = bus.read_byte(pos) as u32;
pos += 1;
bc
};
let end_pos = pos + byte_count;
result.clear();
result.reserve(decoded_row_bytes as usize);
if end_pos <= bus.ram_size() {
unpack_bits_data_into(bus.ram_slice(pos, byte_count), decoded_row_bytes, result);
return end_pos;
}
while pos < end_pos && result.len() < (decoded_row_bytes as usize) * 2 {
let flag = bus.read_byte(pos) as i8;
pos += 1;
if flag >= 0 {
let count = (flag as usize) + 1;
for _ in 0..count {
if pos < end_pos {
result.push(bus.read_byte(pos));
pos += 1;
}
}
} else if flag != -128 {
let count = (-(flag as i16)) as usize + 1;
let val = bus.read_byte(pos);
pos += 1;
for _ in 0..count {
result.push(val);
}
}
}
end_pos
}
fn unpack_bits_with_byte_count_row_bytes_mapped_into(
bus: &MacMemoryBus,
mut pos: u32,
decoded_row_bytes: u16,
byte_count_row_bytes: u16,
src_to_dst: &[u8; 256],
result: &mut Vec<u8>,
) -> u32 {
let byte_count = if byte_count_row_bytes > 250 {
let bc = bus.read_word(pos) as u32;
pos += 2;
bc
} else {
let bc = bus.read_byte(pos) as u32;
pos += 1;
bc
};
let end_pos = pos + byte_count;
result.clear();
result.reserve(decoded_row_bytes as usize);
if end_pos <= bus.ram_size() {
unpack_bits_data_mapped_into(
bus.ram_slice(pos, byte_count),
decoded_row_bytes,
src_to_dst,
result,
);
return end_pos;
}
while pos < end_pos && result.len() < (decoded_row_bytes as usize) * 2 {
let flag = bus.read_byte(pos) as i8;
pos += 1;
if flag >= 0 {
let count = (flag as usize) + 1;
for _ in 0..count {
if pos < end_pos {
result.push(src_to_dst[bus.read_byte(pos) as usize]);
pos += 1;
}
}
} else if flag != -128 {
let count = (-(flag as i16)) as usize + 1;
let val = src_to_dst[bus.read_byte(pos) as usize];
pos += 1;
result.extend(std::iter::repeat(val).take(count));
}
}
end_pos
}
fn unpack_bits_data_into(data: &[u8], decoded_row_bytes: u16, result: &mut Vec<u8>) {
let mut pos = 0usize;
while pos < data.len() && result.len() < (decoded_row_bytes as usize) * 2 {
let flag = data[pos] as i8;
pos += 1;
if flag >= 0 {
let count = (flag as usize) + 1;
let end = pos.saturating_add(count).min(data.len());
result.extend_from_slice(&data[pos..end]);
pos = end;
} else if flag != -128 {
let count = (-(flag as i16)) as usize + 1;
let Some(&val) = data.get(pos) else {
break;
};
pos += 1;
result.extend(std::iter::repeat(val).take(count));
}
}
}
fn unpack_bits_data_mapped_into(
data: &[u8],
decoded_row_bytes: u16,
src_to_dst: &[u8; 256],
result: &mut Vec<u8>,
) {
let mut pos = 0usize;
while pos < data.len() && result.len() < (decoded_row_bytes as usize) * 2 {
let flag = data[pos] as i8;
pos += 1;
if flag >= 0 {
let count = (flag as usize) + 1;
let end = pos.saturating_add(count).min(data.len());
result.extend(
data[pos..end]
.iter()
.map(|&pixel| src_to_dst[pixel as usize]),
);
pos = end;
} else if flag != -128 {
let count = (-(flag as i16)) as usize + 1;
let Some(&val) = data.get(pos) else {
break;
};
pos += 1;
result.extend(std::iter::repeat(src_to_dst[val as usize]).take(count));
}
}
}
fn write_pixel(
bus: &mut MacMemoryBus,
screen_base: u32,
screen_rb: u32,
x: i32,
y: i32,
color_index: u8,
screen_w: i32,
screen_h: i32,
pixel_size: u16,
) {
if x < 0 || y < 0 || x >= screen_w || y >= screen_h {
return;
}
if pixel_size == 1 {
let byte_offset = (x as u32) / 8;
let bit = 7 - ((x as u32) % 8);
let addr = screen_base + (y as u32) * screen_rb + byte_offset;
let byte = bus.read_byte(addr);
if color_index != 0 {
bus.write_byte(addr, byte | (1 << bit));
} else {
bus.write_byte(addr, byte & !(1 << bit));
}
} else {
let addr = screen_base + (y as u32) * screen_rb + (x as u32);
bus.write_byte(addr, color_index);
}
}
fn dst_clip_contains(clip: Option<&DstClip>, x: i32, y: i32) -> bool {
clip.is_none_or(|clip| clip.contains(x, y))
}
#[allow(clippy::too_many_arguments)]
fn write_pixel_clipped(
bus: &mut MacMemoryBus,
screen_base: u32,
screen_rb: u32,
x: i32,
y: i32,
color_index: u8,
screen_w: i32,
screen_h: i32,
pixel_size: u16,
dst_clip: Option<&DstClip>,
) {
if dst_clip_contains(dst_clip, x, y) {
write_pixel(
bus,
screen_base,
screen_rb,
x,
y,
color_index,
screen_w,
screen_h,
pixel_size,
);
}
}
fn clut_black_white_indices(clut: &[[u16; 3]; 256]) -> (u8, u8) {
let mut black_idx = 0u8;
let mut black_luma = u64::MAX;
let mut white_idx = 0u8;
let mut white_luma = 0u64;
for (idx, entry) in clut.iter().enumerate() {
let luma = u64::from(entry[0]) + u64::from(entry[1]) + u64::from(entry[2]);
if luma < black_luma || (luma == black_luma && idx as u8 > black_idx) {
black_idx = idx as u8;
black_luma = luma;
}
if luma > white_luma || (luma == white_luma && (idx as u8) < white_idx) {
white_idx = idx as u8;
white_luma = luma;
}
}
(black_idx, white_idx)
}
fn one_bit_destination_clut() -> [[u16; 3]; 256] {
let mut clut = [[0x0000u16, 0x0000, 0x0000]; 256];
clut[0] = [0xFFFF, 0xFFFF, 0xFFFF];
clut[255] = [0x0000, 0x0000, 0x0000];
clut
}
fn pict_qd_color_to_clut_index(color: u32, default_idx: u8, black_idx: u8, white_idx: u8) -> u8 {
match color {
0 => default_idx,
30 => white_idx,
33 => black_idx,
205 => 35, 341 => 173, 409 => 210, 69 => 17, 137 => 137, 273 => 69, _ => (color & 0xFF) as u8,
}
}
fn read_shape_rect(bus: &MacMemoryBus, pos: u32) -> (i16, i16, i16, i16) {
let t = bus.read_word(pos) as i16;
let l = bus.read_word(pos + 2) as i16;
let b = bus.read_word(pos + 4) as i16;
let r = bus.read_word(pos + 6) as i16;
(t, l, b, r)
}
fn transform_shape_rect(
src_top: i16,
src_left: i16,
src_bottom: i16,
src_right: i16,
frame_top: i16,
frame_left: i16,
dst_top: i16,
dst_left: i16,
scale_x: f64,
scale_y: f64,
) -> (i32, i32, i32, i32) {
let x1 = ((src_left as f64 - frame_left as f64) * scale_x + dst_left as f64).round() as i32;
let y1 = ((src_top as f64 - frame_top as f64) * scale_y + dst_top as f64).round() as i32;
let x2 = ((src_right as f64 - frame_left as f64) * scale_x + dst_left as f64).round() as i32;
let y2 = ((src_bottom as f64 - frame_top as f64) * scale_y + dst_top as f64).round() as i32;
(x1, y1, x2, y2)
}
#[allow(clippy::too_many_arguments)]
fn plot_dst_pixel(
bus: &mut MacMemoryBus,
screen_base: u32,
screen_rb: u32,
screen_w: i32,
screen_h: i32,
pixel_size: u16,
x: i32,
y: i32,
color_index: u8,
frame_top: i16,
frame_left: i16,
dst_top: i16,
dst_left: i16,
scale_x: f64,
scale_y: f64,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
) {
if let Some(rgn) = clip_region {
let inv_sx = if scale_x > 0.0 { 1.0 / scale_x } else { 1.0 };
let inv_sy = if scale_y > 0.0 { 1.0 / scale_y } else { 1.0 };
let pic_x = ((x - dst_left as i32) as f64 * inv_sx + frame_left as f64).floor() as i32;
let pic_y = ((y - dst_top as i32) as f64 * inv_sy + frame_top as f64).floor() as i32;
if !rgn.contains(pic_y, pic_x) {
return;
}
}
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
y,
color_index,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
}
#[allow(clippy::too_many_arguments)]
fn fill_dst_rect(
bus: &mut MacMemoryBus,
screen_mode: (u32, u32, u16, u16, u16),
x1: i32,
y1: i32,
x2: i32,
y2: i32,
frame_top: i16,
frame_left: i16,
dst_top: i16,
dst_left: i16,
scale_x: f64,
scale_y: f64,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
color_index: u8,
) {
let (sb, srb, sw, sh, ps) = screen_mode;
for y in y1..y2 {
for x in x1..x2 {
plot_dst_pixel(
bus,
sb,
srb,
sw as i32,
sh as i32,
ps,
x,
y,
color_index,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn fill_dst_rect_pat(
bus: &mut MacMemoryBus,
screen_mode: (u32, u32, u16, u16, u16),
x1: i32,
y1: i32,
x2: i32,
y2: i32,
frame_top: i16,
frame_left: i16,
dst_top: i16,
dst_left: i16,
scale_x: f64,
scale_y: f64,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
pattern: [u8; 8],
on_color: u8,
off_color: u8,
) {
if pattern == [0xFF; 8] {
fill_dst_rect(
bus,
screen_mode,
x1,
y1,
x2,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
on_color,
);
return;
}
if pattern == [0x00; 8] {
fill_dst_rect(
bus,
screen_mode,
x1,
y1,
x2,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
off_color,
);
return;
}
let (sb, srb, sw, sh, ps) = screen_mode;
for y in y1..y2 {
let row = pattern[y.rem_euclid(8) as usize];
for x in x1..x2 {
let bit = 1u8 << (7 - x.rem_euclid(8));
let color = if row & bit != 0 { on_color } else { off_color };
plot_dst_pixel(
bus,
sb,
srb,
sw as i32,
sh as i32,
ps,
x,
y,
color,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn draw_shape_rect(
bus: &mut MacMemoryBus,
kind: u8,
src_top: i16,
src_left: i16,
src_bottom: i16,
src_right: i16,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
screen_mode: (u32, u32, u16, u16, u16),
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
pen_size: (i16, i16),
pn_pat: [u8; 8],
bk_pat: [u8; 8],
fill_pat: [u8; 8],
fg_idx: u8,
bg_idx: u8,
) {
let kind = kind & 0x0F;
if src_bottom <= src_top || src_right <= src_left {
return;
}
let (x1, y1, x2, y2) = transform_shape_rect(
src_top, src_left, src_bottom, src_right, frame_top, frame_left, dst_top, dst_left,
scale_x, scale_y,
);
if x2 <= x1 || y2 <= y1 {
return;
}
match kind {
0 => {
let (pen_h, pen_w) = pen_size;
let eh = ((pen_h as f64 * scale_y).round() as i32).max(1);
let ew = ((pen_w as f64 * scale_x).round() as i32).max(1);
fill_dst_rect(
bus,
screen_mode,
x1,
y1,
x2,
y1 + eh,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
fg_idx,
);
fill_dst_rect(
bus,
screen_mode,
x1,
y2 - eh,
x2,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
fg_idx,
);
fill_dst_rect(
bus,
screen_mode,
x1,
y1,
x1 + ew,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
fg_idx,
);
fill_dst_rect(
bus,
screen_mode,
x2 - ew,
y1,
x2,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
fg_idx,
);
}
1 => {
fill_dst_rect_pat(
bus,
screen_mode,
x1,
y1,
x2,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
pn_pat,
fg_idx,
bg_idx,
);
}
4 => {
fill_dst_rect_pat(
bus,
screen_mode,
x1,
y1,
x2,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
fill_pat,
fg_idx,
bg_idx,
);
}
2 => {
fill_dst_rect_pat(
bus,
screen_mode,
x1,
y1,
x2,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
bk_pat,
bg_idx,
fg_idx,
);
}
3 => {
invert_dst_rect(
bus,
screen_mode,
x1,
y1,
x2,
y2,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
);
}
_ => {}
}
}
#[allow(clippy::too_many_arguments)]
fn invert_dst_rect(
bus: &mut MacMemoryBus,
screen_mode: (u32, u32, u16, u16, u16),
x1: i32,
y1: i32,
x2: i32,
y2: i32,
frame_top: i16,
frame_left: i16,
dst_top: i16,
dst_left: i16,
scale_x: f64,
scale_y: f64,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
) {
let (sb, srb, sw, sh, ps) = screen_mode;
let sw = sw as i32;
let sh = sh as i32;
for y in y1..y2 {
if y < 0 || y >= sh {
continue;
}
for x in x1..x2 {
if x < 0 || x >= sw {
continue;
}
if !dst_clip_contains(dst_clip, x, y) {
continue;
}
if let Some(rgn) = clip_region {
let inv_sx = if scale_x > 0.0 { 1.0 / scale_x } else { 1.0 };
let inv_sy = if scale_y > 0.0 { 1.0 / scale_y } else { 1.0 };
let pic_x =
((x - dst_left as i32) as f64 * inv_sx + frame_left as f64).floor() as i32;
let pic_y =
((y - dst_top as i32) as f64 * inv_sy + frame_top as f64).floor() as i32;
if !rgn.contains(pic_y, pic_x) {
continue;
}
}
if ps == 1 {
let addr = sb + (y as u32) * srb + (x as u32) / 8;
let bit = 7 - ((x as u32) % 8);
let byte = bus.read_byte(addr);
bus.write_byte(addr, byte ^ (1 << bit));
} else {
let addr = sb + (y as u32) * srb + (x as u32);
let byte = bus.read_byte(addr);
bus.write_byte(addr, byte ^ 0xFF);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn draw_shape_oval(
bus: &mut MacMemoryBus,
kind: u8,
src_top: i16,
src_left: i16,
src_bottom: i16,
src_right: i16,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
screen_mode: (u32, u32, u16, u16, u16),
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
pen_size: (i16, i16),
pn_pat: [u8; 8],
bk_pat: [u8; 8],
fill_pat: [u8; 8],
fg_idx: u8,
bg_idx: u8,
) {
draw_shape_oval_or_arc(
bus,
kind,
src_top,
src_left,
src_bottom,
src_right,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
screen_mode,
clip_region,
dst_clip,
None,
pen_size,
pn_pat,
bk_pat,
fill_pat,
fg_idx,
bg_idx,
);
}
#[allow(clippy::too_many_arguments)]
fn draw_shape_oval_or_arc(
bus: &mut MacMemoryBus,
kind: u8,
src_top: i16,
src_left: i16,
src_bottom: i16,
src_right: i16,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
screen_mode: (u32, u32, u16, u16, u16),
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
arc_angles: Option<(i16, i16)>,
pen_size: (i16, i16),
pn_pat: [u8; 8],
bk_pat: [u8; 8],
fill_pat: [u8; 8],
fg_idx: u8,
bg_idx: u8,
) {
let kind = kind & 0x0F;
if src_bottom <= src_top || src_right <= src_left {
return;
}
let (x1, y1, x2, y2) = transform_shape_rect(
src_top, src_left, src_bottom, src_right, frame_top, frame_left, dst_top, dst_left,
scale_x, scale_y,
);
if x2 <= x1 || y2 <= y1 {
return;
}
let cx = (x1 as f64 + x2 as f64) * 0.5;
let cy = (y1 as f64 + y2 as f64) * 0.5;
let rx = (x2 - x1) as f64 * 0.5;
let ry = (y2 - y1) as f64 * 0.5;
let (pen_h, pen_w) = pen_size;
let stamp_w = ((pen_w.max(1) as f64) * scale_x).max(1.0);
let stamp_h = ((pen_h.max(1) as f64) * scale_y).max(1.0);
let rx_in = (rx - stamp_w).max(0.0);
let ry_in = (ry - stamp_h).max(0.0);
let (sb, srb, sw, sh, ps) = screen_mode;
let sw = sw as i32;
let sh = sh as i32;
let arc_range = arc_angles.map(|(start_raw, extent_raw)| {
if extent_raw == 0 {
(f64::INFINITY, f64::INFINITY)
} else {
let mut start = start_raw as f64;
let mut extent = extent_raw as f64;
if extent < 0.0 {
start += extent;
extent = -extent;
}
if extent > 360.0 {
extent = 360.0;
}
start = start.rem_euclid(360.0);
(start, start + extent)
}
});
for y in y1..y2 {
let ny = (y as f64 + 0.5 - cy) / ry;
let ny2 = 1.0 - ny * ny;
if ny2 < 0.0 {
continue;
}
let hw_out = ny2.sqrt() * rx;
let xl_out = (cx - hw_out).round() as i32;
let xr_out = (cx + hw_out).round() as i32;
let (xl_in, xr_in) = if rx_in <= 0.0 || ry_in <= 0.0 {
(i32::MAX, i32::MIN)
} else {
let ny_in = (y as f64 + 0.5 - cy) / ry_in;
let ny2_in = 1.0 - ny_in * ny_in;
if ny2_in <= 0.0 {
(i32::MAX, i32::MIN)
} else {
let hw_in = ny2_in.sqrt() * rx_in;
((cx - hw_in).round() as i32, (cx + hw_in).round() as i32)
}
};
for x in xl_out..xr_out {
let inside_inner = x >= xl_in && x < xr_in;
let do_draw = match kind {
0 => !inside_inner, 1..=4 => true,
_ => false,
};
if !do_draw {
continue;
}
if let Some((a_start, a_end)) = arc_range {
if !a_start.is_finite() {
continue;
}
let angle = (-(y as f64 + 0.5 - cy)).atan2(x as f64 + 0.5 - cx);
let mut mac_angle = 90.0 - angle.to_degrees();
if mac_angle < 0.0 {
mac_angle += 360.0;
}
let in_range = (mac_angle >= a_start && mac_angle < a_end)
|| (a_end > 360.0 && mac_angle + 360.0 < a_end);
if !in_range {
continue;
}
}
let pat_row_idx = (y.rem_euclid(8)) as usize;
let pat_bit = 1u8 << (7 - x.rem_euclid(8) as u32);
let color = match kind {
0 => fg_idx,
1 => {
if pn_pat[pat_row_idx] & pat_bit != 0 {
fg_idx
} else {
bg_idx
}
}
2 => {
if bk_pat[pat_row_idx] & pat_bit != 0 {
bg_idx
} else {
fg_idx
}
}
4 => {
if fill_pat[pat_row_idx] & pat_bit != 0 {
fg_idx
} else {
bg_idx
}
}
_ => fg_idx,
};
if kind == 3 {
if x < 0 || x >= sw || y < 0 || y >= sh {
continue;
}
if !dst_clip_contains(dst_clip, x, y) {
continue;
}
if let Some(rgn) = clip_region {
let inv_sx = if scale_x > 0.0 { 1.0 / scale_x } else { 1.0 };
let inv_sy = if scale_y > 0.0 { 1.0 / scale_y } else { 1.0 };
let pic_x =
((x - dst_left as i32) as f64 * inv_sx + frame_left as f64).floor() as i32;
let pic_y =
((y - dst_top as i32) as f64 * inv_sy + frame_top as f64).floor() as i32;
if !rgn.contains(pic_y, pic_x) {
continue;
}
}
if ps == 1 {
let addr = sb + (y as u32) * srb + (x as u32) / 8;
let bit = 7 - ((x as u32) % 8);
let byte = bus.read_byte(addr);
bus.write_byte(addr, byte ^ (1 << bit));
} else {
let addr = sb + (y as u32) * srb + (x as u32);
let byte = bus.read_byte(addr);
bus.write_byte(addr, byte ^ 0xFF);
}
} else {
plot_dst_pixel(
bus,
sb,
srb,
sw,
sh,
ps,
x,
y,
color,
frame_top,
frame_left,
dst_top,
dst_left,
scale_x,
scale_y,
clip_region,
dst_clip,
);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_pict_polygon(
bus: &mut MacMemoryBus,
poly_ptr: u32,
kind: u8,
pen_size: (i16, i16),
pn_pat: [u8; 8],
bk_pat: [u8; 8],
fill_pat: [u8; 8],
screen_mode: (u32, u32, u16, u16, u16),
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
fg_idx: u8,
bg_idx: u8,
) {
let poly_size = bus.read_word(poly_ptr) as u32;
if poly_size < 10 {
return;
}
let n = (poly_size - 10) / 4;
if n < 2 {
return;
}
let verts_ptr = poly_ptr + 10;
let mut verts: Vec<(i16, i16)> = Vec::with_capacity(n as usize);
for i in 0..n {
let v = bus.read_word(verts_ptr + i * 4) as i16;
let h = bus.read_word(verts_ptr + i * 4 + 2) as i16;
verts.push((v, h));
}
if kind == 0 {
for i in 0..verts.len() {
let (v0, h0) = verts[i];
let (v1, h1) = verts[(i + 1) % verts.len()];
if v0 == v1 && h0 == h1 {
continue;
}
draw_picture_line(
bus,
screen_mode,
v0,
h0,
v1,
h1,
dst_top,
dst_left,
frame_top,
frame_left,
scale_x,
scale_y,
clip_region,
dst_clip,
pen_size,
pn_pat,
fg_idx,
);
}
return;
}
struct Edge {
y_min: i16,
y_max: i16,
x_at_ymin: f32,
inv_slope: f32,
}
let mut edges: Vec<Edge> = Vec::with_capacity(verts.len());
for i in 0..verts.len() {
let (v0, h0) = verts[i];
let (v1, h1) = verts[(i + 1) % verts.len()];
if v0 == v1 {
continue;
}
let (y_min, y_max, x_at_ymin) = if v0 < v1 {
(v0, v1, h0 as f32)
} else {
(v1, v0, h1 as f32)
};
let inv_slope = (h1 as f32 - h0 as f32) / (v1 as f32 - v0 as f32);
edges.push(Edge {
y_min,
y_max,
x_at_ymin,
inv_slope,
});
}
if edges.is_empty() {
return;
}
let bbox_top = bus.read_word(poly_ptr + 2) as i16;
let bbox_left = bus.read_word(poly_ptr + 4) as i16;
let bbox_bottom = bus.read_word(poly_ptr + 6) as i16;
let bbox_right = bus.read_word(poly_ptr + 8) as i16;
let (screen_base, screen_rb, screen_w, screen_h, pixel_size) = screen_mode;
let screen_w = screen_w as i32;
let screen_h = screen_h as i32;
for y in bbox_top..bbox_bottom {
let mut xs: Vec<f32> = Vec::with_capacity(edges.len());
for edge in &edges {
if y < edge.y_min || y >= edge.y_max {
continue;
}
let x = edge.x_at_ymin + (i32::from(y) - i32::from(edge.y_min)) as f32 * edge.inv_slope;
xs.push(x);
}
if xs.len() < 2 {
continue;
}
xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mut i = 0;
while i + 1 < xs.len() {
let x_start = xs[i].ceil() as i16;
let x_end = xs[i + 1].ceil() as i16;
for x in x_start..x_end.min(bbox_right) {
if x < bbox_left {
continue;
}
if let Some(rgn) = clip_region {
if !rgn.contains(y as i32, x as i32) {
continue;
}
}
let dx = ((i32::from(x) - i32::from(bbox_left)) as f64 * scale_x
+ (i32::from(dst_left) + i32::from(bbox_left) - i32::from(frame_left)) as f64)
as i32;
let dy = ((i32::from(y) - i32::from(bbox_top)) as f64 * scale_y
+ (i32::from(dst_top) + i32::from(bbox_top) - i32::from(frame_top)) as f64)
as i32;
let pat_row_idx = (dy.rem_euclid(8)) as usize;
let pat_bit = 1u8 << (7 - dx.rem_euclid(8) as u32);
match kind {
1 => {
let bit_set = pn_pat[pat_row_idx] & pat_bit != 0;
let color = if bit_set { fg_idx } else { bg_idx };
write_pixel_clipped(
bus,
screen_base,
screen_rb,
dx,
dy,
color,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
}
4 => {
let bit_set = fill_pat[pat_row_idx] & pat_bit != 0;
let color = if bit_set { fg_idx } else { bg_idx };
write_pixel_clipped(
bus,
screen_base,
screen_rb,
dx,
dy,
color,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
}
2 => {
let bit_set = bk_pat[pat_row_idx] & pat_bit != 0;
let color = if bit_set { bg_idx } else { fg_idx };
write_pixel_clipped(
bus,
screen_base,
screen_rb,
dx,
dy,
color,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
}
3 => {
if dx < 0 || dx >= screen_w || dy < 0 || dy >= screen_h {
continue;
}
if !dst_clip_contains(dst_clip, dx, dy) {
continue;
}
if pixel_size == 8 {
let addr = screen_base + (dy as u32) * screen_rb + (dx as u32);
let old = bus.read_byte(addr);
bus.write_byte(addr, old ^ 0xFF);
} else {
}
}
_ => {}
}
}
i += 2;
}
}
}
#[allow(clippy::too_many_arguments)]
fn draw_picture_line(
bus: &mut MacMemoryBus,
screen_mode: (u32, u32, u16, u16, u16),
v0: i16,
h0: i16,
v1: i16,
h1: i16,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
pen_size: (i16, i16),
pn_pat: [u8; 8],
fg_idx: u8,
) {
let (pen_h, pen_w) = pen_size;
if pen_h <= 0 || pen_w <= 0 {
return;
}
if pn_pat == [0u8; 8] {
return;
}
let stamp_w = ((pen_w as f64 * scale_x).round() as i32).max(1);
let stamp_h = ((pen_h as f64 * scale_y).round() as i32).max(1);
let (screen_base, screen_rb, screen_w, screen_h, pixel_size) = screen_mode;
let screen_w = screen_w as i32;
let screen_h = screen_h as i32;
let solid_black = pn_pat == [0xFFu8; 8];
let plot = |bus: &mut MacMemoryBus, cx: i32, cy: i32| {
if stamp_w == 1 && stamp_h == 1 {
if solid_black {
write_pixel_clipped(
bus,
screen_base,
screen_rb,
cx,
cy,
fg_idx,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
} else {
let row = pn_pat[cy.rem_euclid(8) as usize];
let bit = 1u8 << (7 - cx.rem_euclid(8));
if row & bit != 0 {
write_pixel_clipped(
bus,
screen_base,
screen_rb,
cx,
cy,
fg_idx,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
}
}
} else {
for dy in 0..stamp_h {
for dx in 0..stamp_w {
let ox = cx + dx;
let oy = cy + dy;
if solid_black {
write_pixel_clipped(
bus,
screen_base,
screen_rb,
ox,
oy,
fg_idx,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
} else {
let row = pn_pat[oy.rem_euclid(8) as usize];
let bit = 1u8 << (7 - ox.rem_euclid(8));
if row & bit != 0 {
write_pixel_clipped(
bus,
screen_base,
screen_rb,
ox,
oy,
fg_idx,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
}
}
}
}
}
};
let mut x0 = h0 as i32;
let mut y0 = v0 as i32;
let x1 = h1 as i32;
let y1 = v1 as i32;
let dx = (x1 - x0).abs();
let sx: i32 = if x0 < x1 { 1 } else { -1 };
let dy = -(y1 - y0).abs();
let sy: i32 = if y0 < y1 { 1 } else { -1 };
let mut err = dx + dy;
loop {
let plot_here = match clip_region {
Some(rgn) => rgn.contains(y0, x0),
None => true,
};
if plot_here {
let x = ((x0 - i32::from(frame_left)) as f64 * scale_x + dst_left as f64) as i32;
let y = ((y0 - i32::from(frame_top)) as f64 * scale_y + dst_top as f64) as i32;
plot(bus, x, y);
}
if x0 == x1 && y0 == y1 {
break;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x0 += sx;
}
if e2 <= dx {
err += dx;
y0 += sy;
}
}
}
#[allow(clippy::too_many_arguments)]
fn draw_picture_text(
bus: &mut MacMemoryBus,
screen_mode: (u32, u32, u16, u16, u16),
pen_v: i16,
pen_h: i16,
text_ptr: u32,
len: u32,
font_id: i16,
font_size: i16,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
fg_idx: u8,
bg_idx: u8,
tx_mode: i16,
) {
let (screen_base, screen_rb, screen_w, screen_h, pixel_size) = screen_mode;
let screen_w = screen_w as i32;
let screen_h = screen_h as i32;
let mut cur_h: i32 = pen_h as i32;
let inv_sx = if scale_x > 0.0 { 1.0 / scale_x } else { 1.0 };
let inv_sy = if scale_y > 0.0 { 1.0 / scale_y } else { 1.0 };
for i in 0..len {
let ch = bus.read_byte(text_ptr + i) as char;
if let Some((glyph, data)) = crate::quickdraw::text::get_glyph(font_id, font_size, ch) {
let gx0 = cur_h + glyph.origin_x as i32;
let gy0 = pen_v as i32 + glyph.origin_y as i32;
let gw = glyph.width as usize;
let gh = glyph.height as usize;
for row in 0..gh {
for col in 0..gw {
let idx = glyph.data_offset + row * gw + col;
if idx >= data.len() || data[idx] < 128 {
continue;
}
let pic_x = gx0 + col as i32;
let pic_y = gy0 + row as i32;
if let Some(rgn) = clip_region {
if !rgn.contains(pic_y, pic_x) {
continue;
}
}
let x =
((pic_x - i32::from(frame_left)) as f64 * scale_x + dst_left as f64) as i32;
let y =
((pic_y - i32::from(frame_top)) as f64 * scale_y + dst_top as f64) as i32;
let _ = inv_sx;
let _ = inv_sy;
if x < 0 || x >= screen_w || y < 0 || y >= screen_h {
continue;
}
if !dst_clip_contains(dst_clip, x, y) {
continue;
}
if pixel_size == 8 {
let addr = screen_base + (y as u32) * screen_rb + (x as u32);
match tx_mode & 0x3F {
2 => {
let old = bus.read_byte(addr);
bus.write_byte(addr, old ^ fg_idx);
}
3 => {
bus.write_byte(addr, bg_idx);
}
_ => {
bus.write_byte(addr, fg_idx);
}
}
} else {
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
y,
fg_idx,
screen_w,
screen_h,
pixel_size,
dst_clip,
);
}
}
}
cur_h += glyph.advance as i32;
} else {
cur_h += 6;
}
}
}
fn text_advance(bus: &MacMemoryBus, text_ptr: u32, len: u32, font_id: i16, font_size: i16) -> i16 {
let mut w: i32 = 0;
for i in 0..len {
let ch = bus.read_byte(text_ptr + i) as char;
if let Some((g, _)) = crate::quickdraw::text::get_glyph(font_id, font_size, ch) {
w += g.advance as i32;
} else {
w += 6;
}
}
w.clamp(i16::MIN as i32, i16::MAX as i32) as i16
}
pub(crate) fn closest_clut_index(r: u16, g: u16, b: u16, clut: &[[u16; 3]; 256]) -> u8 {
if clut_match_itable_enabled() {
let _ = clut;
return crate::trap::TrapDispatcher::standard_itable_lookup(r, g, b);
}
let rgb = [r, g, b];
if rgb == [0, 0, 0] && clut[255] == [0, 0, 0] {
return 255;
}
if rgb == [0xFFFF, 0xFFFF, 0xFFFF] && clut[0] == [0xFFFF, 0xFFFF, 0xFFFF] {
return 0;
}
if rgb == clut[255] {
return 255;
}
if rgb == clut[0] {
return 0;
}
if r == g && g == b {
let mut best_gray_idx = None;
let mut best_gray_dist = i64::MAX;
for (idx, entry) in clut.iter().enumerate() {
if entry[0] != entry[1] || entry[1] != entry[2] {
continue;
}
let dr = i64::from(r) - i64::from(entry[0]);
let d = dr * dr;
if d < best_gray_dist {
best_gray_dist = d;
best_gray_idx = Some(idx as u8);
if d == 0 {
break;
}
}
}
if let Some(idx) = best_gray_idx {
return idx;
}
}
let mut best_idx = 0u8;
let mut best_dist = i64::MAX;
for (idx, entry) in clut.iter().enumerate() {
let dr = i64::from(r) - i64::from(entry[0]);
let dg = i64::from(g) - i64::from(entry[1]);
let db = i64::from(b) - i64::from(entry[2]);
let d = dr * dr + dg * dg + db * db;
if d < best_dist {
best_dist = d;
best_idx = idx as u8;
if d == 0 {
break;
}
}
}
best_idx
}
fn build_src_to_dst_table(src_clut: &[[u16; 3]], device_clut: &[[u16; 3]; 256]) -> [u8; 256] {
let cache = SRC_TO_DST_TABLE_CACHE.get_or_init(|| Mutex::new(Vec::new()));
if let Ok(mut entries) = cache.lock() {
if let Some(pos) = entries.iter().position(|entry| {
entry.src_clut.as_slice() == src_clut && entry.dst_clut == *device_clut
}) {
let entry = entries.remove(pos);
let table = entry.table;
entries.push(entry);
return table;
}
}
let table = build_src_to_dst_table_uncached(src_clut, device_clut);
if let Ok(mut entries) = cache.lock() {
entries.push(SrcToDstTableCacheEntry {
src_clut: src_clut.to_vec(),
dst_clut: *device_clut,
table,
});
if entries.len() > SRC_TO_DST_TABLE_CACHE_LIMIT {
entries.remove(0);
}
}
table
}
fn build_src_to_dst_table_uncached(
src_clut: &[[u16; 3]],
device_clut: &[[u16; 3]; 256],
) -> [u8; 256] {
let mut table = [0u8; 256];
if should_preserve_source_palette_indices(src_clut, device_clut) {
for (i, slot) in table.iter_mut().enumerate() {
*slot = i as u8;
}
return table;
}
let gray_itable_enabled = !clut_match_legacy_gray_enabled();
let force_all = clut_match_device_itable_enabled();
let use_itable = force_all || (gray_itable_enabled && pict_clut_is_dense_grayscale(src_clut));
if use_itable {
let itable = build_device_itable(device_clut);
for (i, entry) in src_clut.iter().enumerate() {
if i >= 256 {
break;
}
table[i] = if *entry == device_clut[i] {
i as u8
} else {
let qr = (entry[0] >> 12) as u32;
let qg = (entry[1] >> 12) as u32;
let qb = (entry[2] >> 12) as u32;
itable[((qr << 8) | (qg << 4) | qb) as usize]
};
}
return table;
}
for (i, entry) in src_clut.iter().enumerate() {
if i >= 256 {
break;
}
table[i] = if *entry == device_clut[i] {
i as u8
} else if pict_clut_is_dense_grayscale(src_clut)
&& entry[0] == entry[1]
&& entry[1] == entry[2]
{
closest_grayscale_luminance_index(entry[0], device_clut)
} else {
closest_clut_index(entry[0], entry[1], entry[2], device_clut)
};
}
table
}
#[cfg(test)]
fn clear_src_to_dst_table_cache_for_tests() {
if let Some(cache) = SRC_TO_DST_TABLE_CACHE.get() {
cache.lock().expect("src-to-dst cache lock").clear();
}
}
fn build_device_itable(device_clut: &[[u16; 3]; 256]) -> [u8; 4096] {
let mut table = [0u8; 4096];
for cell in 0u32..4096 {
let qr = (cell >> 8) & 0xF;
let qg = (cell >> 4) & 0xF;
let qb = cell & 0xF;
let cr = ((qr << 12) | 0x0800) as i64;
let cg = ((qg << 12) | 0x0800) as i64;
let cb = ((qb << 12) | 0x0800) as i64;
let mut best_idx = 0u8;
let mut best_dist = i64::MAX;
for (idx, entry) in device_clut.iter().enumerate() {
let dr = cr - i64::from(entry[0]);
let dg = cg - i64::from(entry[1]);
let db = cb - i64::from(entry[2]);
let d = dr * dr + dg * dg + db * db;
if d < best_dist {
best_dist = d;
best_idx = idx as u8;
}
}
table[cell as usize] = best_idx;
}
table
}
fn closest_grayscale_luminance_index(luma: u16, clut: &[[u16; 3]; 256]) -> u8 {
let grayscale_entries = clut
.iter()
.enumerate()
.filter_map(|(idx, entry)| (entry[0] == entry[1] && entry[1] == entry[2]).then_some(idx))
.collect::<Vec<_>>();
let distinct_grays = grayscale_entries
.iter()
.map(|&idx| clut[idx][0])
.collect::<std::collections::BTreeSet<_>>();
let search_indices = if distinct_grays.len() >= 8 {
grayscale_entries
} else {
(0..clut.len()).collect()
};
if clut_match_itable_enabled() {
let _ = clut;
let _ = search_indices;
return crate::trap::TrapDispatcher::standard_itable_lookup(luma, luma, luma);
}
let match_target = luma;
let mut best_idx = 0u8;
let mut best_luma_diff = u64::MAX;
let mut best_chroma = u64::MAX;
for idx in search_indices {
let entry = clut[idx];
let entry_luma =
(u64::from(entry[0]) * 30 + u64::from(entry[1]) * 59 + u64::from(entry[2]) * 11) / 100;
let luma_diff = entry_luma.abs_diff(u64::from(match_target));
let max_component = entry[0].max(entry[1]).max(entry[2]);
let min_component = entry[0].min(entry[1]).min(entry[2]);
let chroma = u64::from(max_component - min_component);
if luma_diff < best_luma_diff || (luma_diff == best_luma_diff && chroma < best_chroma) {
best_idx = idx as u8;
best_luma_diff = luma_diff;
best_chroma = chroma;
}
}
best_idx
}
fn pict_clut_is_dense_grayscale(clut: &[[u16; 3]]) -> bool {
let grayscale_entries = clut
.iter()
.take(192)
.filter(|rgb| rgb[0] == rgb[1] && rgb[1] == rgb[2] && rgb[0] != 0)
.count();
grayscale_entries >= 96
}
fn clut_resembles_canonical_8bpp(clut: &[[u16; 3]]) -> bool {
if clut.len() < 256 {
return false;
}
clut[0] == [0xFFFF, 0xFFFF, 0xFFFF]
&& clut[1] == [0xFFFF, 0xFFFF, 0xCCCC]
&& clut[16] == [0xFFFF, 0x9999, 0x3333]
&& clut[42] == [0xCCCC, 0xCCCC, 0xFFFF]
&& clut[255] == [0x0000, 0x0000, 0x0000]
}
fn should_preserve_source_palette_indices(
src_clut: &[[u16; 3]],
device_clut: &[[u16; 3]; 256],
) -> bool {
if pict_identity_remap_enabled() && src_clut.len() == 256 {
return true;
}
if src_clut.len() == 256 {
src_clut.iter().zip(device_clut.iter()).all(|(s, d)| s == d)
} else {
false
}
}
fn map_src_coord(
src_coord: i32,
src_start: i16,
src_end: i16,
dst_start: i16,
dst_end: i16,
) -> Option<i32> {
let src_span = i32::from(src_end) - i32::from(src_start);
let dst_span = i32::from(dst_end) - i32::from(dst_start);
if src_span <= 0 || dst_span <= 0 {
return None;
}
let rel = src_coord - i32::from(src_start);
if rel < 0 || rel >= src_span {
return None;
}
Some(i32::from(dst_start) + (rel * dst_span) / src_span)
}
fn parse_bits_rect(
bus: &mut MacMemoryBus,
mut pos: u32,
has_rgn: bool,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
screen_mode: (u32, u32, u16, u16, u16),
_device_clut: &[[u16; 3]; 256],
fg_idx: u8,
bg_idx: u8,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
) -> u32 {
let row_bytes = bus.read_word(pos) & 0x3FFF;
pos += 2;
let bounds_top = bus.read_word(pos) as i16;
pos += 2;
let bounds_left = bus.read_word(pos) as i16;
pos += 2;
let bounds_bottom = bus.read_word(pos) as i16;
pos += 2;
let bounds_right = bus.read_word(pos) as i16;
pos += 2;
let src_top = bus.read_word(pos) as i16;
pos += 2;
let src_left = bus.read_word(pos) as i16;
pos += 2;
let src_bottom = bus.read_word(pos) as i16;
pos += 2;
let src_right = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_top = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_left = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_bottom = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_right = bus.read_word(pos) as i16;
pos += 2;
let mode = bus.read_word(pos);
pos += 2;
if trace_pict_enabled() {
eprintln!(
"[PICT] Bits{} mode={} src=({},{}..{},{} ) dst=({},{}..{},{} )",
if has_rgn { "Rgn" } else { "Rect" },
mode,
src_top,
src_left,
src_bottom,
src_right,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
);
}
if has_rgn {
let rgn_size = bus.read_word(pos) as u32;
pos += rgn_size;
}
let width = (bounds_right - bounds_left).max(0) as u32;
let height = (bounds_bottom - bounds_top).max(0) as u32;
let (screen_base, screen_rb, screen_w, screen_h, scrn_ps) = (
screen_mode.0,
screen_mode.1,
screen_mode.2 as i32,
screen_mode.3 as i32,
screen_mode.4,
);
for row in 0..height {
let src_y = i32::from(bounds_top) + row as i32;
let mapped_pic_y = map_src_coord(src_y, src_top, src_bottom, pic_dst_top, pic_dst_bottom);
for col_byte in 0..row_bytes as u32 {
let byte = bus.read_byte(pos);
pos += 1;
for bit in 0..8u32 {
let px = col_byte * 8 + bit;
if px >= width {
continue;
}
let is_set = (byte & (1 << (7 - bit))) != 0;
let color_idx = if is_set {
fg_idx
} else if mode == 0 {
bg_idx
} else {
continue;
};
let Some(pic_y) = mapped_pic_y else {
continue;
};
let src_x = i32::from(bounds_left) + px as i32;
let Some(pic_x) =
map_src_coord(src_x, src_left, src_right, pic_dst_left, pic_dst_right)
else {
continue;
};
if clip_region.is_some_and(|clip| !clip.contains(pic_y, pic_x)) {
continue;
}
let x = ((pic_x - i32::from(frame_left)) as f64 * scale_x) as i32 + dst_left as i32;
let y = ((pic_y - i32::from(frame_top)) as f64 * scale_y) as i32 + dst_top as i32;
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
y,
color_idx,
screen_w,
screen_h,
scrn_ps,
dst_clip,
);
}
}
}
pos
}
fn parse_pack_bits_rect(
bus: &mut MacMemoryBus,
mut pos: u32,
has_rgn: bool,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
screen_mode: (u32, u32, u16, u16, u16),
device_clut: &[[u16; 3]; 256],
device_ct_seed: u32,
fg_idx: u8,
bg_idx: u8,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
) -> (u32, Option<Vec<[u16; 3]>>) {
let row_bytes_raw = bus.read_word(pos); let is_pixmap = (row_bytes_raw & 0x8000) != 0;
let (new_pos, pm, colors16, src_ct_seed) = if is_pixmap {
let (p, pm) = read_pixmap(bus, pos);
let (p2, colors16, ct_seed) = read_color_table(bus, p);
(p2, pm, Some(colors16), ct_seed)
} else {
let row_bytes = bus.read_word(pos) & 0x3FFF;
pos += 2;
let bt = bus.read_word(pos) as i16;
pos += 2;
let bl = bus.read_word(pos) as i16;
pos += 2;
let bb = bus.read_word(pos) as i16;
pos += 2;
let br = bus.read_word(pos) as i16;
pos += 2;
let pm = PixMapInfo {
row_bytes,
bounds_top: bt,
bounds_left: bl,
bounds_bottom: bb,
bounds_right: br,
pixel_size: 1,
cmp_count: 1,
pack_type: 0,
};
(pos, pm, None, 0)
};
pos = new_pos;
if trace_pict_enabled() {
eprintln!(
"[PICT] PackBits{} pixelSize={} cmpCount={} rowBytes={} bounds=({}, {}, {}, {}) dstBase=${:08X} dstRowBytes={}",
if has_rgn { "Rgn" } else { "Rect" },
pm.pixel_size,
pm.cmp_count,
pm.row_bytes,
pm.bounds_top,
pm.bounds_left,
pm.bounds_bottom,
pm.bounds_right,
screen_mode.0,
screen_mode.1,
);
}
let src_top = bus.read_word(pos) as i16;
pos += 2;
let src_left = bus.read_word(pos) as i16;
pos += 2;
let src_bottom = bus.read_word(pos) as i16;
pos += 2;
let src_right = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_top = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_left = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_bottom = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_right = bus.read_word(pos) as i16;
pos += 2;
let mode = bus.read_word(pos);
pos += 2;
let mode_base = mode & 0x003F;
if trace_pict_enabled() {
eprintln!(
"[PICT] PackBits{} mode={} src=({},{}..{},{} ) dst=({},{}..{},{} )",
if has_rgn { "Rgn" } else { "Rect" },
mode,
src_top,
src_left,
src_bottom,
src_right,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
);
}
if has_rgn {
let rgn_size = bus.read_word(pos) as u32;
pos += rgn_size;
}
let height = (pm.bounds_bottom - pm.bounds_top) as u32;
let (screen_base, screen_rb, screen_w, screen_h, scrn_ps) = (
screen_mode.0,
screen_mode.1,
screen_mode.2 as i32,
screen_mode.3 as i32,
screen_mode.4,
);
let src_clut = colors16.as_deref().unwrap_or(&[][..]);
let mono_clut = if scrn_ps == 1 {
Some(one_bit_destination_clut())
} else {
None
};
let dst_clut = mono_clut.as_ref().unwrap_or(device_clut);
let seed_and_table_match = scrn_ps != 1
&& src_ct_seed != 0
&& src_ct_seed == device_ct_seed
&& should_preserve_source_palette_indices(src_clut, dst_clut);
let src_to_dst = if seed_and_table_match {
let mut t = [0u8; 256];
for (i, slot) in t.iter_mut().enumerate() {
*slot = i as u8;
}
t
} else {
build_src_to_dst_table(src_clut, dst_clut)
};
let src_to_dst_is_identity = src_to_dst
.iter()
.enumerate()
.all(|(idx, &value)| value == idx as u8);
let indexed_transfer = build_pict_indexed_transfer_table(
mode_base,
src_clut,
&src_to_dst,
dst_clut,
fg_idx,
bg_idx,
);
let can_direct_blit_8bpp_src_copy = !trace_pict_enabled()
&& pm.pixel_size == 8
&& can_blit_8bpp_src_copy_rows_fast(
mode_base,
&pm,
src_top,
src_left,
src_bottom,
src_right,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
scrn_ps,
clip_region,
);
let mut traced_min_index = u8::MAX;
let mut traced_max_index = 0u8;
let mut traced_have_index = false;
let mut update_trace_index_range = |row_data: &[u8]| {
if !trace_pict_enabled() || pm.pixel_size != 8 {
return;
}
for &pixel in row_data {
traced_min_index = traced_min_index.min(pixel);
traced_max_index = traced_max_index.max(pixel);
traced_have_index = true;
}
};
let mut row_data = Vec::with_capacity(pm.row_bytes as usize);
let mut blit_scratch = Vec::new();
if pm.row_bytes < 8 {
for row in 0..height {
row_data.resize(pm.row_bytes as usize, 0);
bus.read_bytes_into(pos, &mut row_data);
pos += pm.row_bytes as u32;
if can_direct_blit_8bpp_src_copy && row_data.len() >= pm.row_bytes as usize {
let copied = try_blit_row_8bpp_src_copy_fast(
bus,
&row_data,
mode_base,
&pm,
&src_to_dst,
src_to_dst_is_identity,
row,
src_top,
src_left,
src_bottom,
src_right,
dst_top,
dst_left,
frame_top,
frame_left,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
screen_base,
screen_rb,
screen_w,
screen_h,
scrn_ps,
clip_region,
dst_clip,
&mut blit_scratch,
);
debug_assert!(copied);
continue;
}
update_trace_index_range(&row_data);
blit_row(
bus,
&row_data,
mode_base,
&pm,
device_clut,
&src_to_dst,
src_to_dst_is_identity,
&indexed_transfer,
row,
src_top,
src_left,
src_bottom,
src_right,
dst_top,
dst_left,
frame_top,
frame_left,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
screen_base,
screen_rb,
screen_w,
screen_h,
scrn_ps,
fg_idx,
bg_idx,
clip_region,
dst_clip,
&mut blit_scratch,
);
}
} else if let Some(fast_pos) = try_blit_packbits_8bpp_src_copy_fast(
bus,
pos,
height,
&pm,
mode_base,
&src_to_dst,
src_to_dst_is_identity,
src_top,
src_left,
src_bottom,
src_right,
dst_top,
dst_left,
frame_top,
frame_left,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
screen_base,
screen_rb,
screen_w,
screen_h,
scrn_ps,
clip_region,
dst_clip,
) {
pos = fast_pos;
} else {
for row in 0..height {
let map_during_unpack = can_direct_blit_8bpp_src_copy
&& pm.pixel_size == 8
&& !src_to_dst_is_identity
&& scrn_ps == 8;
pos = if map_during_unpack {
unpack_bits_with_byte_count_row_bytes_mapped_into(
bus,
pos,
pm.row_bytes,
pm.row_bytes,
&src_to_dst,
&mut row_data,
)
} else {
unpack_bits_with_byte_count_row_bytes_into(
bus,
pos,
pm.row_bytes,
pm.row_bytes,
&mut row_data,
)
};
if can_direct_blit_8bpp_src_copy && row_data.len() >= pm.row_bytes as usize {
let copied = try_blit_row_8bpp_src_copy_fast(
bus,
&row_data,
mode_base,
&pm,
&src_to_dst,
src_to_dst_is_identity || map_during_unpack,
row,
src_top,
src_left,
src_bottom,
src_right,
dst_top,
dst_left,
frame_top,
frame_left,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
screen_base,
screen_rb,
screen_w,
screen_h,
scrn_ps,
clip_region,
dst_clip,
&mut blit_scratch,
);
debug_assert!(copied);
continue;
}
update_trace_index_range(&row_data);
blit_row(
bus,
&row_data,
mode_base,
&pm,
device_clut,
&src_to_dst,
src_to_dst_is_identity,
&indexed_transfer,
row,
src_top,
src_left,
src_bottom,
src_right,
dst_top,
dst_left,
frame_top,
frame_left,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
screen_base,
screen_rb,
screen_w,
screen_h,
scrn_ps,
fg_idx,
bg_idx,
clip_region,
dst_clip,
&mut blit_scratch,
);
}
}
if trace_pict_enabled() && pm.pixel_size == 8 && traced_have_index {
eprintln!(
"[PICT] PackBits index range {}..={}",
traced_min_index, traced_max_index
);
}
(pos, if pm.pixel_size == 8 { colors16 } else { None })
}
fn blit_row(
bus: &mut MacMemoryBus,
row_data: &[u8],
mode_base: u16,
pm: &PixMapInfo,
device_clut: &[[u16; 3]; 256],
src_to_dst: &[u8; 256],
src_to_dst_is_identity: bool,
indexed_transfer: &[PictIndexedTransfer; 256],
row: u32,
src_top: i16,
src_left: i16,
src_bottom: i16,
src_right: i16,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
pic_dst_top: i16,
pic_dst_left: i16,
pic_dst_bottom: i16,
pic_dst_right: i16,
scale_x: f64,
scale_y: f64,
screen_base: u32,
screen_rb: u32,
screen_w: i32,
screen_h: i32,
scrn_ps: u16,
fg_idx: u8,
bg_idx: u8,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
scratch: &mut Vec<u8>,
) {
let width = (pm.bounds_right - pm.bounds_left).max(0) as u32;
if !trace_pict_enabled()
&& pm.pixel_size == 8
&& try_blit_row_8bpp_src_copy_fast(
bus,
row_data,
mode_base,
pm,
src_to_dst,
src_to_dst_is_identity,
row,
src_top,
src_left,
src_bottom,
src_right,
dst_top,
dst_left,
frame_top,
frame_left,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
screen_base,
screen_rb,
screen_w,
screen_h,
scrn_ps,
clip_region,
dst_clip,
scratch,
)
{
return;
}
let src_y = i32::from(pm.bounds_top) + row as i32;
let Some(pic_y) = map_src_coord(src_y, src_top, src_bottom, pic_dst_top, pic_dst_bottom) else {
return;
};
let base_y = ((pic_y - i32::from(frame_top)) as f64 * scale_y) as i32 + dst_top as i32;
let map_x = |px: u32| {
let src_x = i32::from(pm.bounds_left) + px as i32;
map_src_coord(src_x, src_left, src_right, pic_dst_left, pic_dst_right)
};
match pm.pixel_size {
1 => {
for px in 0..width {
let byte_idx = (px / 8) as usize;
let bit = 7 - (px % 8);
if byte_idx < row_data.len() {
let is_set = (row_data[byte_idx] & (1 << bit)) != 0;
let color_idx = if is_set {
fg_idx
} else if mode_base == 0 {
bg_idx
} else {
continue;
};
let Some(pic_x) = map_x(px) else {
continue;
};
if clip_region.is_some_and(|clip| !clip.contains(pic_y, pic_x)) {
continue;
}
let x =
((pic_x - i32::from(frame_left)) as f64 * scale_x) as i32 + dst_left as i32;
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
base_y,
color_idx,
screen_w,
screen_h,
scrn_ps,
dst_clip,
);
}
}
}
2 => {
for px in 0..width {
let byte_idx = (px / 4) as usize;
let shift = (3 - (px % 4)) * 2;
if byte_idx < row_data.len() {
let ci = ((row_data[byte_idx] >> shift) & 0x03) as usize;
if mode_base == 36 && ci == 0 {
continue;
}
let Some(pic_x) = map_x(px) else {
continue;
};
if clip_region.is_some_and(|clip| !clip.contains(pic_y, pic_x)) {
continue;
}
let x =
((pic_x - i32::from(frame_left)) as f64 * scale_x) as i32 + dst_left as i32;
let Some(pixel) = pict_indexed_transfer_pixel(
bus,
ci,
indexed_transfer,
screen_base,
screen_rb,
x,
base_y,
screen_w,
screen_h,
scrn_ps,
device_clut,
) else {
continue;
};
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
base_y,
pixel,
screen_w,
screen_h,
scrn_ps,
dst_clip,
);
}
}
}
4 => {
for px in 0..width {
let byte_idx = (px / 2) as usize;
let shift = if px % 2 == 0 { 4 } else { 0 };
if byte_idx < row_data.len() {
let ci = ((row_data[byte_idx] >> shift) & 0x0F) as usize;
if mode_base == 36 && ci == 0 {
continue;
}
let Some(pic_x) = map_x(px) else {
continue;
};
if clip_region.is_some_and(|clip| !clip.contains(pic_y, pic_x)) {
continue;
}
let x =
((pic_x - i32::from(frame_left)) as f64 * scale_x) as i32 + dst_left as i32;
let Some(pixel) = pict_indexed_transfer_pixel(
bus,
ci,
indexed_transfer,
screen_base,
screen_rb,
x,
base_y,
screen_w,
screen_h,
scrn_ps,
device_clut,
) else {
continue;
};
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
base_y,
pixel,
screen_w,
screen_h,
scrn_ps,
dst_clip,
);
}
}
}
8 => {
for px in 0..width {
let byte_idx = px as usize;
if byte_idx < row_data.len() {
let src_pixel = row_data[byte_idx] as usize;
if mode_base == 36 && src_pixel == 0 {
continue;
}
let Some(pic_x) = map_x(px) else {
continue;
};
if clip_region.is_some_and(|clip| !clip.contains(pic_y, pic_x)) {
continue;
}
let x =
((pic_x - i32::from(frame_left)) as f64 * scale_x) as i32 + dst_left as i32;
let Some(pixel) = pict_indexed_transfer_pixel(
bus,
src_pixel,
indexed_transfer,
screen_base,
screen_rb,
x,
base_y,
screen_w,
screen_h,
scrn_ps,
device_clut,
) else {
continue;
};
if trace_pict_samples_enabled() {
for (label, sample_x, sample_y) in [
("center", 400i32, 300i32),
("title_right", 580, 350),
("title_low", 400, 430),
] {
if x == sample_x && base_y == sample_y {
let src_rgb = device_clut[pixel as usize];
eprintln!(
"[PICT] sample {} dst=({}, {}) src_row={} src_px={} src_idx={} dst_idx={} dst_rgb=({:04X},{:04X},{:04X})",
label,
x,
base_y,
row,
px,
src_pixel,
pixel,
src_rgb[0],
src_rgb[1],
src_rgb[2],
);
}
}
}
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
base_y,
pixel,
screen_w,
screen_h,
scrn_ps,
dst_clip,
);
}
}
}
16 => {
for px in 0..width {
let byte_idx = (px * 2) as usize;
if byte_idx + 1 < row_data.len() {
let hi = row_data[byte_idx] as u16;
let lo = row_data[byte_idx + 1] as u16;
let pixel = (hi << 8) | lo;
let r = (((pixel >> 10) & 0x1F) * 255 / 31) as u8;
let g = (((pixel >> 5) & 0x1F) * 255 / 31) as u8;
let b = ((pixel & 0x1F) * 255 / 31) as u8;
let idx = closest_clut_index(
r as u16 * 257,
g as u16 * 257,
b as u16 * 257,
device_clut,
);
let Some(pic_x) = map_x(px) else {
continue;
};
if clip_region.is_some_and(|clip| !clip.contains(pic_y, pic_x)) {
continue;
}
let x =
((pic_x - i32::from(frame_left)) as f64 * scale_x) as i32 + dst_left as i32;
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
base_y,
idx,
screen_w,
screen_h,
scrn_ps,
dst_clip,
);
}
}
}
_ => {
}
}
}
fn intersect_spans_with_range(spans: &mut Vec<(i32, i32)>, left: i32, right: i32) {
spans.retain_mut(|(span_left, span_right)| {
*span_left = (*span_left).max(left);
*span_right = (*span_right).min(right);
*span_right > *span_left
});
}
fn intersect_spans_with_region_row(spans: &mut Vec<(i32, i32)>, region: &DstClipRegion, y: i32) {
if y < region.top || y >= region.bottom {
spans.clear();
return;
}
let Some(rows) = region.rows.as_ref() else {
intersect_spans_with_range(spans, region.left, region.right);
return;
};
let Some(edges) = rows.get((y - region.top) as usize) else {
spans.clear();
return;
};
let mut row_spans = Vec::new();
for pair in edges.chunks(2) {
if pair.len() != 2 {
break;
}
let left = pair[0].max(region.left);
let right = pair[1].min(region.right);
if right > left {
row_spans.push((left, right));
}
}
if row_spans.is_empty() {
spans.clear();
return;
}
let mut clipped = Vec::new();
for &(span_left, span_right) in spans.iter() {
for &(row_left, row_right) in &row_spans {
let left = span_left.max(row_left);
let right = span_right.min(row_right);
if right > left {
clipped.push((left, right));
}
}
}
*spans = clipped;
}
fn dst_clip_row_spans(
dst_clip: Option<&DstClip>,
y: i32,
left: i32,
right: i32,
) -> Vec<(i32, i32)> {
if right <= left {
return Vec::new();
}
let mut spans = vec![(left, right)];
let Some(dst_clip) = dst_clip else {
return spans;
};
let (clip_top, clip_left, clip_bottom, clip_right) = dst_clip.rect();
if y < clip_top || y >= clip_bottom {
return Vec::new();
}
intersect_spans_with_range(&mut spans, clip_left, clip_right);
for region in &dst_clip.regions {
intersect_spans_with_region_row(&mut spans, region, y);
if spans.is_empty() {
break;
}
}
spans
}
enum RowClipSpan {
Empty,
Single(i32, i32),
Complex,
}
fn dst_clip_simple_row_span(
dst_clip: Option<&DstClip>,
y: i32,
mut left: i32,
mut right: i32,
) -> RowClipSpan {
if right <= left {
return RowClipSpan::Empty;
}
let Some(dst_clip) = dst_clip else {
return RowClipSpan::Single(left, right);
};
let (clip_top, clip_left, clip_bottom, clip_right) = dst_clip.rect();
if y < clip_top || y >= clip_bottom {
return RowClipSpan::Empty;
}
left = left.max(clip_left);
right = right.min(clip_right);
if right <= left {
return RowClipSpan::Empty;
}
for region in &dst_clip.regions {
if region.rows.is_some() {
return RowClipSpan::Complex;
}
if y < region.top || y >= region.bottom {
return RowClipSpan::Empty;
}
left = left.max(region.left);
right = right.min(region.right);
if right <= left {
return RowClipSpan::Empty;
}
}
RowClipSpan::Single(left, right)
}
#[allow(clippy::too_many_arguments)]
fn can_blit_8bpp_src_copy_rows_fast(
mode_base: u16,
pm: &PixMapInfo,
src_top: i16,
src_left: i16,
src_bottom: i16,
src_right: i16,
pic_dst_top: i16,
pic_dst_left: i16,
pic_dst_bottom: i16,
pic_dst_right: i16,
scale_x: f64,
scale_y: f64,
scrn_ps: u16,
clip_region: Option<&PictureRegion>,
) -> bool {
if mode_base != 0
|| (scrn_ps != 8 && scrn_ps != 1)
|| pm.cmp_count != 1
|| clip_region.is_some()
|| scale_x != 1.0
|| scale_y != 1.0
{
return false;
}
let src_span_x = i32::from(src_right) - i32::from(src_left);
let dst_span_x = i32::from(pic_dst_right) - i32::from(pic_dst_left);
let src_span_y = i32::from(src_bottom) - i32::from(src_top);
let dst_span_y = i32::from(pic_dst_bottom) - i32::from(pic_dst_top);
src_span_x > 0
&& src_span_y > 0
&& src_span_x == dst_span_x
&& src_span_y == dst_span_y
&& pm.bounds_right > pm.bounds_left
&& pm.bounds_bottom > pm.bounds_top
}
#[allow(clippy::too_many_arguments)]
fn try_blit_row_8bpp_src_copy_fast(
bus: &mut MacMemoryBus,
row_data: &[u8],
mode_base: u16,
pm: &PixMapInfo,
src_to_dst: &[u8; 256],
src_to_dst_is_identity: bool,
row: u32,
src_top: i16,
src_left: i16,
src_bottom: i16,
src_right: i16,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
pic_dst_top: i16,
pic_dst_left: i16,
pic_dst_bottom: i16,
pic_dst_right: i16,
scale_x: f64,
scale_y: f64,
screen_base: u32,
screen_rb: u32,
screen_w: i32,
screen_h: i32,
scrn_ps: u16,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
scratch: &mut Vec<u8>,
) -> bool {
if !can_blit_8bpp_src_copy_rows_fast(
mode_base,
pm,
src_top,
src_left,
src_bottom,
src_right,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
scrn_ps,
clip_region,
) {
return false;
}
let src_span_x = i32::from(src_right) - i32::from(src_left);
let dst_span_x = i32::from(pic_dst_right) - i32::from(pic_dst_left);
let src_span_y = i32::from(src_bottom) - i32::from(src_top);
let dst_span_y = i32::from(pic_dst_bottom) - i32::from(pic_dst_top);
if src_span_x <= 0 || src_span_y <= 0 || src_span_x != dst_span_x || src_span_y != dst_span_y {
return false;
}
let source_left = i32::from(pm.bounds_left).max(i32::from(src_left));
let source_right = i32::from(pm.bounds_right).min(i32::from(src_right));
let mut run_len = source_right - source_left;
if run_len <= 0 {
return true;
}
let src_y = i32::from(pm.bounds_top) + row as i32;
if src_y < i32::from(src_top) || src_y >= i32::from(src_bottom) {
return true;
}
let pic_y = i32::from(pic_dst_top) + src_y - i32::from(src_top);
let y = i32::from(dst_top) + pic_y - i32::from(frame_top);
if y < 0 || y >= screen_h {
return true;
}
let mut src_offset = source_left - i32::from(pm.bounds_left);
let mut dst_x = i32::from(dst_left) + source_left - i32::from(src_left)
+ i32::from(pic_dst_left)
- i32::from(frame_left);
if dst_x < 0 {
src_offset -= dst_x;
run_len += dst_x;
dst_x = 0;
}
if dst_x + run_len > screen_w {
run_len = screen_w - dst_x;
}
if run_len <= 0 {
return true;
}
if scrn_ps == 1 {
let write_span_1bpp = |bus: &mut MacMemoryBus, span_left: i32, span_right: i32| {
let span_len = span_right - span_left;
let src_start = (src_offset + span_left - dst_x) as usize;
let src_end = src_start + span_len as usize;
if src_end > row_data.len() {
return false;
}
let mut x = span_left;
while x < span_right {
let byte_left = x & !7;
let byte_right = byte_left + 8;
let write_left = x.max(byte_left);
let write_right = span_right.min(byte_right);
let addr = screen_base + (y as u32) * screen_rb + (byte_left as u32 / 8);
let mut byte = if write_left == byte_left && write_right == byte_right {
0
} else {
bus.read_byte(addr)
};
for px in write_left..write_right {
let source_index = src_start + (px - span_left) as usize;
let bit = 7 - (px & 7);
let pixel = if src_to_dst_is_identity {
row_data[source_index]
} else {
src_to_dst[row_data[source_index] as usize]
};
if pixel != 0 {
byte |= 1 << bit;
} else {
byte &= !(1 << bit);
}
}
bus.write_byte(addr, byte);
x = write_right;
}
true
};
match dst_clip_simple_row_span(dst_clip, y, dst_x, dst_x + run_len) {
RowClipSpan::Empty => return true,
RowClipSpan::Single(span_left, span_right) => {
return write_span_1bpp(bus, span_left, span_right);
}
RowClipSpan::Complex => {}
}
for (span_left, span_right) in dst_clip_row_spans(dst_clip, y, dst_x, dst_x + run_len) {
if !write_span_1bpp(bus, span_left, span_right) {
return false;
}
}
return true;
}
let mut write_span = |bus: &mut MacMemoryBus, span_left: i32, span_right: i32| {
let span_len = span_right - span_left;
let src_start = (src_offset + span_left - dst_x) as usize;
let src_end = src_start + span_len as usize;
if src_end > row_data.len() {
return false;
}
let row_slice = &row_data[src_start..src_end];
let dst_addr = screen_base + (y as u32) * screen_rb + span_left as u32;
if src_to_dst_is_identity {
bus.write_bytes(dst_addr, row_slice);
} else {
scratch.resize(row_slice.len(), 0);
for (dst, &pixel) in scratch.iter_mut().zip(row_slice.iter()) {
*dst = src_to_dst[pixel as usize];
}
bus.write_bytes(dst_addr, scratch);
}
true
};
match dst_clip_simple_row_span(dst_clip, y, dst_x, dst_x + run_len) {
RowClipSpan::Empty => return true,
RowClipSpan::Single(span_left, span_right) => {
return write_span(bus, span_left, span_right)
}
RowClipSpan::Complex => {}
}
for (span_left, span_right) in dst_clip_row_spans(dst_clip, y, dst_x, dst_x + run_len) {
if !write_span(bus, span_left, span_right) {
return false;
}
}
true
}
fn packbits_row_data_bounds(
bus: &MacMemoryBus,
pos: u32,
byte_count_row_bytes: u16,
) -> Option<(u32, u32, u32)> {
let (data_start, byte_count) = if byte_count_row_bytes > 250 {
(pos.checked_add(2)?, u32::from(bus.read_word(pos)))
} else {
(pos.checked_add(1)?, u32::from(bus.read_byte(pos)))
};
let data_end = data_start.checked_add(byte_count)?;
(data_end <= bus.ram_size()).then_some((data_start, data_end, data_end))
}
#[allow(clippy::too_many_arguments)]
fn try_blit_packbits_8bpp_src_copy_fast(
bus: &mut MacMemoryBus,
mut pos: u32,
height: u32,
pm: &PixMapInfo,
mode_base: u16,
src_to_dst: &[u8; 256],
src_to_dst_is_identity: bool,
src_top: i16,
src_left: i16,
src_bottom: i16,
src_right: i16,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
pic_dst_top: i16,
pic_dst_left: i16,
pic_dst_bottom: i16,
pic_dst_right: i16,
scale_x: f64,
scale_y: f64,
screen_base: u32,
screen_rb: u32,
screen_w: i32,
screen_h: i32,
scrn_ps: u16,
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
) -> Option<u32> {
if pm.pixel_size != 8
|| pm.row_bytes < 8
|| scrn_ps != 8
|| !can_blit_8bpp_src_copy_rows_fast(
mode_base,
pm,
src_top,
src_left,
src_bottom,
src_right,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
scale_x,
scale_y,
scrn_ps,
clip_region,
)
{
return None;
}
if dst_clip
.map(|clip| clip.regions.iter().any(|region| region.rows.is_some()))
.unwrap_or(false)
{
return None;
}
let mut scan_pos = pos;
for _ in 0..height {
let (_, _, next_pos) = packbits_row_data_bounds(bus, scan_pos, pm.row_bytes)?;
scan_pos = next_pos;
}
let source_left = i32::from(pm.bounds_left).max(i32::from(src_left));
let source_right = i32::from(pm.bounds_right).min(i32::from(src_right));
let source_run_len = source_right - source_left;
if source_run_len <= 0 {
return Some(scan_pos);
}
for row in 0..height {
let (row_start, row_end, next_pos) = packbits_row_data_bounds(bus, pos, pm.row_bytes)?;
pos = next_pos;
let src_y = i32::from(pm.bounds_top) + row as i32;
if src_y < i32::from(src_top) || src_y >= i32::from(src_bottom) {
continue;
}
let pic_y = i32::from(pic_dst_top) + src_y - i32::from(src_top);
let y = i32::from(dst_top) + pic_y - i32::from(frame_top);
if y < 0 || y >= screen_h {
continue;
}
let mut src_offset = source_left - i32::from(pm.bounds_left);
let mut run_len = source_run_len;
let mut dst_x = i32::from(dst_left) + source_left - i32::from(src_left)
+ i32::from(pic_dst_left)
- i32::from(frame_left);
if dst_x < 0 {
src_offset -= dst_x;
run_len += dst_x;
dst_x = 0;
}
if dst_x + run_len > screen_w {
run_len = screen_w - dst_x;
}
if run_len <= 0 {
continue;
}
let (span_left, span_right) =
match dst_clip_simple_row_span(dst_clip, y, dst_x, dst_x + run_len) {
RowClipSpan::Empty => continue,
RowClipSpan::Single(left, right) => (left, right),
RowClipSpan::Complex => return None,
};
let span_len = span_right - span_left;
if span_len <= 0 {
continue;
}
let src_start = (src_offset + span_left - dst_x) as usize;
let src_end = src_start + span_len as usize;
if src_end > pm.row_bytes as usize {
return None;
}
let dst_addr = screen_base + (y as u32) * screen_rb + span_left as u32;
if !write_packbits_span_8bpp(
bus,
row_start,
row_end,
pm.row_bytes as usize,
src_start,
src_end,
dst_addr,
src_to_dst,
src_to_dst_is_identity,
) {
return None;
}
}
Some(pos)
}
#[allow(clippy::too_many_arguments)]
fn write_packbits_span_8bpp(
bus: &mut MacMemoryBus,
mut pos: u32,
end_pos: u32,
decoded_row_bytes: usize,
src_start: usize,
src_end: usize,
dst_addr: u32,
src_to_dst: &[u8; 256],
src_to_dst_is_identity: bool,
) -> bool {
let mut out_pos = 0usize;
while pos < end_pos && out_pos < decoded_row_bytes {
let flag = bus.read_byte(pos) as i8;
pos += 1;
if flag >= 0 {
let count = (flag as usize) + 1;
let literal_start = pos;
let available = (end_pos - pos) as usize;
let actual_count = count.min(available);
let run_start = out_pos;
let run_end = out_pos.saturating_add(actual_count).min(decoded_row_bytes);
if run_end > src_start && run_start < src_end {
let copy_start = run_start.max(src_start);
let copy_end = run_end.min(src_end);
let copy_len = copy_end - copy_start;
let input_addr = literal_start + (copy_start - run_start) as u32;
let write_addr = dst_addr + (copy_start - src_start) as u32;
let copied = if src_to_dst_is_identity {
bus.copy_ram_bytes(input_addr, write_addr, copy_len as u32)
} else {
bus.copy_mapped_ram_bytes(input_addr, write_addr, copy_len as u32, src_to_dst)
};
if !copied {
return false;
}
}
out_pos = out_pos.saturating_add(actual_count);
pos += actual_count as u32;
} else if flag != -128 {
if pos >= end_pos {
return false;
}
let count = (-(flag as i16)) as usize + 1;
let pixel = if src_to_dst_is_identity {
bus.read_byte(pos)
} else {
src_to_dst[bus.read_byte(pos) as usize]
};
pos += 1;
let run_start = out_pos;
let run_end = out_pos.saturating_add(count).min(decoded_row_bytes);
if run_end > src_start && run_start < src_end {
let copy_start = run_start.max(src_start);
let copy_end = run_end.min(src_end);
let copy_len = copy_end - copy_start;
let write_addr = dst_addr + (copy_start - src_start) as u32;
bus.fill_bytes(write_addr, copy_len as u32, pixel);
}
out_pos = out_pos.saturating_add(count);
}
}
out_pos >= src_end
}
fn read_screen_pixel_index(
bus: &MacMemoryBus,
screen_base: u32,
screen_rb: u32,
x: i32,
y: i32,
screen_w: i32,
screen_h: i32,
) -> Option<u8> {
if x < 0 || y < 0 || x >= screen_w || y >= screen_h {
return None;
}
Some(bus.read_byte(screen_base + (y as u32) * screen_rb + x as u32))
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum PictIndexedTransfer {
Write(u8),
Skip,
InvertDestination(u8),
}
fn build_pict_indexed_transfer_table(
mode_base: u16,
src_clut: &[[u16; 3]],
src_to_dst: &[u8; 256],
device_clut: &[[u16; 3]; 256],
fg_idx: u8,
bg_idx: u8,
) -> [PictIndexedTransfer; 256] {
if mode_base == 0 {
return std::array::from_fn(|source_index| {
PictIndexedTransfer::Write(src_to_dst[source_index])
});
}
std::array::from_fn(|source_index| {
let translated_pixel = src_to_dst[source_index];
pict_indexed_source_mode_transfer(
mode_base,
source_index,
translated_pixel,
fg_idx,
bg_idx,
src_clut,
device_clut,
)
})
}
fn pict_indexed_transfer_pixel(
bus: &MacMemoryBus,
source_index: usize,
transfer_table: &[PictIndexedTransfer; 256],
screen_base: u32,
screen_rb: u32,
x: i32,
y: i32,
screen_w: i32,
screen_h: i32,
scrn_ps: u16,
device_clut: &[[u16; 3]; 256],
) -> Option<u8> {
let transfer = transfer_table[source_index];
match transfer {
PictIndexedTransfer::Write(pixel) => Some(pixel),
PictIndexedTransfer::Skip => None,
PictIndexedTransfer::InvertDestination(fallback) => {
if scrn_ps != 8 {
return Some(fallback);
}
let dst_pixel =
read_screen_pixel_index(bus, screen_base, screen_rb, x, y, screen_w, screen_h)?;
Some(pict_inverted_clut_index(device_clut, dst_pixel))
}
}
}
fn pict_source_rgb(
source_index: usize,
translated_pixel: u8,
src_clut: &[[u16; 3]],
device_clut: &[[u16; 3]; 256],
) -> [u16; 3] {
src_clut
.get(source_index)
.copied()
.unwrap_or(device_clut[translated_pixel as usize])
}
fn pict_colorize_src_copy_rgb(src_rgb: [u16; 3], fg_rgb: [u16; 3], bg_rgb: [u16; 3]) -> [u16; 3] {
let mut out = [0u16; 3];
for component in 0..3 {
let src = u32::from(src_rgb[component]);
let fg = u32::from(fg_rgb[component]);
let bg = u32::from(bg_rgb[component]);
out[component] = ((((0xFFFF - src) * fg) + (src * bg) + 0x7FFF) / 0xFFFF) as u16;
}
out
}
fn pict_colorize_src_or_rgb(src_rgb: [u16; 3], fg_rgb: [u16; 3]) -> [u16; 3] {
let mut out = [0u16; 3];
for component in 0..3 {
let src = u32::from(src_rgb[component]);
let fg = u32::from(fg_rgb[component]);
out[component] = (((0xFFFF - src) * fg + 0x7FFF) / 0xFFFF) as u16;
}
out
}
fn pict_colorize_not_src_or_rgb(src_rgb: [u16; 3], fg_rgb: [u16; 3]) -> [u16; 3] {
let mut out = [0u16; 3];
for component in 0..3 {
let src = u32::from(src_rgb[component]);
let fg = u32::from(fg_rgb[component]);
out[component] = ((src * fg + 0x7FFF) / 0xFFFF) as u16;
}
out
}
fn pict_inverted_clut_index(clut: &[[u16; 3]; 256], pixel: u8) -> u8 {
let rgb = clut[pixel as usize];
closest_clut_index(0xFFFF - rgb[0], 0xFFFF - rgb[1], 0xFFFF - rgb[2], clut)
}
fn pict_indexed_source_mode_transfer(
mode_base: u16,
source_index: usize,
translated_pixel: u8,
fg_idx: u8,
bg_idx: u8,
src_clut: &[[u16; 3]],
device_clut: &[[u16; 3]; 256],
) -> PictIndexedTransfer {
let src_rgb = pict_source_rgb(source_index, translated_pixel, src_clut, device_clut);
let is_black = src_rgb == [0, 0, 0];
let is_white = src_rgb == [0xFFFF, 0xFFFF, 0xFFFF];
let fg_rgb = device_clut[fg_idx as usize];
let bg_rgb = device_clut[bg_idx as usize];
let map_rgb = |rgb: [u16; 3]| closest_clut_index(rgb[0], rgb[1], rgb[2], device_clut);
match mode_base {
0 => PictIndexedTransfer::Write(translated_pixel),
1 => {
if is_white {
PictIndexedTransfer::Skip
} else if is_black {
PictIndexedTransfer::Write(fg_idx)
} else {
PictIndexedTransfer::Write(map_rgb(pict_colorize_src_or_rgb(src_rgb, fg_rgb)))
}
}
2 => {
if is_black {
PictIndexedTransfer::InvertDestination(translated_pixel)
} else {
PictIndexedTransfer::Skip
}
}
3 => {
if is_white {
PictIndexedTransfer::Skip
} else if is_black {
PictIndexedTransfer::Write(bg_idx)
} else {
PictIndexedTransfer::Write(map_rgb(pict_colorize_src_or_rgb(src_rgb, bg_rgb)))
}
}
4 => PictIndexedTransfer::Write(if is_black {
bg_idx
} else if is_white {
fg_idx
} else {
map_rgb(pict_colorize_src_copy_rgb(src_rgb, bg_rgb, fg_rgb))
}),
5 => {
if is_black {
PictIndexedTransfer::Skip
} else if is_white {
PictIndexedTransfer::Write(fg_idx)
} else {
PictIndexedTransfer::Write(map_rgb(pict_colorize_not_src_or_rgb(src_rgb, fg_rgb)))
}
}
6 => {
if is_white {
PictIndexedTransfer::InvertDestination(translated_pixel)
} else {
PictIndexedTransfer::Skip
}
}
7 => {
if is_black {
PictIndexedTransfer::Skip
} else if is_white {
PictIndexedTransfer::Write(bg_idx)
} else {
PictIndexedTransfer::Write(map_rgb(pict_colorize_not_src_or_rgb(src_rgb, bg_rgb)))
}
}
36 => {
if source_index == 0 {
PictIndexedTransfer::Skip
} else {
PictIndexedTransfer::Write(translated_pixel)
}
}
_ => PictIndexedTransfer::Write(translated_pixel),
}
}
fn parse_direct_bits_rect(
bus: &mut MacMemoryBus,
mut pos: u32,
has_rgn: bool,
dst_top: i16,
dst_left: i16,
frame_top: i16,
frame_left: i16,
scale_x: f64,
scale_y: f64,
screen_mode: (u32, u32, u16, u16, u16),
device_clut: &[[u16; 3]; 256],
clip_region: Option<&PictureRegion>,
dst_clip: Option<&DstClip>,
) -> u32 {
let (new_pos, pm) = read_pixmap_with_base(bus, pos);
pos = new_pos;
if trace_pict_enabled() {
eprintln!(
"[PICT] DirectBits{} pixelSize={} cmpCount={} packType={} rowBytes={} bounds=({}, {}, {}, {})",
if has_rgn { "Rgn" } else { "Rect" },
pm.pixel_size,
pm.cmp_count,
pm.pack_type,
pm.row_bytes,
pm.bounds_top,
pm.bounds_left,
pm.bounds_bottom,
pm.bounds_right
);
}
let src_top = bus.read_word(pos) as i16;
pos += 2;
let src_left = bus.read_word(pos) as i16;
pos += 2;
let src_bottom = bus.read_word(pos) as i16;
pos += 2;
let src_right = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_top = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_left = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_bottom = bus.read_word(pos) as i16;
pos += 2;
let pic_dst_right = bus.read_word(pos) as i16;
pos += 2;
let mode = bus.read_word(pos);
pos += 2;
if trace_pict_enabled() {
eprintln!(
"[PICT] DirectBits{} mode={} src=({},{}..{},{} ) dst=({},{}..{},{} )",
if has_rgn { "Rgn" } else { "Rect" },
mode,
src_top,
src_left,
src_bottom,
src_right,
pic_dst_top,
pic_dst_left,
pic_dst_bottom,
pic_dst_right,
);
}
if has_rgn {
let rgn_size = bus.read_word(pos) as u32;
pos += rgn_size;
}
let height = (pm.bounds_bottom - pm.bounds_top).max(0) as u32;
let width = (pm.bounds_right - pm.bounds_left).max(0) as u32;
let (screen_base, screen_rb, screen_w, screen_h, scrn_ps) = (
screen_mode.0,
screen_mode.1,
screen_mode.2 as i32,
screen_mode.3 as i32,
screen_mode.4,
);
for row in 0..height {
let unpacked_len = match (pm.pixel_size, pm.pack_type) {
(32, 4) => (pm.bounds_right - pm.bounds_left) as u16 * pm.cmp_count,
_ => pm.row_bytes,
};
let (new_pos, row_data) = if pm.row_bytes < 8 || pm.pack_type == 0 || pm.pack_type == 1 {
let data: Vec<u8> = (0..pm.row_bytes as u32)
.map(|i| bus.read_byte(pos + i))
.collect();
(pos + pm.row_bytes as u32, data)
} else if pm.pack_type == 3 && pm.pixel_size == 16 {
unpack_bits_chunk16(bus, pos, unpacked_len)
} else {
unpack_bits_with_byte_count_row_bytes(bus, pos, unpacked_len, pm.row_bytes)
};
pos = new_pos;
let src_y = i32::from(pm.bounds_top) + row as i32;
let mapped_pic_y = map_src_coord(src_y, src_top, src_bottom, pic_dst_top, pic_dst_bottom);
match pm.pixel_size {
16 => {
for px in 0..width {
let byte_idx = (px * 2) as usize;
if byte_idx + 1 < row_data.len() {
let pixel =
((row_data[byte_idx] as u16) << 8) | (row_data[byte_idx + 1] as u16);
let r = (((pixel >> 10) & 0x1F) * 255 / 31) as u8;
let g = (((pixel >> 5) & 0x1F) * 255 / 31) as u8;
let b = ((pixel & 0x1F) * 255 / 31) as u8;
let idx = closest_clut_index(
r as u16 * 257,
g as u16 * 257,
b as u16 * 257,
device_clut,
);
let Some(pic_y) = mapped_pic_y else {
continue;
};
let src_x = i32::from(pm.bounds_left) + px as i32;
let Some(pic_x) =
map_src_coord(src_x, src_left, src_right, pic_dst_left, pic_dst_right)
else {
continue;
};
if clip_region.is_some_and(|clip| !clip.contains(pic_y, pic_x)) {
continue;
}
let x = ((pic_x - i32::from(frame_left)) as f64 * scale_x) as i32
+ dst_left as i32;
let y = ((pic_y - i32::from(frame_top)) as f64 * scale_y) as i32
+ dst_top as i32;
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
y,
idx,
screen_w,
screen_h,
scrn_ps,
dst_clip,
);
}
}
}
32 => {
let skip = if pm.cmp_count == 4 { width as usize } else { 0 };
let r_start = skip;
let g_start = skip + width as usize;
let b_start = skip + 2 * width as usize;
for px in 0..width {
let ri = r_start + px as usize;
let gi = g_start + px as usize;
let bi = b_start + px as usize;
if bi < row_data.len() {
let ci = closest_clut_index(
row_data[ri] as u16 * 257,
row_data[gi] as u16 * 257,
row_data[bi] as u16 * 257,
device_clut,
);
let Some(pic_y) = mapped_pic_y else {
continue;
};
let src_x = i32::from(pm.bounds_left) + px as i32;
let Some(pic_x) =
map_src_coord(src_x, src_left, src_right, pic_dst_left, pic_dst_right)
else {
continue;
};
if clip_region.is_some_and(|clip| !clip.contains(pic_y, pic_x)) {
continue;
}
let x = ((pic_x - i32::from(frame_left)) as f64 * scale_x) as i32
+ dst_left as i32;
let y = ((pic_y - i32::from(frame_top)) as f64 * scale_y) as i32
+ dst_top as i32;
write_pixel_clipped(
bus,
screen_base,
screen_rb,
x,
y,
ci,
screen_w,
screen_h,
scrn_ps,
dst_clip,
);
}
}
}
_ => {}
}
}
pos
}
#[cfg(test)]
mod tests {
use super::{
build_pict_indexed_transfer_table, build_src_to_dst_table,
clear_src_to_dst_table_cache_for_tests, closest_grayscale_luminance_index, draw_picture,
dst_clip_row_spans, peek_initial_packbits_clut, try_blit_packbits_8bpp_src_copy_fast,
try_blit_row_8bpp_src_copy_fast, DstClip, DstClipRegion, PictIndexedTransfer, PixMapInfo,
};
use crate::memory::{MacMemoryBus, MemoryBus};
use crate::trap::dispatch::TrapDispatcher;
#[test]
fn grayscale_luminance_mapping_prefers_low_chroma_match() {
let mut dst = [[0u16; 3]; 256];
dst[10] = [0xE000, 0x0000, 0x0000];
dst[20] = [0x3939, 0x2C2C, 0x3939];
assert_eq!(closest_grayscale_luminance_index(0x3939, &dst), 20);
}
#[test]
fn dense_grayscale_source_uses_luminance_translation() {
let mut src = [[0u16; 3]; 256];
for (index, rgb) in src.iter_mut().enumerate() {
let value = 0xFFFFu16.saturating_sub((index as u16) * 0x0101);
*rgb = [value, value, value];
}
let mut dst = [[0u16; 3]; 256];
dst[10] = [0xE000, 0x0000, 0x0000];
dst[20] = [0x3939, 0x2C2C, 0x3939];
dst[42] = [0x1111, 0x0202, 0x0000];
let table = build_src_to_dst_table(&src, &dst);
assert_eq!(table[198], 20);
}
#[test]
fn dense_grayscale_source_does_not_preserve_indices_on_system_palette() {
let mut src = [[0u16; 3]; 256];
for (index, rgb) in src.iter_mut().enumerate() {
let value = 0xFFFFu16.saturating_sub((index as u16) * 0x0101);
*rgb = [value, value, value];
}
let dst = TrapDispatcher::standard_mac_8bpp_clut();
let table = build_src_to_dst_table(&src, &dst);
assert_ne!(table[16], 16);
let mapped = dst[table[16] as usize];
assert_eq!(mapped[0], mapped[1]);
assert_eq!(mapped[1], mapped[2]);
}
#[test]
fn exact_same_index_palette_entries_win_over_earlier_duplicates() {
let mut src = [[0u16; 3]; 256];
let mut dst = [[0u16; 3]; 256];
let rgb = [0x2E2E, 0x0000, 0x3333];
src[71] = rgb;
dst[12] = rgb;
dst[71] = rgb;
let table = build_src_to_dst_table(&src, &dst);
assert_eq!(table[71], 71);
}
#[test]
fn src_to_dst_table_cache_reuses_identical_cluts() {
clear_src_to_dst_table_cache_for_tests();
let mut src = [[0u16; 3]; 256];
let mut dst = [[0u16; 3]; 256];
src[1] = [0x2222, 0x3333, 0x4444];
dst[7] = [0x2222, 0x3333, 0x4444];
let first = build_src_to_dst_table(&src, &dst);
let second = build_src_to_dst_table(&src, &dst);
assert_eq!(second, first);
}
#[test]
fn src_to_dst_table_cache_keys_on_destination_clut_contents() {
clear_src_to_dst_table_cache_for_tests();
let mut src = [[0u16; 3]; 256];
src[1] = [0x2222, 0x3333, 0x4444];
let mut first_dst = [[0u16; 3]; 256];
first_dst[7] = [0x2222, 0x3333, 0x4444];
let mut second_dst = first_dst;
second_dst[9] = [0x2222, 0x3333, 0x4444];
second_dst[7] = [0x1111, 0x1111, 0x1111];
let first = build_src_to_dst_table(&src, &first_dst);
let second = build_src_to_dst_table(&src, &second_dst);
assert_ne!(first[1], second[1]);
}
#[test]
fn complex_dst_clip_row_spans_follow_region_edges() {
let clip = DstClip::new(
(0, 0, 5, 20),
vec![DstClipRegion::complex(
1,
0,
4,
20,
vec![vec![3, 8, 12, 15], vec![5, 10], vec![]],
)],
);
assert_eq!(
dst_clip_row_spans(Some(&clip), 1, 0, 20),
vec![(3, 8), (12, 15)]
);
assert_eq!(dst_clip_row_spans(Some(&clip), 2, 0, 20), vec![(5, 10)]);
assert!(dst_clip_row_spans(Some(&clip), 3, 0, 20).is_empty());
}
#[test]
fn eight_bit_src_copy_fast_path_respects_complex_dst_clip() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
bus.write_bytes(screen_base, &[0xEE; 8]);
let pm = PixMapInfo {
row_bytes: 8,
bounds_top: 0,
bounds_left: 0,
bounds_bottom: 1,
bounds_right: 8,
pixel_size: 8,
cmp_count: 1,
pack_type: 0,
};
let mut src_to_dst = [0u8; 256];
for (index, slot) in src_to_dst.iter_mut().enumerate() {
*slot = 100u8.saturating_add(index as u8);
}
let clip = DstClip::new(
(0, 0, 1, 8),
vec![DstClipRegion::complex(0, 0, 1, 8, vec![vec![2, 5]])],
);
let mut scratch = Vec::new();
assert!(try_blit_row_8bpp_src_copy_fast(
&mut bus,
&[1, 2, 3, 4, 5, 6, 7, 8],
0,
&pm,
&src_to_dst,
false,
0,
0,
0,
1,
8,
0,
0,
0,
0,
0,
0,
1,
8,
1.0,
1.0,
screen_base,
8,
8,
1,
8,
None,
Some(&clip),
&mut scratch,
));
assert_eq!(
bus.read_bytes(screen_base, 8),
vec![0xEE, 0xEE, 103, 104, 105, 0xEE, 0xEE, 0xEE]
);
}
#[test]
fn eight_bit_src_copy_fast_path_packs_one_bit_destination_rows() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
bus.write_byte(screen_base, 0xFF);
let pm = PixMapInfo {
row_bytes: 8,
bounds_top: 0,
bounds_left: 0,
bounds_bottom: 1,
bounds_right: 8,
pixel_size: 8,
cmp_count: 1,
pack_type: 0,
};
let mut src_to_dst = [0u8; 256];
for (index, slot) in src_to_dst.iter_mut().enumerate() {
*slot = if index % 2 == 0 { 0 } else { 255 };
}
let mut scratch = Vec::new();
assert!(try_blit_row_8bpp_src_copy_fast(
&mut bus,
&[0, 1, 2, 3, 4, 5, 6, 7],
0,
&pm,
&src_to_dst,
false,
0,
0,
0,
1,
8,
0,
0,
0,
0,
0,
0,
1,
8,
1.0,
1.0,
screen_base,
1,
8,
1,
1,
None,
None,
&mut scratch,
));
assert_eq!(bus.read_byte(screen_base), 0b0101_0101);
}
#[test]
fn packbits_src_copy_fast_path_decodes_only_visible_mapped_span() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let pict_row = 0x04_0000u32;
bus.write_byte(pict_row, 9); bus.write_byte(pict_row + 1, 7); bus.write_bytes(pict_row + 2, &[1, 2, 3, 4, 5, 6, 7, 8]);
let screen_base = 0x08_0000u32;
bus.write_bytes(screen_base, &[0xEE; 4]);
let pm = PixMapInfo {
row_bytes: 8,
bounds_top: 0,
bounds_left: 0,
bounds_bottom: 1,
bounds_right: 8,
pixel_size: 8,
cmp_count: 1,
pack_type: 0,
};
let mut src_to_dst = [0u8; 256];
for (index, slot) in src_to_dst.iter_mut().enumerate() {
*slot = index.wrapping_add(10) as u8;
}
let end = try_blit_packbits_8bpp_src_copy_fast(
&mut bus,
pict_row,
1,
&pm,
0,
&src_to_dst,
false,
0,
0,
1,
8,
0,
-2,
0,
0,
0,
0,
1,
8,
1.0,
1.0,
screen_base,
4,
4,
1,
8,
None,
None,
)
.expect("simple clipped 8bpp PackBits srcCopy should use the direct fast path");
assert_eq!(end, pict_row + 10);
assert_eq!(bus.read_bytes(screen_base, 4), vec![13, 14, 15, 16]);
}
fn write_peekable_indexed_packbits_pict(
bus: &mut MacMemoryBus,
pic: u32,
mode: u16,
leading_fill_rect: bool,
) {
bus.write_word(pic, 0);
bus.write_word(pic + 2, 0);
bus.write_word(pic + 4, 0);
bus.write_word(pic + 6, 1);
bus.write_word(pic + 8, 2);
let mut p = pic + 10;
bus.write_byte(p, 0x11);
p += 1;
bus.write_byte(p, 0x01);
p += 1;
bus.write_byte(p, 0x0E); p += 1;
bus.write_long(p, 0x0000_00CD);
p += 4;
if leading_fill_rect {
bus.write_byte(p, 0x34);
p += 1;
for value in [0i16, 0, 1, 1] {
bus.write_word(p, value as u16);
p += 2;
}
}
bus.write_byte(p, 0x98); p += 1;
bus.write_word(p, 0x8002); p += 2;
for value in [0i16, 0, 1, 2] {
bus.write_word(p, value as u16);
p += 2;
}
bus.write_word(p, 0); p += 2;
bus.write_word(p, 0); p += 2;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0x0048_0000); p += 4;
bus.write_long(p, 0x0048_0000); p += 4;
bus.write_word(p, 0); p += 2;
bus.write_word(p, 8); p += 2;
bus.write_word(p, 1); p += 2;
bus.write_word(p, 8); p += 2;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_word(p, 0x8000); p += 2;
bus.write_word(p, 1); p += 2;
for (index, rgb) in [
(0u16, [0x1111, 0x2222, 0x3333]),
(1u16, [0xAAAA, 0xBBBB, 0xCCCC]),
] {
bus.write_word(p, index);
p += 2;
for component in rgb {
bus.write_word(p, component);
p += 2;
}
}
for _ in 0..2 {
for value in [0i16, 0, 1, 2] {
bus.write_word(p, value as u16);
p += 2;
}
}
bus.write_word(p, mode);
p += 2;
bus.write_bytes(p, &[0, 1]);
p += 2;
bus.write_byte(p, 0xFF);
p += 1;
bus.write_word(pic, (p - pic) as u16);
}
#[test]
fn peek_initial_packbits_clut_accepts_simple_first_src_copy_image() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let pic = 0x10_0000u32;
write_peekable_indexed_packbits_pict(&mut bus, pic, 0, false);
let clut = peek_initial_packbits_clut(&bus, pic).expect("initial PackBitsRect CLUT");
assert_eq!(clut[0], [0x1111, 0x2222, 0x3333]);
assert_eq!(clut[1], [0xAAAA, 0xBBBB, 0xCCCC]);
}
#[test]
fn peek_initial_packbits_clut_rejects_non_src_copy_images() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let pic = 0x10_0000u32;
write_peekable_indexed_packbits_pict(&mut bus, pic, 1, false);
assert!(peek_initial_packbits_clut(&bus, pic).is_none());
}
#[test]
fn peek_initial_packbits_clut_rejects_after_prior_drawing_opcode() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let pic = 0x10_0000u32;
write_peekable_indexed_packbits_pict(&mut bus, pic, 0, true);
assert!(peek_initial_packbits_clut(&bus, pic).is_none());
}
#[test]
fn src_copy_transfer_table_is_direct_write_mapping() {
let mut src_to_dst = [0u8; 256];
for (index, slot) in src_to_dst.iter_mut().enumerate() {
*slot = 255u8.saturating_sub(index as u8);
}
let src = [[0u16; 3]; 256];
let dst = [[0u16; 3]; 256];
let table = build_pict_indexed_transfer_table(0, &src, &src_to_dst, &dst, 1, 2);
assert_eq!(table[0], PictIndexedTransfer::Write(255));
assert_eq!(table[42], PictIndexedTransfer::Write(213));
assert_eq!(table[255], PictIndexedTransfer::Write(0));
}
#[test]
fn packbitsrect_matching_ctseed_still_translates_when_tables_differ() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
let screen_row_bytes = 8u32;
bus.write_bytes(screen_base, &[0xEE; 8]);
let mut device_clut = [[0u16; 3]; 256];
device_clut[42] = [0xFFFF, 0, 0];
let pic = 0x10_0000u32;
let mut p = pic;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 1);
p += 2;
bus.write_word(p, 2);
p += 2;
bus.write_byte(p, 0x98); p += 1;
bus.write_word(p, 0x8002); p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 1);
p += 2;
bus.write_word(p, 2);
p += 2;
bus.write_word(p, 0); p += 2;
bus.write_word(p, 0); p += 2;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0x0048_0000); p += 4;
bus.write_long(p, 0x0048_0000); p += 4;
bus.write_word(p, 0); p += 2;
bus.write_word(p, 8); p += 2;
bus.write_word(p, 1); p += 2;
bus.write_word(p, 8); p += 2;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 8); p += 4;
bus.write_word(p, 0x8000);
p += 2;
bus.write_word(p, 1); p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 1);
p += 2;
bus.write_word(p, 0xFFFF);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
for _ in 0..2 {
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 1);
p += 2;
bus.write_word(p, 2);
p += 2;
}
bus.write_word(p, 0); p += 2;
bus.write_byte(p, 1);
p += 1;
bus.write_byte(p, 1);
p += 1;
bus.write_byte(p, 0xFF); p += 1;
bus.write_word(pic, (p - pic) as u16);
let (ok, _) = draw_picture(
&mut bus,
pic,
0,
0,
1,
2,
(screen_base, screen_row_bytes, 8, 1, 8),
&device_clut,
8,
None,
);
assert!(ok);
assert_eq!(
bus.read_bytes(screen_base, 2),
vec![42, 42],
"matching ctSeed is not enough for identity mapping when ColorTable contents differ"
);
}
#[test]
fn one_bit_packbitsrect_uses_destination_clut_black_and_white() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
bus.write_bytes(screen_base, &[0x42; 8]);
let mut clut = [[0x7777u16; 3]; 256];
clut[4] = [0x0000, 0x0000, 0x0000];
clut[7] = [0xFFFF, 0xFFFF, 0xFFFF];
let pic = 0x10_0000u32;
bus.write_word(pic, 42); bus.write_word(pic + 2, 0); bus.write_word(pic + 4, 0); bus.write_word(pic + 6, 1); bus.write_word(pic + 8, 8); let mut p = pic + 10;
bus.write_byte(p, 0x11);
p += 1;
bus.write_byte(p, 0x01);
p += 1;
bus.write_byte(p, 0x98); p += 1;
bus.write_word(p, 1); p += 2;
for value in [0i16, 0, 1, 8] {
bus.write_word(p, value as u16);
p += 2;
}
for _ in 0..2 {
for value in [0i16, 0, 1, 8] {
bus.write_word(p, value as u16);
p += 2;
}
}
bus.write_word(p, 0); p += 2;
bus.write_byte(p, 0b1010_0000);
p += 1;
bus.write_byte(p, 0xFF);
let (ok, _) = draw_picture(
&mut bus,
pic,
0,
0,
1,
8,
(screen_base, 8, 8, 1, 8),
&clut,
0,
None,
);
assert!(ok);
assert_eq!(bus.read_bytes(screen_base, 8), vec![4, 7, 4, 7, 7, 7, 7, 7]);
}
#[test]
fn eight_bit_packbitsrect_srcor_preserves_white_source_pixels() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
bus.write_bytes(screen_base, &[42; 16]);
let mut clut = [[0x7777u16; 3]; 256];
clut[0] = [0xFFFF, 0xFFFF, 0xFFFF];
clut[42] = [0x1234, 0x5678, 0x9ABC];
clut[255] = [0x0000, 0x0000, 0x0000];
let pic = 0x10_0000u32;
bus.write_word(pic, 0); bus.write_word(pic + 2, 0); bus.write_word(pic + 4, 0); bus.write_word(pic + 6, 4); bus.write_word(pic + 8, 4); let mut p = pic + 10;
bus.write_byte(p, 0x11); p += 1;
bus.write_byte(p, 0x01); p += 1;
bus.write_byte(p, 0x98); p += 1;
bus.write_word(p, 0x8004); p += 2;
for value in [0i16, 0, 4, 4] {
bus.write_word(p, value as u16);
p += 2;
}
bus.write_word(p, 0); p += 2;
bus.write_word(p, 0); p += 2;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0x0048_0000); p += 4;
bus.write_long(p, 0x0048_0000); p += 4;
bus.write_word(p, 0); p += 2;
bus.write_word(p, 8); p += 2;
bus.write_word(p, 1); p += 2;
bus.write_word(p, 8); p += 2;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_word(p, 0x8000); p += 2;
bus.write_word(p, 1); p += 2;
for (value, rgb) in [
(0u16, [0xFFFF, 0xFFFF, 0xFFFF]),
(1u16, [0x0000, 0x0000, 0x0000]),
] {
bus.write_word(p, value);
p += 2;
for component in rgb {
bus.write_word(p, component);
p += 2;
}
}
for _ in 0..2 {
for value in [0i16, 0, 4, 4] {
bus.write_word(p, value as u16);
p += 2;
}
}
bus.write_word(p, 1); p += 2;
bus.write_bytes(
p,
&[
1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
],
);
p += 16;
bus.write_byte(p, 0xFF); p += 1;
bus.write_word(pic, (p - pic) as u16);
let (ok, _) = draw_picture(
&mut bus,
pic,
0,
0,
4,
4,
(screen_base, 4, 4, 4, 8),
&clut,
0,
None,
);
assert!(ok);
assert_eq!(
bus.read_bytes(screen_base, 16),
vec![
255, 255, 255, 42, 255, 42, 42, 42, 255, 42, 42, 42, 42, 42, 42, 42,
]
);
}
#[test]
fn indexed_transfer_table_precomputes_srcor_black_white_actions() {
let mut src_clut = [[0u16; 3]; 256];
src_clut[0] = [0xFFFF, 0xFFFF, 0xFFFF];
src_clut[1] = [0x0000, 0x0000, 0x0000];
src_clut[2] = [0x8000, 0x8000, 0x8000];
let mut dst_clut = [[0x7777u16; 3]; 256];
dst_clut[0] = [0xFFFF, 0xFFFF, 0xFFFF];
dst_clut[42] = [0x8000, 0x8000, 0x8000];
dst_clut[255] = [0x0000, 0x0000, 0x0000];
let mut src_to_dst = [0u8; 256];
src_to_dst[0] = 0;
src_to_dst[1] = 255;
src_to_dst[2] = 42;
let table = build_pict_indexed_transfer_table(1, &src_clut, &src_to_dst, &dst_clut, 255, 0);
assert_eq!(table[0], PictIndexedTransfer::Skip);
assert_eq!(table[1], PictIndexedTransfer::Write(255));
assert!(
matches!(table[2], PictIndexedTransfer::Write(_)),
"non-white source colors should be resolved once into the transfer table"
);
}
#[test]
fn indexed_src_copy_ignores_foreground_and_background_colors() {
let mut src_clut = [[0u16; 3]; 256];
src_clut[1] = [0xFFFF, 0x0000, 0x0000];
let mut dst_clut = [[0x7777u16; 3]; 256];
dst_clut[7] = [0x0000, 0xFFFF, 0x0000];
dst_clut[42] = [0xFFFF, 0x0000, 0x0000];
dst_clut[99] = [0x0000, 0x0000, 0xFFFF];
let mut src_to_dst = [0u8; 256];
src_to_dst[1] = 42;
let table = build_pict_indexed_transfer_table(0, &src_clut, &src_to_dst, &dst_clut, 7, 99);
assert_eq!(
table[1],
PictIndexedTransfer::Write(42),
"indexed srcCopy should copy the source ColorTable color, not tint it through fg/bg"
);
}
#[test]
fn color_packbitsrect_to_one_bit_destination_maps_to_black_or_white() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
bus.write_byte(screen_base, 0);
let pic = 0x10_0000u32;
bus.write_word(pic, 0);
bus.write_word(pic + 2, 0);
bus.write_word(pic + 4, 0);
bus.write_word(pic + 6, 1);
bus.write_word(pic + 8, 3);
let mut p = pic + 10;
bus.write_byte(p, 0x98); p += 1;
bus.write_word(p, 0x8003); p += 2;
for value in [0i16, 0, 1, 3] {
bus.write_word(p, value as u16);
p += 2;
}
bus.write_word(p, 0); p += 2;
bus.write_word(p, 0); p += 2;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0x0048_0000); p += 4;
bus.write_long(p, 0x0048_0000); p += 4;
bus.write_word(p, 0); p += 2;
bus.write_word(p, 8); p += 2;
bus.write_word(p, 1); p += 2;
bus.write_word(p, 8); p += 2;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_long(p, 0); p += 4;
bus.write_word(p, 0x8000); p += 2;
bus.write_word(p, 2); p += 2;
for (index, [r, g, b]) in [
(0u16, [0x0000, 0x0000, 0x0000]), (1u16, [0xFFFF, 0xFFFF, 0x0000]), (2u16, [0xFFFF, 0xFFFF, 0xFFFF]), ] {
bus.write_word(p, index);
p += 2;
bus.write_word(p, r);
p += 2;
bus.write_word(p, g);
p += 2;
bus.write_word(p, b);
p += 2;
}
for _ in 0..2 {
for value in [0i16, 0, 1, 3] {
bus.write_word(p, value as u16);
p += 2;
}
}
bus.write_word(p, 0); p += 2;
bus.write_byte(p, 0); p += 1;
bus.write_byte(p, 1); p += 1;
bus.write_byte(p, 2); p += 1;
bus.write_byte(p, 0xFF); p += 1;
bus.write_word(pic, (p - pic) as u16);
let clut = TrapDispatcher::standard_mac_8bpp_clut();
let (ok, _) = draw_picture(
&mut bus,
pic,
0,
0,
1,
3,
(screen_base, 1, 8, 1, 1),
&clut,
0,
None,
);
assert!(ok);
assert_eq!(
bus.read_byte(screen_base) & 0b1110_0000,
0b1000_0000,
"indexed color PICT pixels drawn into 1bpp must resolve against the black/white destination, not the full 8bpp CLUT"
);
}
#[test]
fn directbitsrect_packtype4_uses_pixmap_rowbytes_for_word_byte_counts() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
let screen_w: u16 = 80;
let screen_h: u16 = 2;
let row_bytes = u32::from(screen_w);
bus.write_bytes(
screen_base,
&vec![0xAA; (row_bytes * u32::from(screen_h)) as usize],
);
let pic = 0x10_0000u32;
let mut p = pic + 10;
bus.write_byte(p, 0x11);
p += 1; bus.write_byte(p, 0x02);
p += 1; bus.write_byte(p, 0xFF);
p += 1; bus.write_byte(p, 0x00);
p += 1;
bus.write_word(p, 0x009A);
p += 2; bus.write_long(p, 0x0000_00FF);
p += 4; bus.write_word(p, 0x8000 | 320);
p += 2; for value in [0i16, 0, 1, 80] {
bus.write_word(p, value as u16);
p += 2;
}
bus.write_word(p, 0);
p += 2; bus.write_word(p, 4);
p += 2; bus.write_long(p, 0);
p += 4; bus.write_long(p, 0x0048_0000);
p += 4; bus.write_long(p, 0x0048_0000);
p += 4; bus.write_word(p, 16);
p += 2; bus.write_word(p, 32);
p += 2; bus.write_word(p, 3);
p += 2; bus.write_word(p, 8);
p += 2; bus.write_long(p, 0);
p += 4; bus.write_long(p, 0);
p += 4; bus.write_long(p, 0);
p += 4;
for _ in 0..2 {
for value in [0i16, 0, 1, 80] {
bus.write_word(p, value as u16);
p += 2;
}
}
bus.write_word(p, 64);
p += 2;
bus.write_word(p, 6);
p += 2;
for component in [0x12u8, 0x34, 0x56] {
bus.write_byte(p, 0xB1); p += 1;
bus.write_byte(p, component);
p += 1;
}
bus.write_word(p, 0x0034);
p += 2; bus.write_word(p, 1);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 2);
p += 2;
bus.write_word(p, 80);
p += 2;
bus.write_word(p, 0x00FF);
p += 2;
bus.write_word(pic, (p - pic) as u16);
bus.write_word(pic + 2, 0);
bus.write_word(pic + 4, 0);
bus.write_word(pic + 6, 2);
bus.write_word(pic + 8, 80);
let mut clut = [[0x8000u16, 0x8000, 0x8000]; 256];
clut[0] = [0xFFFF, 0xFFFF, 0xFFFF];
clut[42] = [0x1212, 0x3434, 0x5656];
clut[255] = [0x0000, 0x0000, 0x0000];
let (ok, _) = draw_picture(
&mut bus,
pic,
0,
0,
screen_h as i16,
screen_w as i16,
(screen_base, row_bytes, screen_w, screen_h, 8),
&clut,
0,
None,
);
assert!(ok);
assert_eq!(bus.read_byte(screen_base), 42);
assert_eq!(bus.read_byte(screen_base + row_bytes), 255);
}
#[test]
fn pict_fillrect_honors_fillpat_not_pnpat() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
let screen_w: u16 = 32;
let screen_h: u16 = 32;
let row_bytes = screen_w as u32;
bus.write_bytes(
screen_base,
&vec![0x42; (row_bytes * screen_h as u32) as usize],
);
let pic = 0x10_0000u32;
let mut p = pic + 10;
bus.write_byte(p, 0x11);
p += 1; bus.write_byte(p, 0x01);
p += 1; bus.write_byte(p, 0x09);
p += 1;
bus.fill_zeros(p, 8);
p += 8;
bus.write_byte(p, 0x0A);
p += 1;
bus.write_bytes(p, &[0xFFu8; 8]);
p += 8;
bus.write_byte(p, 0x34);
p += 1;
bus.write_word(p, 0);
p += 2; bus.write_word(p, 0);
p += 2; bus.write_word(p, 32);
p += 2; bus.write_word(p, 32);
p += 2; bus.write_byte(p, 0xFF);
bus.write_word(pic, (p - pic + 1) as u16); bus.write_word(pic + 2, 0);
bus.write_word(pic + 4, 0);
bus.write_word(pic + 6, 32);
bus.write_word(pic + 8, 32);
let clut = TrapDispatcher::standard_mac_8bpp_clut();
let (_ok, _clut) = draw_picture(
&mut bus,
pic,
0,
0,
32,
32,
(screen_base, row_bytes, screen_w, screen_h, 8),
&clut,
0,
None,
);
let sample = bus.read_byte(screen_base + 8 * row_bytes + 8);
assert_eq!(
sample, 255,
"fillRect must use FillPat (all-0xFF → fg=255), not PnPat \
(all-0x00 → bg=0). Got 0x{:02X}.",
sample,
);
}
#[test]
fn pict_v2_opcolor_advances_to_following_opcode() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
let screen_w: u16 = 16;
let screen_h: u16 = 16;
let row_bytes = screen_w as u32;
bus.write_bytes(
screen_base,
&vec![0x42; (row_bytes * screen_h as u32) as usize],
);
let pic = 0x10_0000u32;
let mut p = pic + 10;
bus.write_byte(p, 0x11);
p += 1; bus.write_byte(p, 0x02);
p += 1; bus.write_byte(p, 0xFF);
p += 1; bus.write_byte(p, 0x00);
p += 1; bus.write_word(p, 0x001F);
p += 2; bus.write_word(p, 0x1111);
p += 2;
bus.write_word(p, 0x2222);
p += 2;
bus.write_word(p, 0x3333);
p += 2;
bus.write_word(p, 0x0034);
p += 2; bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 16);
p += 2;
bus.write_word(p, 16);
p += 2;
bus.write_word(p, 0x00FF);
p += 2;
bus.write_word(pic, (p - pic) as u16);
bus.write_word(pic + 2, 0);
bus.write_word(pic + 4, 0);
bus.write_word(pic + 6, 16);
bus.write_word(pic + 8, 16);
let clut = TrapDispatcher::standard_mac_8bpp_clut();
let (ok, _) = draw_picture(
&mut bus,
pic,
0,
0,
16,
16,
(screen_base, row_bytes, screen_w, screen_h, 8),
&clut,
0,
None,
);
assert!(ok, "v2 OpColor should be skipped, not stop the PICT stream");
assert_eq!(bus.read_byte(screen_base + 8 * row_bytes + 8), 255);
}
#[test]
fn pict_v1_reserved_shape_opcode_advances_to_following_opcode() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
let screen_w: u16 = 16;
let screen_h: u16 = 16;
let row_bytes = screen_w as u32;
bus.write_bytes(
screen_base,
&vec![0x42; (row_bytes * screen_h as u32) as usize],
);
let pic = 0x10_0000u32;
let mut p = pic + 10;
bus.write_byte(p, 0x11);
p += 1; bus.write_byte(p, 0x01);
p += 1; bus.write_byte(p, 0x35);
p += 1; bus.write_bytes(p, &[0xAA; 8]);
p += 8;
bus.write_byte(p, 0x34);
p += 1; bus.write_word(p, 0);
p += 2;
bus.write_word(p, 0);
p += 2;
bus.write_word(p, 16);
p += 2;
bus.write_word(p, 16);
p += 2;
bus.write_byte(p, 0xFF);
p += 1;
bus.write_word(pic, (p - pic) as u16);
bus.write_word(pic + 2, 0);
bus.write_word(pic + 4, 0);
bus.write_word(pic + 6, 16);
bus.write_word(pic + 8, 16);
let clut = TrapDispatcher::standard_mac_8bpp_clut();
let (ok, _) = draw_picture(
&mut bus,
pic,
0,
0,
16,
16,
(screen_base, row_bytes, screen_w, screen_h, 8),
&clut,
0,
None,
);
assert!(
ok,
"v1 reserved shape opcodes should skip their data, not stop the PICT stream"
);
assert_eq!(bus.read_byte(screen_base + 8 * row_bytes + 8), 255);
}
#[test]
fn pict_fillpoly_honors_fillpat_striped_pattern() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
let screen_w: u16 = 32;
let screen_h: u16 = 32;
let row_bytes = screen_w as u32;
bus.write_bytes(
screen_base,
&vec![0x42; (row_bytes * screen_h as u32) as usize],
);
let pic = 0x10_0000u32;
let mut p = pic + 10;
bus.write_byte(p, 0x11);
p += 1; bus.write_byte(p, 0x01);
p += 1; bus.write_byte(p, 0x0A);
p += 1;
for row in 0..8 {
bus.write_byte(p, if row % 2 == 0 { 0xFF } else { 0x00 });
p += 1;
}
bus.write_byte(p, 0x74);
p += 1;
let poly_size: u16 = 10 + 4 * 4;
bus.write_word(p, poly_size);
p += 2;
bus.write_word(p, 4);
p += 2; bus.write_word(p, 4);
p += 2; bus.write_word(p, 28);
p += 2; bus.write_word(p, 28);
p += 2; for &(v, h) in &[(4i16, 4i16), (4, 28), (28, 28), (28, 4)] {
bus.write_word(p, v as u16);
p += 2;
bus.write_word(p, h as u16);
p += 2;
}
bus.write_byte(p, 0xFF);
bus.write_word(pic, (p - pic + 1) as u16);
bus.write_word(pic + 2, 0);
bus.write_word(pic + 4, 0);
bus.write_word(pic + 6, 32);
bus.write_word(pic + 8, 32);
let clut = TrapDispatcher::standard_mac_8bpp_clut();
let _ = draw_picture(
&mut bus,
pic,
0,
0,
32,
32,
(screen_base, row_bytes, screen_w, screen_h, 8),
&clut,
0,
None,
);
let x = 10u32;
let mut saw_fg = false;
let mut saw_bg = false;
for dy in 6..16u32 {
let val = bus.read_byte(screen_base + dy * row_bytes + x);
if val == 255 {
saw_fg = true;
}
if val == 0 {
saw_bg = true;
}
}
assert!(saw_fg, "striped fillPoly must produce some fg_idx pixels");
assert!(saw_bg, "striped fillPoly must produce some bg_idx pixels");
}
#[test]
fn pict_filloval_honors_fillpat_striped_pattern() {
let mut bus = MacMemoryBus::new(2 * 1024 * 1024);
let screen_base = 0x08_0000u32;
let screen_w: u16 = 32;
let screen_h: u16 = 32;
let row_bytes = screen_w as u32;
bus.write_bytes(
screen_base,
&vec![0x42; (row_bytes * screen_h as u32) as usize],
);
let pic = 0x10_0000u32;
let mut p = pic + 10;
bus.write_byte(p, 0x11);
p += 1;
bus.write_byte(p, 0x01);
p += 1;
bus.write_byte(p, 0x0A);
p += 1;
for row in 0..8 {
bus.write_byte(p, if row % 2 == 0 { 0xFF } else { 0x00 });
p += 1;
}
bus.write_byte(p, 0x54);
p += 1;
bus.write_word(p, 2);
p += 2;
bus.write_word(p, 2);
p += 2;
bus.write_word(p, 30);
p += 2;
bus.write_word(p, 30);
p += 2;
bus.write_byte(p, 0xFF);
bus.write_word(pic, (p - pic + 1) as u16);
bus.write_word(pic + 2, 0);
bus.write_word(pic + 4, 0);
bus.write_word(pic + 6, 32);
bus.write_word(pic + 8, 32);
let clut = TrapDispatcher::standard_mac_8bpp_clut();
let _ = draw_picture(
&mut bus,
pic,
0,
0,
32,
32,
(screen_base, row_bytes, screen_w, screen_h, 8),
&clut,
0,
None,
);
let x = 16u32;
let mut saw_fg = false;
let mut saw_bg = false;
for dy in 6..22u32 {
let val = bus.read_byte(screen_base + dy * row_bytes + x);
if val == 255 {
saw_fg = true;
}
if val == 0 {
saw_bg = true;
}
}
assert!(saw_fg, "striped fillOval must produce some fg_idx pixels");
assert!(saw_bg, "striped fillOval must produce some bg_idx pixels");
}
}