add support for loading/saving palettes with fewer than 256 colors

This commit is contained in:
Gered 2022-12-12 15:05:08 -05:00
parent 5e65d63540
commit 8cb1d82b49
3 changed files with 160 additions and 20 deletions

View file

@ -156,25 +156,34 @@ pub fn greyscale(r: u8, b: u8, g: u8) -> u8 {
} }
// vga bios (0-63) format // vga bios (0-63) format
fn read_256color_6bit_palette<T: ReadBytesExt>( fn read_palette_6bit<T: ReadBytesExt>(
reader: &mut T, reader: &mut T,
num_colors: usize,
) -> Result<[u32; NUM_COLORS], PaletteError> { ) -> Result<[u32; NUM_COLORS], PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors))
}
let mut colors = [0u32; NUM_COLORS]; let mut colors = [0u32; NUM_COLORS];
for color in colors.iter_mut() { for i in 0..num_colors {
let r = reader.read_u8()?; let r = reader.read_u8()?;
let g = reader.read_u8()?; let g = reader.read_u8()?;
let b = reader.read_u8()?; let b = reader.read_u8()?;
*color = to_rgb32(r * 4, g * 4, b * 4); let color = to_rgb32(r * 4, g * 4, b * 4);
colors[i as usize] = color;
} }
Ok(colors) Ok(colors)
} }
fn write_256color_6bit_palette<T: WriteBytesExt>( fn write_palette_6bit<T: WriteBytesExt>(
writer: &mut T, writer: &mut T,
colors: &[u32; NUM_COLORS], colors: &[u32; NUM_COLORS],
num_colors: usize,
) -> Result<(), PaletteError> { ) -> Result<(), PaletteError> {
for color in colors.iter() { if num_colors > NUM_COLORS {
let (r, g, b) = from_rgb32(*color); return Err(PaletteError::OutOfRange(num_colors))
}
for i in 0..num_colors {
let (r, g, b) = from_rgb32(colors[i as usize]);
writer.write_u8(r / 4)?; writer.write_u8(r / 4)?;
writer.write_u8(g / 4)?; writer.write_u8(g / 4)?;
writer.write_u8(b / 4)?; writer.write_u8(b / 4)?;
@ -183,25 +192,34 @@ fn write_256color_6bit_palette<T: WriteBytesExt>(
} }
// normal (0-255) format // normal (0-255) format
fn read_256color_8bit_palette<T: ReadBytesExt>( fn read_palette_8bit<T: ReadBytesExt>(
reader: &mut T, reader: &mut T,
num_colors: usize,
) -> Result<[u32; NUM_COLORS], PaletteError> { ) -> Result<[u32; NUM_COLORS], PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors))
}
let mut colors = [0u32; NUM_COLORS]; let mut colors = [0u32; NUM_COLORS];
for color in colors.iter_mut() { for i in 0..num_colors {
let r = reader.read_u8()?; let r = reader.read_u8()?;
let g = reader.read_u8()?; let g = reader.read_u8()?;
let b = reader.read_u8()?; let b = reader.read_u8()?;
*color = to_rgb32(r, g, b); let color = to_rgb32(r, g, b);
colors[i as usize] = color;
} }
Ok(colors) Ok(colors)
} }
fn write_256color_8bit_palette<T: WriteBytesExt>( fn write_palette_8bit<T: WriteBytesExt>(
writer: &mut T, writer: &mut T,
colors: &[u32; NUM_COLORS], colors: &[u32; NUM_COLORS],
num_colors: usize,
) -> Result<(), PaletteError> { ) -> Result<(), PaletteError> {
for color in colors.iter() { if num_colors > NUM_COLORS {
let (r, g, b) = from_rgb32(*color); return Err(PaletteError::OutOfRange(num_colors))
}
for i in 0..num_colors {
let (r, g, b) = from_rgb32(colors[i as usize]);
writer.write_u8(r)?; writer.write_u8(r)?;
writer.write_u8(g)?; writer.write_u8(g)?;
writer.write_u8(b)?; writer.write_u8(b)?;
@ -213,6 +231,9 @@ fn write_256color_8bit_palette<T: WriteBytesExt>(
pub enum PaletteError { pub enum PaletteError {
#[error("Palette I/O error")] #[error("Palette I/O error")]
IOError(#[from] std::io::Error), IOError(#[from] std::io::Error),
#[error("Size or index is out of the supported range for palettes: {0}")]
OutOfRange(usize),
} }
pub enum PaletteFormat { pub enum PaletteFormat {
@ -274,8 +295,52 @@ impl Palette {
format: PaletteFormat, format: PaletteFormat,
) -> Result<Palette, PaletteError> { ) -> Result<Palette, PaletteError> {
let colors = match format { let colors = match format {
PaletteFormat::Vga => read_256color_6bit_palette(reader)?, PaletteFormat::Vga => read_palette_6bit(reader, NUM_COLORS)?,
PaletteFormat::Normal => read_256color_8bit_palette(reader)?, PaletteFormat::Normal => read_palette_8bit(reader, NUM_COLORS)?,
};
Ok(Palette { colors })
}
/// Loads and returns a Palette from a palette file on disk, where the palette only contains
/// the number of colors specified, less than or equal to 256 otherwise an error is returned.
/// The remaining color entries will all be 0,0,0 (black) in the returned palette.
///
/// # Arguments
///
/// * `path`: the path of the palette file to be loaded
/// * `format`: the format that the palette data is expected to be in
/// * `num_colors`: the expected number of colors in the palette to be loaded (<= 256)
pub fn load_num_colors_from_file(
path: &Path,
format: PaletteFormat,
num_colors: usize,
) -> Result<Palette, PaletteError> {
let f = File::open(path)?;
let mut reader = BufReader::new(f);
Self::load_num_colors_from_bytes(&mut reader, format, num_colors)
}
/// Loads and returns a Palette from a reader. The data being loaded is expected to be the same
/// as if the palette was being loaded from a file on disk. The palette being read should only
/// contain the number of colors specified, less than or equal to 256 otherwise an error is
/// returned. The remaining color entries will all be 0,0,0 (black) in the returned palette.
///
/// # Arguments
///
/// * `reader`: the reader to load the palette from
/// * `format`: the format that the palette data is expected to be in
/// * `num_colors`: the expected number of colors in the palette to be loaded (<= 256)
pub fn load_num_colors_from_bytes<T: ReadBytesExt>(
reader: &mut T,
format: PaletteFormat,
num_colors: usize,
) -> Result<Palette, PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors))
}
let colors = match format {
PaletteFormat::Vga => read_palette_6bit(reader, num_colors)?,
PaletteFormat::Normal => read_palette_8bit(reader, num_colors)?,
}; };
Ok(Palette { colors }) Ok(Palette { colors })
} }
@ -304,8 +369,57 @@ impl Palette {
format: PaletteFormat, format: PaletteFormat,
) -> Result<(), PaletteError> { ) -> Result<(), PaletteError> {
match format { match format {
PaletteFormat::Vga => write_256color_6bit_palette(writer, &self.colors), PaletteFormat::Vga => write_palette_6bit(writer, &self.colors, NUM_COLORS),
PaletteFormat::Normal => write_256color_8bit_palette(writer, &self.colors), PaletteFormat::Normal => write_palette_8bit(writer, &self.colors, NUM_COLORS),
}
}
/// Writes the palette to a file on disk. If the file already exists, it will be overwritten.
/// Will only write out the specified number of colors to the palette file being written,
/// starting from the first color in the palette always. If the color count specified is
/// greater than 256 an error is returned.
///
/// # Arguments
///
/// * `path`: the path of the file to save the palette to
/// * `format`: the format to write the palette data in
/// * `num_colors`: the number of colors from this palette to write out to the file (<= 256)
pub fn num_colors_to_file(
&self,
path: &Path,
format: PaletteFormat,
num_colors: usize,
) -> Result<(), PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors))
}
let f = File::create(path)?;
let mut writer = BufWriter::new(f);
self.num_colors_to_bytes(&mut writer, format, num_colors)
}
/// Writes the palette to a writer, in the same format as if it was writing to a file on disk.
/// Will only write out the specified number of colors to the writer, starting from the first
/// color in the palette always. If the color count specified is greater than 256 an error is
/// returned.
///
/// # Arguments
///
/// * `writer`: the writer to write palette data to
/// * `format`: the format to write the palette data in
/// * `num_colors`: the number of colors from this palette to write out (<= 256)
pub fn num_colors_to_bytes<T: WriteBytesExt>(
&self,
writer: &mut T,
format: PaletteFormat,
num_colors: usize,
) -> Result<(), PaletteError> {
if num_colors > NUM_COLORS {
return Err(PaletteError::OutOfRange(num_colors))
}
match format {
PaletteFormat::Vga => write_palette_6bit(writer, &self.colors, num_colors),
PaletteFormat::Normal => write_palette_8bit(writer, &self.colors, num_colors),
} }
} }
@ -570,7 +684,7 @@ mod tests {
assert_eq!(0, palette[1]); assert_eq!(0, palette[1]);
} }
fn assert_vga_palette(palette: &Palette) { fn assert_ega_colors(palette: &Palette) {
assert_eq!(0xff000000, palette[0]); assert_eq!(0xff000000, palette[0]);
assert_eq!(0xff0000a8, palette[1]); assert_eq!(0xff0000a8, palette[1]);
assert_eq!(0xff00a800, palette[2]); assert_eq!(0xff00a800, palette[2]);
@ -593,17 +707,17 @@ mod tests {
fn load_and_save() -> Result<(), PaletteError> { fn load_and_save() -> Result<(), PaletteError> {
let tmp_dir = TempDir::new()?; let tmp_dir = TempDir::new()?;
// vga format // vga rgb format (6-bit)
let palette = Palette::load_from_file(Path::new("./assets/vga.pal"), PaletteFormat::Vga)?; let palette = Palette::load_from_file(Path::new("./assets/vga.pal"), PaletteFormat::Vga)?;
assert_vga_palette(&palette); assert_ega_colors(&palette);
let save_path = tmp_dir.path().join("test_save_vga_format.pal"); let save_path = tmp_dir.path().join("test_save_vga_format.pal");
palette.to_file(&save_path, PaletteFormat::Vga)?; palette.to_file(&save_path, PaletteFormat::Vga)?;
let reloaded_palette = Palette::load_from_file(&save_path, PaletteFormat::Vga)?; let reloaded_palette = Palette::load_from_file(&save_path, PaletteFormat::Vga)?;
assert_eq!(palette, reloaded_palette); assert_eq!(palette, reloaded_palette);
// normal format // normal rgb format (8-bit)
let palette = let palette =
Palette::load_from_file(Path::new("./test-assets/dp2.pal"), PaletteFormat::Normal)?; Palette::load_from_file(Path::new("./test-assets/dp2.pal"), PaletteFormat::Normal)?;
@ -615,4 +729,30 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn load_and_save_arbitrary_color_count() -> Result<(), PaletteError> {
let tmp_dir = TempDir::new()?;
// vga rgb format (6-bit)
let palette = Palette::load_num_colors_from_file(Path::new("./test-assets/ega_6bit.pal"), PaletteFormat::Vga, 16)?;
assert_ega_colors(&palette);
let save_path = tmp_dir.path().join("test_save_vga_format_16_colors.pal");
palette.num_colors_to_file(&save_path, PaletteFormat::Vga, 16)?;
let reloaded_palette = Palette::load_num_colors_from_file(&save_path, PaletteFormat::Vga, 16)?;
assert_eq!(palette, reloaded_palette);
// normal rgb format (8-bit)
let palette = Palette::load_num_colors_from_file(Path::new("./test-assets/ega_8bit.pal"), PaletteFormat::Normal, 16)?;
let save_path = tmp_dir.path().join("test_save_normal_format_16_colors.pal");
palette.to_file(&save_path, PaletteFormat::Normal)?;
let reloaded_palette = Palette::load_num_colors_from_file(&save_path, PaletteFormat::Normal, 16)?;
assert_eq!(palette, reloaded_palette);
Ok(())
}
} }

Binary file not shown.

Binary file not shown.