-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcolorspace.mbt
More file actions
117 lines (111 loc) · 3.2 KB
/
Copy pathcolorspace.mbt
File metadata and controls
117 lines (111 loc) · 3.2 KB
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
///|
/// Wraps a hue angle into `[0, 360)`.
fn wrap_hue(h : Double) -> Double {
h - 360.0 * ifloor(h / 360.0).to_double()
}
///|
/// Converts 8-bit RGB to HSV: hue in degrees `[0, 360)`, saturation and value
/// in `[0, 1]`.
pub fn rgb_to_hsv(r : Int, g : Int, b : Int) -> (Double, Double, Double) {
let rf = r.to_double() / 255.0
let gf = g.to_double() / 255.0
let bf = b.to_double() / 255.0
let mut mx = rf
if gf > mx {
mx = gf
}
if bf > mx {
mx = bf
}
let mut mn = rf
if gf < mn {
mn = gf
}
if bf < mn {
mn = bf
}
let d = mx - mn
let h = if d == 0.0 {
0.0
} else if mx == rf {
wrap_hue(60.0 * (gf - bf) / d)
} else if mx == gf {
60.0 * ((bf - rf) / d + 2.0)
} else {
60.0 * ((rf - gf) / d + 4.0)
}
let s = if mx == 0.0 { 0.0 } else { d / mx }
(h, s, mx)
}
///|
/// Converts HSV (hue in degrees, saturation/value in `[0, 1]`) back to 8-bit
/// RGB using the standard sector decomposition.
pub fn hsv_to_rgb(h : Double, s : Double, v : Double) -> (Int, Int, Int) {
let hh = wrap_hue(h) / 60.0
let c = v * s
let mod2 = hh - 2.0 * ifloor(hh / 2.0).to_double()
let diff = if mod2 - 1.0 < 0.0 { 1.0 - mod2 } else { mod2 - 1.0 }
let x = c * (1.0 - diff)
let sector = ifloor(hh)
let (r1, g1, b1) = match sector {
0 => (c, x, 0.0)
1 => (x, c, 0.0)
2 => (0.0, c, x)
3 => (0.0, x, c)
4 => (x, 0.0, c)
_ => (c, 0.0, x)
}
let m = v - c
(
((r1 + m) * 255.0 + 0.5).to_int(),
((g1 + m) * 255.0 + 0.5).to_int(),
((b1 + m) * 255.0 + 0.5).to_int(),
)
}
///|
/// Converts 8-bit RGB to BT.601 full-range YCbCr (all components 0..255).
pub fn rgb_to_ycbcr(r : Int, g : Int, b : Int) -> (Int, Int, Int) {
let rf = r.to_double()
let gf = g.to_double()
let bf = b.to_double()
let y = 0.299 * rf + 0.587 * gf + 0.114 * bf
let cb = 128.0 - 0.168736 * rf - 0.331264 * gf + 0.5 * bf
let cr = 128.0 + 0.5 * rf - 0.418688 * gf - 0.081312 * bf
(
clamp_byte((y + 0.5).to_int()).to_int(),
clamp_byte((cb + 0.5).to_int()).to_int(),
clamp_byte((cr + 0.5).to_int()).to_int(),
)
}
///|
/// Converts BT.601 full-range YCbCr back to 8-bit RGB.
pub fn ycbcr_to_rgb(y : Int, cb : Int, cr : Int) -> (Int, Int, Int) {
let yf = y.to_double()
let cbf = cb.to_double() - 128.0
let crf = cr.to_double() - 128.0
(
clamp_byte((yf + 1.402 * crf + 0.5).to_int()).to_int(),
clamp_byte((yf - 0.344136 * cbf - 0.714136 * crf + 0.5).to_int()).to_int(),
clamp_byte((yf + 1.772 * cbf + 0.5).to_int()).to_int(),
)
}
///|
/// Scales saturation in HSV space: `factor > 1` makes colors more vivid,
/// `0` fully desaturates (equivalent brightness-preserving grayscale in V).
pub fn Image::saturate(self : Image, factor : Double) -> Image {
let f = if factor < 0.0 { 0.0 } else { factor }
self.map_rgb(fn(r, g, b) {
let (h, s, v) = rgb_to_hsv(r, g, b)
let ns = if s * f > 1.0 { 1.0 } else { s * f }
hsv_to_rgb(h, ns, v)
})
}
///|
/// Rotates every pixel's hue by `degrees` around the HSV color wheel while
/// keeping saturation and value untouched.
pub fn Image::hue_rotate(self : Image, degrees : Double) -> Image {
self.map_rgb(fn(r, g, b) {
let (h, s, v) = rgb_to_hsv(r, g, b)
hsv_to_rgb(h + degrees, s, v)
})
}