Skip to content

Commit

Permalink
Introduce Color Struct (#7)
Browse files Browse the repository at this point in the history
* Add Color struct

* Update u16 unpacking to hardware equivalent
  • Loading branch information
dcvz authored Jul 11, 2023
1 parent dccb3c1 commit 6fb4bd0
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 33 deletions.
76 changes: 76 additions & 0 deletions src/color.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/// RGBA color type where values are 8-bit integers (0-255).
///
/// This is not to be used as a generic color type, only for specific pigment interfaces.
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}

impl Color {
#[rustfmt::skip]
pub const TRANSPARENT: Color = Color { r: 0, g: 0, b: 0, a: 0 };
#[rustfmt::skip]
pub const BLACK: Color = Color { r: 0, g: 0, b: 0, a: 255 };
#[rustfmt::skip]
pub const WHITE: Color = Color { r: 255, g: 255, b: 255, a: 255 };

/// Creates a new color with the given RGBA values.
#[inline]
#[allow(non_snake_case)]
pub const fn RGBA(r: u8, g: u8, b: u8, a: u8) -> Color {
Color { r, g, b, a }
}

/// Creates a new color with the given RGB values and an alpha value of 255.
#[inline]
#[allow(non_snake_case)]
pub const fn RGB(r: u8, g: u8, b: u8) -> Color {
Color { r, g, b, a: 0xFF }
}

/// Converts a 16-bit RGBA pixel to a 32-bit RGBA color.
#[inline]
pub fn from_u16(pixel: u16) -> Color {
let r = ((pixel >> 11) & 0x1F) as u8;
let g = ((pixel >> 6) & 0x1F) as u8;
let b = ((pixel >> 1) & 0x1F) as u8;
let a = (pixel & 0x01) as u8;

let r = (r << 3) | (r >> 2);
let g = (g << 3) | (g >> 2);
let b = (b << 3) | (b >> 2);
let a = 255 * a;

Color { r, g, b, a }
}

/// Converts a 32-bit RGBA color to a 16-bit RGBA pixel.
#[inline]
pub fn to_u16(&self) -> u16 {
let r = (self.r / 8) as u16;
let g = (self.g / 8) as u16;
let b = (self.b / 8) as u16;
let a = (self.a / 255) as u16;

(r << 11) | (g << 6) | (b << 1) | a
}

/// Converts a 32-bit RGBA color to a 16-bit RGBA pixel and
/// returns the two 8-bit components.
#[inline]
pub fn rgba16(self) -> (u8, u8) {
let pixel = self.to_u16();
((pixel >> 8) as u8, (pixel & 0xFF) as u8)
}

/// Converts the rgb components to a single intensity value.
/// This is used for grayscale images.
#[inline]
pub fn rgb_to_intensity(&self) -> u8 {
(self.r as f32 * 0.2126 + self.g as f32 * 0.7152 + 0.0722 * self.b as f32) as u8
}
}
65 changes: 32 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,14 @@
use crate::color::Color;
use png::{BitDepth, ColorType};
use std::io::prelude::*;

// TODO: make this an option or sompthin, also ask clover
#[inline]
fn rgb_to_intensity(r: u8, g: u8, b: u8) -> u8 {
(r as f32 * 0.2126 + g as f32 * 0.7152 + 0.0722 * b as f32) as u8
}
pub mod color;

#[inline]
fn u8_to_u4(x: u8) -> u8 {
x >> 4
}

#[inline]
// convert rgba8888 to rgba16
fn pack_color(r: u8, g: u8, b: u8, a: u8) -> (u8, u8) {
let r = (r >> 3) as u16;
let g = (g >> 3) as u16;
let b = (b >> 3) as u16;
let a = (a > 127) as u16;

let s = (r << 11) | (g << 6) | (b << 1) | a;

((s >> 8) as u8, s as u8)
}

pub struct Image {
data: Vec<u8>,
color_type: ColorType,
Expand Down Expand Up @@ -171,13 +155,13 @@ impl Image {
*/

pub fn as_ci8(&self) -> Vec<u8> {
assert!(self.bit_depth == png::BitDepth::Eight);
assert!(self.color_type == png::ColorType::Indexed);
assert_eq!(self.bit_depth, BitDepth::Eight);
assert_eq!(self.color_type, ColorType::Indexed);
self.data.to_vec()
}

pub fn as_ci4(&self) -> Vec<u8> {
assert!(self.color_type == png::ColorType::Indexed);
assert_eq!(self.color_type, ColorType::Indexed);

match self.bit_depth {
BitDepth::Four => self.data.to_vec(),
Expand All @@ -202,17 +186,23 @@ impl Image {
.data
.chunks_exact(8)
.map(|chunk| {
let i1 = rgb_to_intensity(chunk[0], chunk[1], chunk[2]);
let i2 = rgb_to_intensity(chunk[4], chunk[5], chunk[6]);
let c1 = Color::RGBA(chunk[0], chunk[1], chunk[2], chunk[3]);
let i1 = c1.rgb_to_intensity();
let c2 = Color::RGBA(chunk[4], chunk[5], chunk[6], chunk[7]);
let i2 = c2.rgb_to_intensity();

u8_to_u4(i1) << 4 | u8_to_u4(i2)
})
.collect(),
(ColorType::Rgb, BitDepth::Eight) => self
.data
.chunks_exact(6)
.map(|chunk| {
let i1 = rgb_to_intensity(chunk[0], chunk[1], chunk[2]);
let i2 = rgb_to_intensity(chunk[3], chunk[4], chunk[5]);
let c1 = Color::RGB(chunk[0], chunk[1], chunk[2]);
let i1 = c1.rgb_to_intensity();
let c2 = Color::RGB(chunk[3], chunk[4], chunk[5]);
let i2 = c2.rgb_to_intensity();

u8_to_u4(i1) << 4 | u8_to_u4(i2)
})
.collect(),
Expand All @@ -231,12 +221,18 @@ impl Image {
(ColorType::Rgba, BitDepth::Eight) => self
.data
.chunks_exact(4)
.map(|chunk| rgb_to_intensity(chunk[0], chunk[1], chunk[2]))
.map(|chunk| {
let c = Color::RGBA(chunk[0], chunk[1], chunk[2], chunk[3]);
c.rgb_to_intensity()
})
.collect(),
(ColorType::Rgb, BitDepth::Eight) => self
.data
.chunks_exact(3)
.map(|chunk| rgb_to_intensity(chunk[0], chunk[1], chunk[2]))
.map(|chunk| {
let c = Color::RGB(chunk[0], chunk[1], chunk[2]);
c.rgb_to_intensity()
})
.collect(),
p => panic!("unsupported format {:?}", p),
}
Expand Down Expand Up @@ -287,8 +283,9 @@ impl Image {
.data
.chunks_exact(4)
.flat_map(|chunk| {
let (first, second) = pack_color(chunk[0], chunk[1], chunk[2], chunk[3]);
[first, second].into_iter()
let color = Color::RGBA(chunk[0], chunk[1], chunk[2], chunk[3]);
let (high, low) = color.rgba16();
vec![high, low]
})
.collect(),
p => panic!("unsupported format {:?}", p),
Expand All @@ -315,17 +312,19 @@ pub fn get_palette_rgba16<R: Read>(r: R) -> Vec<u8> {
.chunks_exact(3)
.zip(alpha_data.iter())
.flat_map(|(rgb, &alpha)| {
let (first, second) = pack_color(rgb[0], rgb[1], rgb[2], alpha);
[first, second].into_iter()
let color = Color::RGBA(rgb[0], rgb[1], rgb[2], alpha);
let (high, low) = color.rgba16();
vec![high, low]
})
.collect(),

// If there's no alpha channel, assume everything is opaque
None => rgb_data
.chunks_exact(3)
.flat_map(|rgb| {
let (first, second) = pack_color(rgb[0], rgb[1], rgb[2], 0xFF);
[first, second].into_iter()
let color = Color::RGB(rgb[0], rgb[1], rgb[2]);
let (high, low) = color.rgba16();
vec![high, low]
})
.collect(),
}
Expand Down
95 changes: 95 additions & 0 deletions tests/color.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use pigment::color::Color;

#[test]
fn test_color_new() {
// Test case 1: Color with alpha component
let color = Color::RGBA(255, 128, 64, 255);
assert_eq!(color.r, 255);
assert_eq!(color.g, 128);
assert_eq!(color.b, 64);
assert_eq!(color.a, 255);

// Test case 2: Color without alpha component
let color = Color::RGB(255, 128, 64);
assert_eq!(color.r, 255);
assert_eq!(color.g, 128);
assert_eq!(color.b, 64);
assert_eq!(color.a, 255);
}

#[test]
fn test_color_from_u16() {
// Test case 1: Pixel value with maximum component values
let pixel: u16 = 0xFFFF; // Binary: 1111111111111111
let color = Color::WHITE;
assert_eq!(Color::from_u16(pixel), color);

// Test case 2: Pixel value with minimum component values
let pixel: u16 = 0x0000; // Binary: 0000000000000000
let color = Color::TRANSPARENT;
assert_eq!(Color::from_u16(pixel), color);

// Test case 3: Random pixel value
let pixel: u16 = 0b1101010101010101; // Binary: 11010 10101 01010 101
let color = Color::RGBA(214, 173, 82, 255);
assert_eq!(Color::from_u16(pixel), color);
}

#[test]
fn test_color_to_u16() {
// Test case 1: Pixel value with maximum component values
let color = Color::WHITE;
let pixel = color.to_u16();
assert_eq!(pixel, 0xFFFF);

// Test case 2: Pixel value with minimum component values
let color = Color::BLACK;
let pixel = color.to_u16();
assert_eq!(pixel, 0x0001);

// Test case 3: Random pixel value
let color = Color::RGBA(213, 172, 82, 255);
let pixel = color.to_u16();
assert_eq!(pixel, 0b1101010101010101);
}

#[test]
fn test_color_rba16() {
// Test case 1: Color value with maximum component values
let color = Color::WHITE;
let pixel = color.rgba16();
assert_eq!(pixel, (0xFF, 0xFF));

// Test case 2: Color value with minimum component values
let color = Color::BLACK;
let pixel = color.rgba16();
assert_eq!(pixel, (0, 1));

// Test case 3: Random color values
let color = Color::RGBA(120, 200, 50, 150);
let pixel = color.rgba16();
assert_eq!(pixel, (126, 76));
}

#[test]
fn test_color_rgb_to_intensity() {
// Test case 1: Pixel value with maximum component values
let color = Color::WHITE;
let intensity = color.rgb_to_intensity();
assert_eq!(intensity, 255);

// Test case 2: Pixel value with minimum component values
let color = Color::BLACK;
let intensity = color.rgb_to_intensity();
assert_eq!(intensity, 0);

// Test case 3: RGB values with equal intensity
let color = Color::RGBA(128, 128, 128, 255);
let intensity = color.rgb_to_intensity();
assert_eq!(intensity, 128);

// Test case 4: RGB values with different intensities
let color = Color::RGBA(255, 128, 64, 255);
let intensity = color.rgb_to_intensity();
assert_eq!(intensity, 150);
}

0 comments on commit 6fb4bd0

Please sign in to comment.