extern crate cdg;
extern crate image;
use cdg::RgbColor;
use std::ops::{Add, Fn, Index, IndexMut};
pub trait One {
fn one() -> Self;
}
impl One for u16 {
fn one() -> Self {
1
}
}
impl One for u8 {
fn one() -> Self {
1
}
}
#[derive(Clone, Copy, Debug)]
pub struct Position<T> {
x: T,
y: T,
}
impl<T> Position<T> {
pub fn new(x: T, y: T) -> Self {
Position { x, y }
}
}
#[derive(Copy, Clone, Debug)]
pub struct Rectangle<T> {
pub nw: Position<T>,
pub se: Position<T>,
}
impl<T: Ord + Copy + Add<Output = T> + One> Rectangle<T> {
pub fn new(p0: Position<T>, p1: Position<T>) -> Self {
use std::cmp::{max, min};
Rectangle {
nw: Position {
x: min(p0.x, p1.x),
y: min(p0.y, p1.y),
},
se: Position {
x: max(p0.x, p1.x),
y: max(p0.y, p1.y),
},
}
}
pub fn expand(&self, p: Position<T>) -> Self {
use std::cmp::{max, min};
Rectangle {
nw: Position {
x: min(self.nw.x, p.x),
y: min(self.nw.y, p.y),
},
se: Position {
x: max(self.se.x, p.x + T::one()),
y: max(self.se.y, p.y + T::one()),
},
}
}
}
const TILE_ROWS: usize = 18;
const TILE_COLS: usize = 50;
pub struct CdgInterpreter {
tile_shift: Position<u16>,
pixel_shift: Position<u16>,
clut: [cdg::RgbColor; 16],
dirty: Option<Rectangle<u16>>, content: [[u8; 300]; 216],
border: u8,
transparent: u8, }
struct TileView<'a> {
interp: &'a mut CdgInterpreter,
x: usize, y: usize,
}
impl<'a> TileView<'a> {
fn map_pixels<F>(&mut self, func: F)
where
F: Fn(u8, u8, &mut u8),
{
for y in 0..12 {
for x in 0..6 {
func(x, y, &mut self[(x, y)])
}
}
}
fn draw_normal(&mut self, tile: &cdg::Tile) {
self.map_pixels(|x, y, px| *px = tile.get_pixel(x, y))
}
fn draw_xor(&mut self, tile: &cdg::Tile) {
self.map_pixels(|x, y, px| *px ^= tile.get_pixel(x, y))
}
}
impl<'b> Index<(u8, u8)> for TileView<'b> {
type Output = u8;
fn index(&self, pos: (u8, u8)) -> &u8 {
assert!(pos.0 < 6 && pos.1 < 12);
unsafe {
self.interp
.content
.get_unchecked(self.y + pos.1 as usize)
.get_unchecked(self.x + pos.0 as usize)
}
}
}
impl<'b> IndexMut<(u8, u8)> for TileView<'b> {
fn index_mut(&mut self, pos: (u8, u8)) -> &mut u8 {
assert!(pos.0 < 6 && pos.1 < 12);
unsafe {
self.interp
.content
.get_unchecked_mut(self.y + pos.1 as usize)
.get_unchecked_mut(self.x + pos.0 as usize)
}
}
}
fn default_colors() -> [RgbColor; 16] {
[
RgbColor::from_rgb(0, 0, 0),
RgbColor::from_rgb(0, 0, 170),
RgbColor::from_rgb(0, 170, 0),
RgbColor::from_rgb(0, 170, 170),
RgbColor::from_rgb(170, 0, 0),
RgbColor::from_rgb(170, 0, 170),
RgbColor::from_rgb(170, 170, 0),
RgbColor::from_rgb(170, 170, 170),
RgbColor::from_rgb(85, 85, 85),
RgbColor::from_rgb(85, 85, 255),
RgbColor::from_rgb(85, 255, 85),
RgbColor::from_rgb(85, 255, 255),
RgbColor::from_rgb(255, 85, 85),
RgbColor::from_rgb(255, 85, 255),
RgbColor::from_rgb(255, 255, 85),
RgbColor::from_rgb(255, 255, 255),
]
}
impl CdgInterpreter {
pub fn new() -> Self {
CdgInterpreter {
tile_shift: Position::new(0, 0),
pixel_shift: Position::new(0, 0),
clut: default_colors(),
dirty: Some(Rectangle::new(Position::new(0, 0), Position::new(50, 18))),
content: [[0; 300]; 216],
border: 0,
transparent: 255,
}
}
#[allow(unused)]
fn map_pxrow(&self, row: usize) -> usize {
(row + self.tile_shift.y as usize * 12) % 216
}
#[allow(unused)]
fn map_pxcol(&self, row: usize) -> usize {
(row + self.tile_shift.x as usize * 6) % 300
}
fn map_trow(&self, row: usize) -> usize {
(row + self.tile_shift.y as usize) % TILE_ROWS
}
fn map_tcol(&self, col: usize) -> usize {
(col + self.tile_shift.x as usize) % TILE_COLS
}
fn clear_col(&mut self, n: usize, color: Option<u8>) {
match color {
None => (),
Some(color) => {
let col = self.map_tcol(n) * 6;
for r in 0..216 {
for c in col..col + 6 {
unsafe {
*self.content.get_unchecked_mut(r).get_unchecked_mut(c) = color;
}
}
}
}
}
}
fn clear_row(&mut self, n: usize, color: Option<u8>) {
match color {
None => (),
Some(color) => {
let col = self.map_trow(n) * 12;
for c in col..col + 12 {
for r in 0..216 {
unsafe {
*self.content.get_unchecked_mut(r).get_unchecked_mut(c) = color;
}
}
}
}
}
}
fn get_tile(&mut self, pos: (u8, u8)) -> TileView {
let x = (pos.0 as u16 + self.tile_shift.x) as usize % TILE_COLS;
let y = (pos.1 as u16 + self.tile_shift.y) as usize % TILE_ROWS;
TileView {
interp: self,
x: x * 6,
y: y * 12,
}
}
pub fn dirty(&self) -> Option<Rectangle<u16>> {
self.dirty
}
fn invalidate_tile(&mut self, pos: (u8, u8)) {
let new_tile = Position::new(pos.0 as u16, pos.1 as u16);
let new_tilep = Position::new(pos.0 as u16 + 1, pos.1 as u16 + 1);
self.dirty = self
.dirty
.map(|x| x.expand(new_tile))
.or_else(|| Some(Rectangle::new(new_tile, new_tilep)));
}
fn invalidate_all(&mut self) {
self.dirty = Some(Rectangle::new(Position::new(0, 0), Position::new(50, 18)));
}
pub fn clear_dirty_region(&mut self) {
self.dirty = None;
}
pub fn reset(&mut self, reset_color: bool) {
self.tile_shift = Position::new(0, 0);
self.pixel_shift = Position::new(0, 0);
self.dirty = Some(Rectangle::new(Position::new(0, 0), Position::new(50, 18)));
self.content = [[0; 300]; 216];
self.border = 0;
self.transparent = 255;
if reset_color {
self.clut = default_colors();
}
}
}
impl image::GenericImageView for CdgInterpreter {
type Pixel = image::Rgba<u8>;
fn dimensions(&self) -> (u32, u32) {
(300, 216)
}
fn bounds(&self) -> (u32, u32, u32, u32) {
(0, 0, 300, 216)
}
fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel {
use image::Pixel;
let cindex = self.content[self.map_pxrow(y as usize)][self.map_pxcol(x as usize)];
let c = self.clut[cindex as usize];
if self.transparent == cindex {
*Pixel::from_slice(&[c.r(), c.g(), c.b(), 0])
} else {
*Pixel::from_slice(&[c.r(), c.g(), c.b(), 255])
}
}
}
impl CdgInterpreter {
pub fn handle_cmd(&mut self, command: cdg::Command) {
use cdg::Command::*;
match command {
MemoryPreset { color, repeat: 0 } => {
for y in 0..216 {
for x in 0..300 {
self.content[y][x] = color;
}
}
self.invalidate_all();
}
MemoryPreset { .. } => (),
BorderPreset { color } => {
self.border = color;
self.invalidate_all();
}
TileNormal { tile } => {
self.get_tile(tile.pos).draw_normal(&tile);
self.invalidate_tile(tile.pos);
}
TileXOR { tile } => {
self.get_tile(tile.pos).draw_xor(&tile);
self.invalidate_tile(tile.pos);
}
Scroll {
color,
cmd: (xc, yc),
offset: (xo, yo),
} => {
use cdg::ScrollCommand::{Noop, NW, SE};
match xc {
NW => {
self.clear_col(0, color);
self.tile_shift.x = (self.tile_shift.x + 1) % TILE_COLS as u16;
}
SE => {
self.tile_shift.x =
(self.tile_shift.x + TILE_COLS as u16 - 1) % TILE_COLS as u16;
self.clear_col(0, color);
}
Noop => (),
}
match yc {
NW => {
self.clear_row(0, color);
self.tile_shift.y = (self.tile_shift.y + 1) % TILE_ROWS as u16;
}
SE => {
self.tile_shift.y =
(self.tile_shift.y + TILE_ROWS as u16 - 1) % TILE_ROWS as u16;
self.clear_row(0, color);
}
Noop => (),
}
self.pixel_shift = Position::new(xo as u16 % 6, yo as u16 % 12);
self.invalidate_all();
}
SetTransparent { color } => {
self.transparent = color;
self.invalidate_all();
}
LoadPalette { offset, clut } => {
let off = offset as usize;
self.clut[off..off + 8].copy_from_slice(&clut);
self.invalidate_all();
}
}
}
}
impl Default for CdgInterpreter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {}
}