///|
/// Continuous display transforms for callers that choose the output raster.
/// Source coordinates use pixel centers: integer x/y values are pixel
/// centers and the image boundary is [-0.5, width - 0.5]. A crop's first
/// center is `(width - crop_width) / 2 + horizontal_offset`, with the same
/// rule vertically. The transform chain is composed before sampling, so a
/// crop/rotation/mirror sequence performs one interpolation pass.
///
/// Interpolation is performed in the input image's encoded channel-number
/// domain. It does not assume sRGB or apply a transfer curve. Alpha is
/// premultiplied for the weighted samples and the returned image is straight
/// RGBA. Coordinates outside the source pixel-center interval are extended
/// by clamping to the nearest edge center.
priv struct AvifResampleMap {
width : Double
height : Double
width_ratio : (Int64, Int64)
height_ratio : (Int64, Int64)
xx : Double
xy : Double
yx : Double
yy : Double
tx : Double
ty : Double
}
///|
fn avif_resample_finite(value : Double) -> Bool {
!value.is_nan() && !value.is_inf()
}
///|
fn avif_resample_source_shape(
width : Int,
height : Int,
length : Int,
context : String,
) -> Result[Unit, DecodeError] {
if width <= 0 || height <= 0 || width > MAX_IMAGE_PIXELS / height {
return Err(
decode_error(InvalidData, context, "Source image dimensions are invalid"),
)
}
let expected = width * height * 4
if length != expected {
return Err(
decode_error(
InvalidBuffer,
context,
"RGBA buffer size does not match source dimensions",
),
)
}
Ok(())
}
///|
fn avif_resample_output_shape(
width : Int,
height : Int,
context : String,
) -> Result[Unit, DecodeError] {
if width <= 0 || height <= 0 {
return Err(
decode_error(InvalidData, context, "Output dimensions must be positive"),
)
}
if width > MAX_IMAGE_PIXELS / height {
return Err(
decode_error(
LimitExceeded,
context,
"Output image exceeds the maximum pixel count",
),
)
}
Ok(())
}
///|
fn avif_resample_crop(
crop : AvifCleanAperture,
map : AvifResampleMap,
) -> Result[(Double, Double, Double, Double), DecodeError] {
for
value in [
crop.width_n,
crop.width_d,
crop.height_n,
crop.height_d,
crop.horizontal_d,
crop.vertical_d,
] {
if value <= 0L || value > 4294967295L {
return Err(
decode_error(
InvalidData,
"clap",
"Aperture sizes and denominators must be positive uint32 values",
),
)
}
}
if crop.horizontal_n < -2147483648L ||
crop.horizontal_n > 2147483647L ||
crop.vertical_n < -2147483648L ||
crop.vertical_n > 2147483647L {
return Err(
decode_error(
InvalidData,
"clap",
"Aperture offset numerator is outside int32",
),
)
}
let crop_width = crop.width_n.to_double() / crop.width_d.to_double()
let crop_height = crop.height_n.to_double() / crop.height_d.to_double()
let horizontal = crop.horizontal_n.to_double() / crop.horizontal_d.to_double()
let vertical = crop.vertical_n.to_double() / crop.vertical_d.to_double()
if !avif_resample_finite(crop_width) ||
!avif_resample_finite(crop_height) ||
!avif_resample_finite(horizontal) ||
!avif_resample_finite(vertical) ||
crop_width <= 0.0 ||
crop_height <= 0.0 {
return Err(
decode_error(
InvalidData,
"clap",
"Aperture rational values are not finite and positive",
),
)
}
// Bounds are a rational comparison, not a floating-point tolerance.
// Three uint32 factors can require 96 bits. Standard-library BigInt keeps
// boundary decisions exact; it is used once per transform, never per pixel.
if !avif_resample_axis_inside(
map.width_ratio,
crop.width_n,
crop.width_d,
crop.horizontal_n,
crop.horizontal_d,
) ||
!avif_resample_axis_inside(
map.height_ratio,
crop.height_n,
crop.height_d,
crop.vertical_n,
crop.vertical_d,
) {
return Err(
decode_error(
InvalidData,
"clap",
"Aperture geometry is outside the source raster",
),
)
}
let max_left = map.width - crop_width
let max_top = map.height - crop_height
let raw_left = max_left / 2.0 + horizontal
let raw_top = max_top / 2.0 + vertical
// Exact validation already proved containment. Clamp round-off at a legal
// boundary, without admitting a mathematically out-of-bounds rectangle.
let left = if raw_left < 0.0 {
0.0
} else if raw_left > max_left {
max_left
} else {
raw_left
}
let top = if raw_top < 0.0 {
0.0
} else if raw_top > max_top {
max_top
} else {
raw_top
}
Ok((left, top, crop_width, crop_height))
}
///|
fn avif_resample_axis_inside(
parent : (Int64, Int64),
size_n : Int64,
size_d : Int64,
offset_n : Int64,
offset_d : Int64,
) -> Bool {
let parent_n = @bigint.BigInt::from_int64(parent.0)
let parent_d = @bigint.BigInt::from_int64(parent.1)
let numerator = @bigint.BigInt::from_int64(size_n)
let denominator = @bigint.BigInt::from_int64(size_d)
let spare = parent_n * denominator - numerator * parent_d
if spare.compare_int(0) < 0 {
return false
}
let magnitude = if offset_n < 0L { -offset_n } else { offset_n }
let distance = @bigint.BigInt::from_int64(2L * magnitude) *
parent_d *
denominator
spare * @bigint.BigInt::from_int64(offset_d) >= distance
}
///|
fn avif_resample_map(
metadata : AvifMetadata,
width : Int,
height : Int,
) -> Result[AvifResampleMap, DecodeError] {
if width <= 0 || height <= 0 || width > MAX_IMAGE_PIXELS / height {
return Err(
decode_error(
InvalidData,
"display",
"Source image dimensions are invalid",
),
)
}
let mut map : AvifResampleMap = {
width: width.to_double(),
height: height.to_double(),
width_ratio: (width.to_int64(), 1L),
height_ratio: (height.to_int64(), 1L),
xx: 1.0,
xy: 0.0,
yx: 0.0,
yy: 1.0,
tx: 0.0,
ty: 0.0,
}
for transform in metadata.transforms {
match transform {
Crop(crop) => {
let (left, top, crop_width, crop_height) = match
avif_resample_crop(crop, map) {
Ok(value) => value
Err(error) => return Err(error)
}
map = {
..map,
width: crop_width,
height: crop_height,
width_ratio: (crop.width_n, crop.width_d),
height_ratio: (crop.height_n, crop.height_d),
tx: map.tx + map.xx * left + map.xy * top,
ty: map.ty + map.yx * left + map.yy * top,
}
}
Rotate(turns) => {
if turns < 0 || turns > 3 {
return Err(
decode_error(InvalidData, "irot", "Rotation must be in 0..3"),
)
}
for _ in 0..
if mode == 0 {
map = {
..map,
xy: -map.xy,
yy: -map.yy,
tx: map.tx + map.xy * (map.height - 1.0),
ty: map.ty + map.yy * (map.height - 1.0),
}
} else if mode == 1 {
map = {
..map,
xx: -map.xx,
yx: -map.yx,
tx: map.tx + map.xx * (map.width - 1.0),
ty: map.ty + map.yx * (map.width - 1.0),
}
} else {
return Err(
decode_error(InvalidData, "imir", "Mirror mode must be 0 or 1"),
)
}
}
if !avif_resample_finite(map.width) ||
!avif_resample_finite(map.height) ||
!avif_resample_finite(map.xx) ||
!avif_resample_finite(map.xy) ||
!avif_resample_finite(map.yx) ||
!avif_resample_finite(map.yy) ||
!avif_resample_finite(map.tx) ||
!avif_resample_finite(map.ty) {
return Err(
decode_error(
InvalidData,
"display",
"Display transform produced non-finite geometry",
),
)
}
}
Ok(map)
}
///|
fn avif_resample_axis(value : Double, limit : Int) -> (Int, Int, Double) {
let maximum = (limit - 1).to_double()
let clamped = if value <= 0.0 {
0.0
} else if value >= maximum {
maximum
} else {
value
}
let left = clamped.to_int()
let right = if left + 1 < limit { left + 1 } else { left }
(left, right, clamped - left.to_double())
}
///|
fn avif_resample_u8(value : Double) -> Byte {
if value <= 0.0 {
b'\x00'
} else if value >= 255.0 {
b'\xFF'
} else {
(value + 0.5).to_int().to_byte()
}
}
///|
fn avif_resample_u16(value : Double) -> UInt16 {
if value <= 0.0 {
(0 : UInt16)
} else if value >= 65535.0 {
(65535 : UInt16)
} else {
(value + 0.5).to_int().to_uint16()
}
}
///|
fn avif_resample_bilinear8(
data : FixedArray[Byte],
width : Int,
height : Int,
source_x : Double,
source_y : Double,
) -> (Byte, Byte, Byte, Byte) {
let (x0, x1, fx) = avif_resample_axis(source_x, width)
let (y0, y1, fy) = avif_resample_axis(source_y, height)
let wx0 = 1.0 - fx
let wx1 = fx
let wy0 = 1.0 - fy
let wy1 = fy
let mut premult_r = 0.0
let mut premult_g = 0.0
let mut premult_b = 0.0
let mut alpha = 0.0
for tap in 0..<4 {
let x = if (tap & 1) == 0 { x0 } else { x1 }
let y = if tap < 2 { y0 } else { y1 }
let wx = if (tap & 1) == 0 { wx0 } else { wx1 }
let wy = if tap < 2 { wy0 } else { wy1 }
let weight = wx * wy
let at = (y * width + x) * 4
let coverage = data[at + 3].to_int().to_double() / 255.0
alpha += weight * coverage
premult_r += weight * data[at].to_int().to_double() * coverage
premult_g += weight * data[at + 1].to_int().to_double() * coverage
premult_b += weight * data[at + 2].to_int().to_double() * coverage
}
if alpha <= 0.0 {
(b'\x00', b'\x00', b'\x00', b'\x00')
} else {
(
avif_resample_u8(premult_r / alpha),
avif_resample_u8(premult_g / alpha),
avif_resample_u8(premult_b / alpha),
avif_resample_u8(alpha * 255.0),
)
}
}
///|
fn avif_resample_bilinear16(
data : FixedArray[UInt16],
width : Int,
height : Int,
source_x : Double,
source_y : Double,
) -> (UInt16, UInt16, UInt16, UInt16) {
let (x0, x1, fx) = avif_resample_axis(source_x, width)
let (y0, y1, fy) = avif_resample_axis(source_y, height)
let wx0 = 1.0 - fx
let wx1 = fx
let wy0 = 1.0 - fy
let wy1 = fy
let mut premult_r = 0.0
let mut premult_g = 0.0
let mut premult_b = 0.0
let mut alpha = 0.0
for tap in 0..<4 {
let x = if (tap & 1) == 0 { x0 } else { x1 }
let y = if tap < 2 { y0 } else { y1 }
let wx = if (tap & 1) == 0 { wx0 } else { wx1 }
let wy = if tap < 2 { wy0 } else { wy1 }
let weight = wx * wy
let at = (y * width + x) * 4
let coverage = data[at + 3].to_int().to_double() / 65535.0
alpha += weight * coverage
premult_r += weight * data[at].to_int().to_double() * coverage
premult_g += weight * data[at + 1].to_int().to_double() * coverage
premult_b += weight * data[at + 2].to_int().to_double() * coverage
}
if alpha <= 0.0 {
((0 : UInt16), (0 : UInt16), (0 : UInt16), (0 : UInt16))
} else {
(
avif_resample_u16(premult_r / alpha),
avif_resample_u16(premult_g / alpha),
avif_resample_u16(premult_b / alpha),
avif_resample_u16(alpha * 65535.0),
)
}
}
///|
fn avif_resample_source_point(
map : AvifResampleMap,
x : Int,
y : Int,
output_width : Int,
output_height : Int,
) -> (Double, Double) {
let logical_x = (x.to_double() + 0.5) * map.width / output_width.to_double() -
0.5
let logical_y = (y.to_double() + 0.5) * map.height / output_height.to_double() -
0.5
(
map.xx * logical_x + map.xy * logical_y + map.tx,
map.yx * logical_x + map.yy * logical_y + map.ty,
)
}
///|
/// Fractional crop/resize/display transform for straight RGBA8.
///
/// The output owns a fresh buffer. If the transform chain is an exact
/// integer crop/permutation and the requested dimensions match it, this uses
/// the exact display path so identity and rotation/mirror cases retain their
/// byte-for-byte behavior.
pub fn AvifMetadata::resample_rgba8(
self : AvifMetadata,
image : Image,
width : Int,
height : Int,
) -> Result[Image, DecodeError] {
match
avif_resample_source_shape(
image.width,
image.height,
image.data.length(),
"resample",
) {
Err(error) => return Err(error)
Ok(_) => ()
}
match avif_resample_output_shape(width, height, "resample") {
Err(error) => return Err(error)
Ok(_) => ()
}
match avif_display_map(self, image.width, image.height) {
Ok(exact) if exact.width == width && exact.height == height =>
return self.apply_rgba8(image)
_ => ()
}
let map = match avif_resample_map(self, image.width, image.height) {
Ok(value) => value
Err(error) => return Err(error)
}
let output = FixedArray::make(width * height * 4, b'\x00')
for i in 0..<(width * height) {
let x = i % width
let y = i / width
let (source_x, source_y) = avif_resample_source_point(
map, x, y, width, height,
)
let (r, g, b, a) = avif_resample_bilinear8(
image.data,
image.width,
image.height,
source_x,
source_y,
)
output[i * 4] = r
output[i * 4 + 1] = g
output[i * 4 + 2] = b
output[i * 4 + 3] = a
}
Ok({ width, height, data: output, })
}
///|
/// Fractional crop/resize/display transform for straight RGBA16.
pub fn AvifMetadata::resample_rgba16(
self : AvifMetadata,
image : Image16,
width : Int,
height : Int,
) -> Result[Image16, DecodeError] {
match
avif_resample_source_shape(
image.width,
image.height,
image.data.length(),
"resample",
) {
Err(error) => return Err(error)
Ok(_) => ()
}
match avif_resample_output_shape(width, height, "resample") {
Err(error) => return Err(error)
Ok(_) => ()
}
match avif_display_map(self, image.width, image.height) {
Ok(exact) if exact.width == width && exact.height == height =>
return self.apply_rgba16(image)
_ => ()
}
let map = match avif_resample_map(self, image.width, image.height) {
Ok(value) => value
Err(error) => return Err(error)
}
let output = FixedArray::make(width * height * 4, (0 : UInt16))
for i in 0..<(width * height) {
let x = i % width
let y = i / width
let (source_x, source_y) = avif_resample_source_point(
map, x, y, width, height,
)
let (r, g, b, a) = avif_resample_bilinear16(
image.data,
image.width,
image.height,
source_x,
source_y,
)
output[i * 4] = r
output[i * 4 + 1] = g
output[i * 4 + 2] = b
output[i * 4 + 3] = a
}
Ok({ width, height, data: output, })
}