1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
// Copyright 2024 Mikael Lund aka Wombat
//
// Licensed under the Apache license, version 2.0 (the "license");
// you may not use this file except in compliance with the license.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/license-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the license is distributed on an "as is" basis,
// without warranties or conditions of any kind, either express or implied.
// See the license for the specific language governing permissions and
// limitations under the license.
//! Commodore 64 display drivers and color palette
use embedded_graphics_core::pixelcolor::*;
use embedded_graphics_core::prelude::*;
/// Color palette of the VIC-II chip found in e.g. the Commodore 64 and 128
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum VicIIPalette {
Black = 0,
White = 1,
Red = 2,
Cyan = 3,
Purple = 4,
Green = 5,
Blue = 6,
Yellow = 7,
Orange = 8,
Brown = 9,
LightRed = 10,
/// Also known as gray 1
DarkGray = 11,
/// Also known as gray 2
Gray = 12,
LightGreen = 13,
LightBlue = 14,
/// Also known as Gray 3
LightGray = 15,
}
impl const From<Gray8> for VicIIPalette {
fn from(value: Gray8) -> Self {
match value.luma() {
0..=31 => Self::Black,
32..=63 => Self::DarkGray,
64..=95 => Self::Gray,
96..=127 => Self::LightGray,
128..=255 => Self::White,
}
}
}
/// Todo: currently this simply transforms to grey scale
impl const From<Rgb555> for VicIIPalette {
fn from(value: Rgb555) -> Self {
Gray8::from(value).into()
}
}
/// Todo: currently this simply transforms to grey scale
impl const From<Rgb888> for VicIIPalette {
fn from(value: Rgb888) -> Self {
Gray8::from(value).into()
}
}
impl const From<VicIIPalette> for Rgb888 {
fn from(color: VicIIPalette) -> Self {
match color {
VicIIPalette::Black => Self::new(0, 0, 0),
VicIIPalette::White => Self::new(255, 255, 255),
VicIIPalette::Red => Self::new(136, 0, 0),
VicIIPalette::Cyan => Self::new(170, 255, 238),
VicIIPalette::Purple => Self::new(204, 68, 204),
VicIIPalette::Green => Self::new(0, 204, 85),
VicIIPalette::Blue => Self::new(0, 0, 170),
VicIIPalette::Yellow => Self::new(238, 238, 119),
VicIIPalette::Orange => Self::new(221, 136, 85),
VicIIPalette::Brown => Self::new(102, 68, 0),
VicIIPalette::LightRed => Self::new(255, 119, 119),
VicIIPalette::DarkGray => Self::new(51, 51, 51),
VicIIPalette::Gray => Self::new(119, 119, 119),
VicIIPalette::LightGreen => Self::new(170, 255, 102),
VicIIPalette::LightBlue => Self::new(0, 136, 255),
VicIIPalette::LightGray => Self::new(187, 187, 187),
}
}
}
impl const PixelColor for VicIIPalette {
type Raw = ();
}
/// Color PETSCII Display Driver
///
/// Simple display driver for the C64's PETSCII text mode.
/// A "pixel" is a colored, filled square character on the 40 x 25 text display.
///
/// # Examples
/// ~~~
/// use retro_display::c64::{C64Color, PetsciiDisplay};
/// let mut display = PetsciiDisplay {};
/// display.clear(C64Color::Blue)?;
/// ~~~
pub struct PetsciiDisplay {}
impl PetsciiDisplay {
/// Number of columns in the C64 PETSCII display
const COLS: isize = 40;
/// Number of rows in the C64 PETSCII display
const ROWS: isize = 25;
/// VIC-II video memory pointer
const VIDEO_RAM: *mut u8 = (0x0400) as *mut u8;
/// VIC-II color memory pointer
const COLOR_RAM: *mut u8 = (0xd800) as *mut u8;
/// PETSCII symbol to mimic a pixel (filled square)
const PIXEL_SYMBOL: u8 = 0xa0;
/// Set pixel without checking if the position is within bounds
///
/// # Safety
///
/// Unsafe as the index is unchecked and may write to memory outside the display.
///
unsafe fn set_pixel_unchecked(index: isize, color: VicIIPalette) {
Self::COLOR_RAM.offset(index).write(color as u8);
Self::VIDEO_RAM.offset(index).write(Self::PIXEL_SYMBOL);
}
/// Set pixel with bounds checking
fn set_pixel(coord: &Point, color: VicIIPalette) {
// inelegant but small(est?) binary size
let x = coord.x as isize;
if (0..Self::COLS).contains(&x) {
let y = coord.y as isize;
if (0..Self::ROWS).contains(&y) {
let index = x + y * Self::COLS;
unsafe {
Self::set_pixel_unchecked(index, color);
}
}
}
}
}
impl const OriginDimensions for PetsciiDisplay {
fn size(&self) -> Size {
Size::new(Self::COLS as u32, Self::ROWS as u32)
}
}
impl DrawTarget for PetsciiDisplay {
type Color = VicIIPalette;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
pixels
.into_iter()
.for_each(|Pixel(coord, color)| Self::set_pixel(&coord, color));
Ok(())
}
}