// Image construction, sampling, and deterministic synthetic transforms.
///|
fn clamp_int(value : Int, low : Int, high : Int) -> Int {
if high < low {
low
} else if value < low {
low
} else if value > high {
high
} else {
value
}
}
///|
fn reflect_index(index : Int, size : Int) -> Int {
if size <= 1 {
0
} else {
let mut value = index
let period = (size - 1) * 2
while value < 0 {
value = value + period
}
value = value % period
if value >= size {
period - value
} else {
value
}
}
}
///|
fn sample_index(img : GrayImage, x : Int, y : Int, border : BorderMode) -> Int {
if img.width <= 0 || img.height <= 0 {
match border {
Constant(value) => value
_ => 0
}
} else if x >= 0 && x < img.width && y >= 0 && y < img.height {
img.get(x, y)
} else {
match border {
Constant(value) => value
Clamp =>
img.get(clamp_int(x, 0, img.width - 1), clamp_int(y, 0, img.height - 1))
Reflect =>
img.get(reflect_index(x, img.width), reflect_index(y, img.height))
}
}
}
///|
pub fn GrayImage::clone(self : GrayImage) -> GrayImage {
let result = GrayImage::new(self.width, self.height)
let mut i = 0
while i < self.data.length() {
result.data[i] = self.data[i]
i = i + 1
}
result
}
///|
pub fn GrayImage::fill(self : GrayImage, value : Int) -> Unit {
let clamped = if value < 0 { 0 } else if value > 255 { 255 } else { value }
let mut i = 0
while i < self.data.length() {
self.data[i] = clamped
i = i + 1
}
}
///|
pub fn GrayImage::crop(self : GrayImage, rect : Rect) -> GrayImage {
let left = clamp_int(rect.x0, 0, self.width)
let top = clamp_int(rect.y0, 0, self.height)
let right = clamp_int(rect.x1, left, self.width)
let bottom = clamp_int(rect.y1, top, self.height)
let result = GrayImage::new(right - left, bottom - top)
let mut y = top
while y < bottom {
let mut x = left
while x < right {
result.set(x - left, y - top, self.get(x, y))
x = x + 1
}
y = y + 1
}
result
}
///|
pub fn GrayImage::histogram(self : GrayImage) -> Array[Int] {
let histogram = Array::make(256, 0)
let mut i = 0
while i < self.data.length() {
let value = clamp_int(self.data[i], 0, 255)
histogram[value] = histogram[value] + 1
i = i + 1
}
histogram
}
///|
pub fn GrayImage::normalize(self : GrayImage) -> GrayImage {
let result = GrayImage::new(self.width, self.height)
if self.data.length() == 0 {
return result
}
let mut low = 255
let mut high = 0
let mut i = 0
while i < self.data.length() {
let value = clamp_int(self.data[i], 0, 255)
if value < low {
low = value
}
if value > high {
high = value
}
i = i + 1
}
if low == high {
return result
}
let span = high - low
i = 0
while i < self.data.length() {
let value = clamp_int(self.data[i], 0, 255)
let mapped = ((value - low) * 255 + span / 2) / span
result.data[i] = clamp_int(mapped, 0, 255)
i = i + 1
}
result
}
///|
pub fn GrayImage::resize_nearest(
self : GrayImage,
width : Int,
height : Int,
) -> GrayImage {
let result = GrayImage::new(width, height)
if result.width == 0 ||
result.height == 0 ||
self.width == 0 ||
self.height == 0 {
return result
}
let mut y = 0
while y < result.height {
let source_y = y * self.height / result.height
let mut x = 0
while x < result.width {
let source_x = x * self.width / result.width
result.set(x, y, self.get(source_x, source_y))
x = x + 1
}
y = y + 1
}
result
}
///|
pub fn GrayImage::translate(
self : GrayImage,
dx : Int,
dy : Int,
border : BorderMode,
) -> GrayImage {
let result = GrayImage::new(self.width, self.height)
let mut y = 0
while y < self.height {
let mut x = 0
while x < self.width {
result.set(x, y, sample_index(self, x - dx, y - dy, border))
x = x + 1
}
y = y + 1
}
result
}
///|
pub fn GrayImage::sample(
self : GrayImage,
x : Int,
y : Int,
border : BorderMode,
) -> Int {
sample_index(self, x, y, border)
}
///|
fn floor_int(value : Double) -> Int {
let truncated = value.to_int()
if Double::from_int(truncated) > value {
truncated - 1
} else {
truncated
}
}
///|
fn approximate_sin_cos(angle : Double) -> (Double, Double) {
let pi = 3.141592653589793
let two_pi = 6.283185307179586
let mut a = angle
while a > pi {
a = a - two_pi
}
while a < -pi {
a = a + two_pi
}
let a2 = a * a
let sin_a = a - a2 * a / 6.0 + a2 * a2 * a / 120.0 - a2 * a2 * a2 / 5040.0
let cos_a = 1.0 - a2 / 2.0 + a2 * a2 / 24.0 - a2 * a2 * a2 / 720.0
(sin_a, cos_a)
}
///|
pub fn GrayImage::sample_bilinear(
self : GrayImage,
x : Double,
y : Double,
border : BorderMode,
) -> Double {
let x0 = floor_int(x)
let y0 = floor_int(y)
let x1 = x0 + 1
let y1 = y0 + 1
let fx = x - Double::from_int(x0)
let fy = y - Double::from_int(y0)
let p00 = Double::from_int(sample_index(self, x0, y0, border))
let p10 = Double::from_int(sample_index(self, x1, y0, border))
let p01 = Double::from_int(sample_index(self, x0, y1, border))
let p11 = Double::from_int(sample_index(self, x1, y1, border))
let top = p00 + (p10 - p00) * fx
let bottom = p01 + (p11 - p01) * fx
top + (bottom - top) * fy
}
///|
pub fn GrayImage::rotate_about(
self : GrayImage,
angle : Double,
center : FloatPoint2,
border : BorderMode,
) -> GrayImage {
let result = GrayImage::new(self.width, self.height)
let (sin_a, cos_a) = approximate_sin_cos(angle)
let mut y = 0
while y < self.height {
let mut x = 0
while x < self.width {
let dx = Double::from_int(x) - center.x
let dy = Double::from_int(y) - center.y
let source_x = (cos_a * dx + sin_a * dy + center.x + 0.5).to_int()
let source_y = (-sin_a * dx + cos_a * dy + center.y + 0.5).to_int()
result.set(x, y, sample_index(self, source_x, source_y, border))
x = x + 1
}
y = y + 1
}
result
}