-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimage.mbt
More file actions
145 lines (134 loc) · 4.58 KB
/
Copy pathimage.mbt
File metadata and controls
145 lines (134 loc) · 4.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
///|
/// An 8-bit RGBA raster image.
///
/// Pixels are stored row-major in `data`, four bytes per pixel in R, G, B, A
/// order, so `data.length() == width * height * 4`. This layout matches the
/// browser `ImageData.data` buffer, which lets a `<canvas>` hand its pixels to
/// the library without any conversion step.
pub(all) struct Image {
width : Int
height : Int
data : FixedArray[Byte]
}
// Keep this limit in sync with the codec decoders. Besides bounding memory
// use, checking the product before multiplying prevents hostile dimensions
// from wrapping an `Int` and reaching `FixedArray::make`.
///|
const MAX_IMAGE_PIXELS : Int = 100000000
///|
/// Computes the RGBA buffer length after validating image dimensions.
///
/// Image dimensions are non-negative and may contain at most
/// `MAX_IMAGE_PIXELS` pixels. Empty images (where either dimension is zero)
/// are valid and use an empty backing buffer. The checks are deliberately
/// performed before either multiplication so this helper is safe for
/// untrusted decoder input.
fn checked_buffer_len(width : Int, height : Int) -> Int {
if width < 0 || height < 0 {
abort("Image: width and height must be non-negative")
}
if width == 0 || height == 0 {
return 0
}
if width > MAX_IMAGE_PIXELS / height {
abort("Image: dimensions exceed the maximum pixel count")
}
let pixels = width * height
// MAX_IMAGE_PIXELS is chosen so that this multiplication is safe on all
// supported MoonBit Int targets (including 32-bit targets).
pixels * 4
}
///|
/// Creates a fully transparent black image of the given size.
pub fn Image::new(width : Int, height : Int) -> Image {
let len = checked_buffer_len(width, height)
{ width, height, data: FixedArray::make(len, b'\x00'), }
}
///|
/// Wraps an existing RGBA buffer. Aborts if `data` does not hold exactly
/// `width * height * 4` bytes, so a malformed buffer fails fast instead of
/// producing silently corrupt output.
pub fn Image::from_bytes(
width : Int,
height : Int,
data : FixedArray[Byte],
) -> Image {
let len = checked_buffer_len(width, height)
if data.length() != len {
abort("Image::from_bytes: buffer length must equal width * height * 4")
}
{ width, height, data, }
}
///|
/// The number of pixels (not bytes) in the image.
pub fn Image::pixel_count(self : Image) -> Int {
// Validate dimensions even when this method is called on a struct assembled
// by a low-level host. This keeps malformed public records from silently
// propagating an overflowing pixel count.
checked_buffer_len(self.width, self.height) / 4
}
///|
/// Returns a deep copy that shares no mutable state with the original.
pub fn Image::copy(self : Image) -> Image {
{ width: self.width, height: self.height, data: self.data.copy(), }
}
///|
/// Reads the (R, G, B, A) channels of the pixel at (`x`, `y`).
///
/// Aborts when the coordinates are outside the image bounds or when a host
/// supplies an `Image` record whose backing buffer does not match its size.
pub fn Image::get_pixel(
self : Image,
x : Int,
y : Int,
) -> (Byte, Byte, Byte, Byte) {
let i = self.pixel_offset(x, y)
(self.data[i], self.data[i + 1], self.data[i + 2], self.data[i + 3])
}
///|
/// Writes the (R, G, B, A) channels of the pixel at (`x`, `y`).
///
/// Aborts when the coordinates are outside the image bounds or when a host
/// supplies an `Image` record whose backing buffer does not match its size.
pub fn Image::set_pixel(
self : Image,
x : Int,
y : Int,
r : Byte,
g : Byte,
b : Byte,
a : Byte,
) -> Unit {
let i = self.pixel_offset(x, y)
self.data[i] = r
self.data[i + 1] = g
self.data[i + 2] = b
self.data[i + 3] = a
}
///|
/// Returns the byte offset for one pixel after checking the image invariant
/// and coordinate bounds. Keeping this in one place makes all public pixel
/// accessors fail with a deterministic message instead of a raw array panic.
fn Image::pixel_offset(self : Image, x : Int, y : Int) -> Int {
let len = checked_buffer_len(self.width, self.height)
if self.data.length() != len {
abort("Image: backing buffer length does not match dimensions")
}
if x < 0 || x >= self.width || y < 0 || y >= self.height {
abort("Image: pixel coordinates are out of bounds")
}
(y * self.width + x) * 4
}
///|
/// Clamps an integer into the `[0, 255]` range and narrows it to a `Byte`.
/// Channel math is done in `Int` to avoid wrap-around, then funnelled through
/// this helper before being stored back into a pixel buffer.
pub fn clamp_byte(value : Int) -> Byte {
if value < 0 {
b'\x00'
} else if value > 255 {
b'\xFF'
} else {
value.to_byte()
}
}