///|
/// Alpha-mask utilities for transparent GIF composition and cleanup.
pub(all) struct MaskStats {
pixels : Int
covered : Int
partial : Int
mean_alpha : Int
bounds : GifRect?
} derive(Debug)
///|
pub fn MaskStats::pixels(self : MaskStats) -> Int {
self.pixels
}
///|
pub fn MaskStats::covered(self : MaskStats) -> Int {
self.covered
}
///|
pub fn MaskStats::partial(self : MaskStats) -> Int {
self.partial
}
///|
pub fn MaskStats::mean_alpha(self : MaskStats) -> Int {
self.mean_alpha
}
///|
pub fn MaskStats::bounds(self : MaskStats) -> GifRect? {
self.bounds
}
///|
/// Collect coverage information without reading RGB channels.
pub fn Canvas::mask_stats(self : Canvas, threshold : Byte) -> MaskStats {
let mut covered = 0
let mut partial = 0
let mut total = 0
let mut left = self.width
let mut top = self.height
let mut right = -1
let mut bottom = -1
for y in 0..= threshold.to_int() {
covered += 1
if alpha < 255 {
partial += 1
}
if x < left {
left = x
}
if y < top {
top = y
}
if x > right {
right = x
}
if y > bottom {
bottom = y
}
}
}
}
MaskStats::{
pixels: self.len(),
covered,
partial,
mean_alpha: if self.len() == 0 {
0
} else {
total / self.len()
},
bounds: if right < 0 {
None
} else {
Some(
GifRect::new(
left~,
top~,
width=right - left + 1,
height=bottom - top + 1,
),
)
},
}
}
///|
/// Build a feathered alpha mask from an input canvas.
pub fn Canvas::feather_mask(self : Canvas, radius : Int) -> Canvas {
let safe_radius = if radius < 0 { 0 } else { radius }
let result = Canvas::new(
width=self.width,
height=self.height,
fill=rgba(255, 255, 255, 0),
)
for y in 0.. ()
Some(color) => {
total += color.alpha().to_int()
count += 1
}
}
}
}
let average = if count == 0 { 0 } else { total / count }
result.pixels[x + y * self.width] = rgba(255, 255, 255, average.to_byte())
}
}
result
}
///|
/// Multiply a canvas alpha channel by a mask alpha channel.
pub fn Canvas::apply_mask(self : Canvas, mask : Canvas) -> Canvas {
let result = self.copy()
let width = if self.width < mask.width { self.width } else { mask.width }
let height = if self.height < mask.height { self.height } else { mask.height }
for y in 0.. Canvas {
let result = self.copy()
for i in 0.. Canvas {
let result = self.copy()
for i in 0..= threshold { 255 } else { 0 }
result.pixels[i] = result.pixels[i].with_alpha(output)
}
result
}
///|
/// Composite an image over a matte color using its alpha channel.
pub fn Canvas::matte(self : Canvas, color : Rgba) -> Canvas {
let result = Canvas::new(width=self.width, height=self.height, fill=color)
for i in 0.. Canvas {
let result = self.copy()
for i in 0.. Canvas {
let result = self.copy()
for i in 0.. Animation {
let result = Animation::new(width=self.width, height=self.height)
.with_loop_count(loop_count=self.loop_count)
.with_background(background=self.background)
for frame in self.frames {
ignore(result.add_frame(frame.with_canvas(frame.canvas().apply_mask(mask))))
}
result
}
///|
/// Return the visible fraction of a mask in per mille.
pub fn Canvas::coverage_per_mille(self : Canvas, threshold : Byte) -> Int {
let stats = self.mask_stats(threshold)
if stats.pixels() == 0 {
0
} else {
stats.covered() * 1000 / stats.pixels()
}
}