///|
/// Represents an 8-bit RGB color.
pub(all) struct ColorRGB {
r : Int
g : Int
b : Int
} derive(Debug, Eq, Default)
///|
/// Creates a new ColorRGB.
pub fn ColorRGB::new(r : Int, g : Int, b : Int) -> ColorRGB {
{
r: if r < 0 {
0
} else if r > 255 {
255
} else {
r
},
g: if g < 0 {
0
} else if g > 255 {
255
} else {
g
},
b: if b < 0 {
0
} else if b > 255 {
255
} else {
b
},
}
}
///|
/// Converts RGB to 8-bit grayscale intensity using standard ITU-R BT.601 weights.
pub fn ColorRGB::to_grayscale(self : ColorRGB) -> Int {
let gray = 0.299 * self.r.to_double() +
0.587 * self.g.to_double() +
0.114 * self.b.to_double()
gray.round().to_int()
}
///|
/// Represents HSV color with H in [0, 360), S in [0, 1], V in [0, 1].
pub(all) struct ColorHSV {
h : Double
s : Double
v : Double
} derive(Debug, Eq, Default)
///|
/// Creates a new ColorHSV.
pub fn ColorHSV::new(h : Double, s : Double, v : Double) -> ColorHSV {
{ h, s, v }
}
///|
/// Converts RGB to HSV.
pub fn ColorRGB::to_hsv(self : ColorRGB) -> ColorHSV {
let r = self.r.to_double() / 255.0
let g = self.g.to_double() / 255.0
let b = self.b.to_double() / 255.0
let max_val = if r > g {
if r > b {
r
} else {
b
}
} else if g > b {
g
} else {
b
}
let min_val = if r < g {
if r < b {
r
} else {
b
}
} else if g < b {
g
} else {
b
}
let delta = max_val - min_val
let mut h = 0.0
if delta > 1.0e-12 {
if max_val == r {
h = 60.0 * ((g - b) / delta % 6.0)
} else if max_val == g {
h = 60.0 * ((b - r) / delta + 2.0)
} else {
h = 60.0 * ((r - g) / delta + 4.0)
}
if h < 0.0 {
h += 360.0
}
}
let s = if max_val > 1.0e-12 { delta / max_val } else { 0.0 }
let v = max_val
{ h, s, v }
}
///|
/// Converts HSV to RGB.
pub fn ColorHSV::to_rgb(self : ColorHSV) -> ColorRGB {
let c = self.v * self.s
let x = c * (1.0 - (self.h / 60.0 % 2.0 - 1.0).abs())
let m = self.v - c
let (r1, g1, b1) = if self.h < 60.0 {
(c, x, 0.0)
} else if self.h < 120.0 {
(x, c, 0.0)
} else if self.h < 180.0 {
(0.0, c, x)
} else if self.h < 240.0 {
(0.0, x, c)
} else if self.h < 300.0 {
(x, 0.0, c)
} else {
(c, 0.0, x)
}
ColorRGB::new(
((r1 + m) * 255.0).round().to_int(),
((g1 + m) * 255.0).round().to_int(),
((b1 + m) * 255.0).round().to_int(),
)
}
///|
/// Represents a 24-bit RGB Color Image.
pub(all) struct ColorImage {
width : Int
height : Int
data : Array[ColorRGB]
} derive(Debug, Eq)
///|
/// Creates a new ColorImage.
pub fn ColorImage::new(width : Int, height : Int) -> ColorImage {
let size = width * height
let data = Array::make(size, ColorRGB::new(0, 0, 0))
{ width, height, data }
}
///|
/// Gets RGB pixel at (x, y).
pub fn ColorImage::get(self : ColorImage, x : Int, y : Int) -> ColorRGB {
if x < 0 || x >= self.width || y < 0 || y >= self.height {
ColorRGB::new(0, 0, 0)
} else {
self.data[y * self.width + x]
}
}
///|
/// Sets RGB pixel at (x, y).
pub fn ColorImage::set(
self : ColorImage,
x : Int,
y : Int,
color : ColorRGB,
) -> Unit {
if x >= 0 && x < self.width && y >= 0 && y < self.height {
self.data[y * self.width + x] = color
}
}
///|
/// Converts 24-bit ColorImage to 8-bit grayscale Image.
pub fn ColorImage::to_grayscale(self : ColorImage) -> Image {
let gray = Image::new(self.width, self.height)
for y in 0.. ColorImage {
let col = ColorImage::new(self.width, self.height)
for y in 0.. Image {
let w = img.width
let h = img.height
let total = w * h
if total == 0 {
return img.clone()
}
// Compute histogram
let hist = Array::make(256, 0)
for y in 0.. 0 {
cdf_min = cdf[i]
break
}
}
let denom = (total - cdf_min).to_double()
let lut = Array::make(256, 0)
if denom > 1.0e-12 {
for i in 0..<256 {
let val = ((cdf[i] - cdf_min).to_double() / denom * 255.0)
.round()
.to_int()
lut[i] = if val < 0 { 0 } else if val > 255 { 255 } else { val }
}
}
let res = Image::new(w, h)
for y in 0.. Image {
let w = img.width
let h = img.height
if w < 16 || h < 16 {
return equalize_histogram(img)
}
let tile_w = w / grid_cols
let tile_h = h / grid_rows
let num_tiles = grid_cols * grid_rows
let tile_size = tile_w * tile_h
let clip_val = (clip_limit * (tile_size.to_double() / 256.0)).round().to_int()
// Compute clipped CDF for each tile
let tile_cdfs = Array::make(num_tiles * 256, 0.0)
for ty in 0.. clip_val {
excess += hist[i] - clip_val
hist[i] = clip_val
}
}
let bonus = excess / 256
for i in 0..<256 {
hist[i] += bonus
}
// Build CDF
let tile_idx = (ty * grid_cols + tx) * 256
let mut acc = 0.0
for i in 0..<256 {
acc += hist[i].to_double()
tile_cdfs[tile_idx + i] = acc / actual_tile_pixels.to_double() * 255.0
}
}
}
// Bilinear interpolation across tiles
let res = Image::new(w, h)
for y in 0.. Int {
if tx < 0 {
0
} else if tx >= grid_cols {
grid_cols - 1
} else {
tx
}
}
let clamp_ty = fn(ty : Int) -> Int {
if ty < 0 {
0
} else if ty >= grid_rows {
grid_rows - 1
} else {
ty
}
}
let p = img.get(x, y)
let c00 = tile_cdfs[(clamp_ty(ty0) * grid_cols + clamp_tx(tx0)) * 256 + p]
let c10 = tile_cdfs[(clamp_ty(ty0) * grid_cols + clamp_tx(tx1)) * 256 + p]
let c01 = tile_cdfs[(clamp_ty(ty1) * grid_cols + clamp_tx(tx0)) * 256 + p]
let c11 = tile_cdfs[(clamp_ty(ty1) * grid_cols + clamp_tx(tx1)) * 256 + p]
let top = c00 + fx * (c10 - c00)
let bot = c01 + fx * (c11 - c01)
let val = (top + fy * (bot - top)).round().to_int()
let pixel = if val < 0 { 0 } else if val > 255 { 255 } else { val }
res.set(x, y, pixel)
}
}
res
}