From 5695ff973b642a00d766d6ff7a90dd33b0d4f7c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com>
Date: Mon, 6 Nov 2023 22:57:39 +0000
Subject: [PATCH] chore: support for palettes in py Makes use of hex based
 colors to simplify interface.

---
 src/ppu.rs                       | 36 ++++++++++++++++++++++++++++++++
 src/py.rs                        |  7 ++++++-
 src/python/boytacean/__init__.py | 10 ++++++++-
 src/python/boytacean/gb.py       | 11 ++++++++++
 src/python/boytacean/palettes.py | 17 +++++++++++++++
 5 files changed, 79 insertions(+), 2 deletions(-)
 create mode 100644 src/python/boytacean/palettes.py

diff --git a/src/ppu.rs b/src/ppu.rs
index 995bfb27..1cd40a9f 100644
--- a/src/ppu.rs
+++ b/src/ppu.rs
@@ -144,6 +144,24 @@ impl PaletteInfo {
         }
     }
 
+    pub fn from_colors_hex(name: &str, colors_hex: &str) -> Self {
+        let colors = Self::parse_colors_hex(colors_hex);
+        Self::new(name, colors)
+    }
+
+    pub fn parse_colors_hex(colors_hex: &str) -> Palette {
+        let mut colors = [[0u8; RGB_SIZE]; PALETTE_SIZE];
+        for (index, color) in colors_hex.split(',').enumerate() {
+            let color = color.trim();
+            let color = u32::from_str_radix(color, 16).unwrap_or(0);
+            let r = ((color >> 16) & 0xff) as u8;
+            let g = ((color >> 8) & 0xff) as u8;
+            let b = (color & 0xff) as u8;
+            colors[index] = [r, g, b];
+        }
+        colors
+    }
+
     pub fn name(&self) -> &String {
         &self.name
     }
@@ -151,6 +169,24 @@ impl PaletteInfo {
     pub fn colors(&self) -> &Palette {
         &self.colors
     }
+
+    pub fn colors_hex(&self) -> String {
+        let mut buffer = String::new();
+        let mut is_first = true;
+        for color in self.colors.iter() {
+            let r = color[0];
+            let g = color[1];
+            let b = color[2];
+            let color = (r as u32) << 16 | (g as u32) << 8 | b as u32;
+            if is_first {
+                is_first = false;
+            } else {
+                buffer.push(',');
+            }
+            buffer.push_str(format!("{:06x}", color).as_str());
+        }
+        buffer
+    }
 }
 
 /// Represents a tile within the Game Boy context,
diff --git a/src/py.rs b/src/py.rs
index 87aadc16..4b18b1a7 100644
--- a/src/py.rs
+++ b/src/py.rs
@@ -3,7 +3,7 @@ use pyo3::{prelude::*, types::PyBytes};
 use crate::{
     gb::{GameBoy as GameBoyBase, GameBoyMode},
     info::Info,
-    ppu::{DISPLAY_HEIGHT, DISPLAY_WIDTH},
+    ppu::{PaletteInfo, DISPLAY_HEIGHT, DISPLAY_WIDTH},
 };
 
 #[pyclass]
@@ -57,6 +57,11 @@ impl GameBoy {
         pybytes.into()
     }
 
+    pub fn set_palette_colors(&mut self, colors_hex: &str) {
+        let palette = PaletteInfo::from_colors_hex("default", colors_hex);
+        self.system.ppu().set_palette_colors(palette.colors());
+    }
+
     pub fn ppu_enabled(&self) -> bool {
         self.system.ppu_enabled()
     }
diff --git a/src/python/boytacean/__init__.py b/src/python/boytacean/__init__.py
index adc3074f..1b4517ad 100644
--- a/src/python/boytacean/__init__.py
+++ b/src/python/boytacean/__init__.py
@@ -1 +1,9 @@
-from .gb import *
+from .gb import (
+    DISPLAY_WIDTH,
+    DISPLAY_HEIGHT,
+    CPU_FREQ,
+    GameBoy,
+    GameBoyMode,
+    GameBoyRust,
+)
+from .palettes import PALETTES
diff --git a/src/python/boytacean/gb.py b/src/python/boytacean/gb.py
index 12c477c2..9451ffdb 100644
--- a/src/python/boytacean/gb.py
+++ b/src/python/boytacean/gb.py
@@ -2,6 +2,8 @@ from enum import Enum
 
 from PIL.Image import Image, frombytes
 
+from .palettes import PALETTES
+
 from .boytacean import DISPLAY_WIDTH, DISPLAY_HEIGHT, CPU_FREQ, GameBoy as GameBoyRust
 
 
@@ -76,6 +78,15 @@ This is a [Game Boy](https://en.wikipedia.org/wiki/Game_Boy) emulator built usin
         image = self.image()
         image.save(filename, format=format)
 
+    def set_palette(self, name: str):
+        if not name in PALETTES:
+            raise ValueError(f"Unknown palette: {name}")
+        palette = PALETTES[name]
+        self.set_palette_colors(palette)
+
+    def set_palette_colors(self, colors_hex: str):
+        self._system.set_palette_colors(colors_hex)
+
     @property
     def ppu_enabled(self) -> bool:
         return self._system.ppu_enabled()
diff --git a/src/python/boytacean/palettes.py b/src/python/boytacean/palettes.py
new file mode 100644
index 00000000..7f119777
--- /dev/null
+++ b/src/python/boytacean/palettes.py
@@ -0,0 +1,17 @@
+BASIC_PALETTE = "ffffff,c0c0c0,606060,000000"
+HOGWARDS_PALETTE = "b6a571,8b7e56,554d35,201d13"
+CHRISTMAS_PALETTE = "e8e7df,8bab95,9e5c5e,534d57"
+GOLDSILVER_PALETTE = "c5c66d,97a1b0,585e67,235229"
+PACMAN_PALETTE = "ffff00,ffb897,3732ff,000000"
+MARIOBROS_PALETTE = "f7cec3,cc9e22,923404,000000"
+POKEMON_PALETTE = "f87800,b86000,783800,000000"
+
+PALETTES = {
+    "basic": BASIC_PALETTE,
+    "hogwards": HOGWARDS_PALETTE,
+    "christmas": CHRISTMAS_PALETTE,
+    "goldsilver": GOLDSILVER_PALETTE,
+    "pacman": PACMAN_PALETTE,
+    "mariobros": MARIOBROS_PALETTE,
+    "pokemon": POKEMON_PALETTE,
+}
-- 
GitLab