// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Bitmap helpers used by the renderer.
///
/// Ported from upstream `swash/src/scale/bitmap/mod.rs`.
///|
/// Alpha blends an 8-bit mask tinted with `color` into a target RGBA buffer.
pub fn blit(
mask : Array[Byte],
mask_width : UInt,
mask_height : UInt,
x : Int,
y : Int,
color : Array[Byte],
target : Array[Byte],
target_width : UInt,
target_height : UInt,
) -> Unit {
if mask_width == 0U ||
mask_height == 0U ||
target_width == 0U ||
target_height == 0U {
return
}
if color.length() < 4 {
return
}
let source_width = mask_width.reinterpret_as_int()
let source_height = mask_height.reinterpret_as_int()
let dest_width = target_width.reinterpret_as_int()
let dest_height = target_height.reinterpret_as_int()
let source_x = if x < 0 { -x } else { 0 }
let source_y = if y < 0 { -y } else { 0 }
if source_x >= source_width || source_y >= source_height {
return
}
let dest_x = if x < 0 { 0 } else { x }
let dest_y = if y < 0 { 0 } else { y }
if dest_x >= dest_width || dest_y >= dest_height {
return
}
let source_end_x = source_width.min(dest_width - dest_x + source_x)
let source_end_y = source_height.min(dest_height - dest_y + source_y)
let dest_pitch = target_width.reinterpret_as_int() * 4
let color_a = color[3].to_int().reinterpret_as_uint()
let mut dy = dest_y
for sy in source_y..> 8
if a >= 255U {
target[dst_row + dx + 0] = color[0]
target[dst_row + dx + 1] = color[1]
target[dst_row + dx + 2] = color[2]
target[dst_row + dx + 3] = (255).to_byte()
} else if a != 0U {
let inverse_a = 255U - a
for i in 0..<3 {
let d = target[dst_row + dx + i].to_int().reinterpret_as_uint()
let c = (inverse_a * d + a * color[i].to_int().reinterpret_as_uint()) >>
8
target[dst_row + dx + i] = c.reinterpret_as_int().to_byte()
}
let d = target[dst_row + dx + 3].to_int().reinterpret_as_uint()
let c = (inverse_a * d + a * 255U) >> 8
target[dst_row + dx + 3] = c.reinterpret_as_int().to_byte()
}
dx = dx + 4
}
}
}
///|
fn decode_ebdt_1bpp(
data : BytesView,
width : UInt,
height : UInt,
target : Array[Byte],
) -> Bool {
let w = width.reinterpret_as_int()
let h = height.reinterpret_as_int()
if w <= 0 || h <= 0 {
return false
}
let bytes_per_row = (w + 7) / 8
let needed = bytes_per_row * h
if data.length() < needed {
return false
}
let expected = w * h
if target.length() < expected {
return false
}
let mut out = 0
for y in 0.. return false
Some(b) => {
let bit = (b.to_int().reinterpret_as_uint() >> bit_index) & 1
target[out] = if bit != 0 { (255).to_byte() } else { (0).to_byte() }
out = out + 1
}
}
}
}
true
}
///|
fn mitchell(x : Double) -> Double {
let a = x.abs()
if a < 1.0 {
((16.0 + a * a * (21.0 * a - 36.0)) / 18.0).abs()
} else if a < 2.0 {
((32.0 + a * (-60.0 + a * (36.0 - 7.0 * a))) / 18.0).abs()
} else {
0.0
}
}
///|
fn resize_weights(
width : UInt,
new_width : UInt,
outx : UInt,
weights : Array[Double],
) -> (Int, Int, Double) {
// Mirrors upstream swash `sample_dir` weight computation for Mitchell.
weights.clear()
let ratio = width.to_double() / new_width.to_double()
let sratio = if ratio > 1.0 { ratio } else { 1.0 }
let src_support = 2.0 * sratio
let isratio = 1.0 / sratio
let inx = (outx.to_double() + 0.5) * ratio - 0.5
let mut left = (inx - src_support).floor().to_int()
let mut right = (inx + src_support).ceil().to_int()
if left < 0 {
left = 0
}
let max_left = width.reinterpret_as_int() - 1
if left > max_left {
left = max_left
}
if right < left + 1 {
right = left + 1
}
let max_right = width.reinterpret_as_int()
if right > max_right {
right = max_right
}
// Limit weights to 64 samples like upstream.
while right - left > 64 {
right = right - 1
left = left + 1
}
let mut sum = 0.0
for i in left.. Bool {
if target_width == 0U || target_height == 0U {
return true
}
let image_size = (width * height).reinterpret_as_int()
if image.length() < image_size {
return false
}
let target_size = (target_width * target_height).reinterpret_as_int()
if target.length() < target_size {
return false
}
// Horizontal pass: image -> scratch (target_width x height).
scratch.clear()
let scratch_size = (target_width * height).reinterpret_as_int()
for _ in 0.. Bool {
if target_width == 0U || target_height == 0U {
return true
}
let image_size = (width * height * 4U).reinterpret_as_int()
if image.length() < image_size {
return false
}
let target_size = (target_width * target_height * 4U).reinterpret_as_int()
if target.length() < target_size {
return false
}
// Horizontal pass: image -> scratch (target_width x height x 4).
scratch.clear()
let scratch_size = (target_width * height * 4U).reinterpret_as_int()
for _ in 0.. target (target_width x target_height x 4).
for outy in 0U..