///|
/// RGBA 图像数据结构:每像素 4 字节(R,G,B,A),行优先存储。
///
/// 内部用 `FixedArray[Byte]` 存储以便像素级原地修改(水印嵌入零复制);
/// 与 mizchi/image 的 `ImageData`(Bytes)互转见 `decode_png` / `encode_png`。
pub struct RgbaImage {
width : Int32
height : Int32
data : FixedArray[Byte]
}
///|
/// 数学常量(DCT 基函数与旋转共用;同包内直接引用)。
const PI = 3.141592653589793
///|
const SQRT2 = 1.4142135623730951
///|
/// 缩放采样方式。
pub(all) enum SampleMode {
Nearest
Bilinear
}
///|
/// 创建指定尺寸的纯色图像。
///
/// 参数 `rgba` 为打包颜色:`0xRRGGBBAA`。
pub fn RgbaImage::new(width : Int32, height : Int32, rgba : Int32) -> RgbaImage {
// 字节模式填充(v0.1.12 优化):比逐像素 get/set 打包快数倍;
// rotate/resize 内部大量调用 new,纯色画布创建是热点路径
let bytes = FixedArray::make(width * height * 4, b'\x00')
let r = ((rgba >> 24) & 0xFF).to_byte()
let g = ((rgba >> 16) & 0xFF).to_byte()
let b = ((rgba >> 8) & 0xFF).to_byte()
let a = (rgba & 0xFF).to_byte()
for i in 0..<(width * height) {
let off = i * 4
bytes[off] = r
bytes[off + 1] = g
bytes[off + 2] = b
bytes[off + 3] = a
}
{ width, height, data: bytes, }
}
///|
/// 读取像素,返回打包 RGBA 颜色 `0xRRGGBBAA`。
pub fn RgbaImage::get_pixel(self : RgbaImage, x : Int32, y : Int32) -> Int32 {
let idx = (y * self.width + x) * 4
let d = self.data
(d[idx].to_int() << 24) |
(d[idx + 1].to_int() << 16) |
(d[idx + 2].to_int() << 8) |
d[idx + 3].to_int()
}
///|
/// 写入像素,`rgba` 为打包颜色 `0xRRGGBBAA`。
pub fn RgbaImage::set_pixel(
self : RgbaImage,
x : Int32,
y : Int32,
rgba : Int32,
) -> Unit {
let idx = (y * self.width + x) * 4
let d = self.data
d[idx] = ((rgba >> 24) & 0xFF).to_byte()
d[idx + 1] = ((rgba >> 16) & 0xFF).to_byte()
d[idx + 2] = ((rgba >> 8) & 0xFF).to_byte()
d[idx + 3] = (rgba & 0xFF).to_byte()
}
///|
/// 从 `mizchi/image` 的 `ImageData` 构造本库 `RgbaImage`(decode 共用)。
fn from_image_data(img : @image.ImageData) -> RgbaImage {
{ width: img.width, height: img.height, data: img.data.to_fixedarray(), }
}
///|
/// 将本库 `RgbaImage` 转为 `mizchi/image` 的 `ImageData`(encode / resize 共用)。
fn to_image_data(img : RgbaImage) -> @image.ImageData {
{ width: img.width, height: img.height, data: Bytes::from_array(img.data), }
}
///|
/// 将图像绕中心旋转 `deg` 度(顺时针为正;任意角度,自动规范化到 [0, 360))。
///
/// 采用逆映射最近邻采样(像素中心坐标,`round` 定位源像素),源范围外的
/// 像素保持透明(alpha 0)。输出为旋转后的包围盒尺寸;90/180/270 度为
/// 精确像素搬运(无采样误差),其余角度为最近邻近似。
pub fn RgbaImage::rotate(self : RgbaImage, deg : Double) -> RgbaImage {
let mut a = deg
while a >= 360.0 {
a = a - 360.0
}
while a < 0.0 {
a = a + 360.0
}
if a == 0.0 || self.width == 0 || self.height == 0 {
return self
}
// 90/180/270 精确搬运,避免三角函数浮点误差
if a == 90.0 {
return rotate90(self)
}
if a == 180.0 {
return rotate180(self)
}
if a == 270.0 {
return rotate270(self)
}
let rad = a * PI / 180.0
let c = @math.cos(rad)
let s = @math.sin(rad)
let w = self.width.to_double()
let h = self.height.to_double()
// 包围盒:旋转后跨度的 ceil(像素中心坐标下 90° 精确得 w/h 互换)
let nw = (w * abs_d(c) + h * abs_d(s) - 0.000001).to_int() + 1
let nh = (w * abs_d(s) + h * abs_d(c) - 0.000001).to_int() + 1
let out = RgbaImage::new(nw, nh, 0x0000_0000)
let cx = nw.to_double() / 2.0
let cy = nh.to_double() / 2.0
let ox = w / 2.0
let oy = h / 2.0
for y in 0..= 0 && ix < self.width && iy >= 0 && iy < self.height {
out.set_pixel(x, y, self.get_pixel(ix, iy))
}
}
}
out
}
///|
/// 90° 顺时针精确旋转。
fn rotate90(img : RgbaImage) -> RgbaImage {
let out = RgbaImage::new(img.height, img.width, 0x0000_0000)
for y in 0.. RgbaImage {
let out = RgbaImage::new(img.width, img.height, 0x0000_0000)
for y in 0.. RgbaImage {
let out = RgbaImage::new(img.height, img.width, 0x0000_0000)
for y in 0.. Int32 {
if x >= 0.0 {
(x + 0.5).to_int()
} else {
(x - 0.5).to_int()
}
}
///|
/// 求 Double 绝对值(math 包未直接导出 abs,内联实现)。
fn abs_d(x : Double) -> Double {
if x < 0.0 {
-x
} else {
x
}
}
///|
/// 从 PNG 字节解码为 `RgbaImage`。
pub fn decode_png(raw : Bytes) -> Result[RgbaImage, String] {
Ok(from_image_data(@image.decode_png(raw))) catch {
_ => Err("decode_png: invalid png data")
}
}
///|
/// 将 `RgbaImage` 编码为 PNG 字节。
pub fn RgbaImage::encode_png(self : RgbaImage) -> Result[Bytes, String] {
Ok(@image.encode_png(to_image_data(self))) catch {
_ => Err("encode_png: invalid image")
}
}
///|
/// 从 JPEG 字节解码为 `RgbaImage`。
pub fn decode_jpeg(raw : Bytes) -> Result[RgbaImage, String] {
Ok(from_image_data(@image.decode_jpeg(raw))) catch {
_ => Err("decode_jpeg: invalid jpeg data")
}
}
///|
/// 将 `RgbaImage` 编码为 JPEG 字节(质量 0~100,用于鲁棒性测试)。
pub fn RgbaImage::encode_jpeg(
self : RgbaImage,
quality : Int32,
) -> Result[Bytes, String] {
Ok(@image.encode_jpeg(to_image_data(self), quality~)) catch {
_ => Err("encode_jpeg: invalid image")
}
}
///|
/// 缩放图像。
///
/// 注意:`width` / `height` 必须为正数——非法尺寸会 **panic**(而非返回
/// 原图副本,避免调用方静默拿到错误结果);`SampleMode` 决定采样方式
/// (`Nearest` 最近邻 / `Bilinear` 双线性)。
pub fn RgbaImage::resize(
self : RgbaImage,
width : Int32,
height : Int32,
sampler : SampleMode,
) -> RgbaImage {
let m = match sampler {
Nearest => @image.ResizeMethod::Nearest
Bilinear => @image.ResizeMethod::Bilinear
}
from_image_data(@image.resize(to_image_data(self), width, height, m)) catch {
_ => panic()
}
}
///|
/// 裁剪图像区域 `(x, y, w, h)`。越界坐标被钳制到图像范围内。
pub fn RgbaImage::crop(
self : RgbaImage,
x : Int32,
y : Int32,
w : Int32,
h : Int32,
) -> RgbaImage {
if w <= 0 || h <= 0 || self.width == 0 || self.height == 0 {
// 空目标或空源:返回 0×0 图(0 宽源下 `width - 1` 会得 -1 越界)
return { width: 0, height: 0, data: FixedArray::make(0, b'\x00'), }
}
let cx0 = if x < 0 { 0 } else if x >= self.width { self.width - 1 } else { x }
let cy0 = if y < 0 {
0
} else if y >= self.height {
self.height - 1
} else {
y
}
let cw = if cx0 + w > self.width { self.width - cx0 } else { w }
let ch = if cy0 + h > self.height { self.height - cy0 } else { h }
let out = FixedArray::make(cw * ch * 4, b'\x00')
// 源数据一次转 Bytes(避免每行全量拷贝),按行 blit 到裁剪画布
let src = Bytes::from_array(self.data)
for row in 0..