@pixagram/renderart 0.4.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +171 -67
- package/dist/crt-gpu.d.ts +30 -0
- package/dist/crt-gpu.d.ts.map +1 -0
- package/dist/crt-gpu.js +282 -0
- package/dist/crt-gpu.js.map +1 -0
- package/dist/hex-gpu.d.ts +35 -0
- package/dist/hex-gpu.d.ts.map +1 -0
- package/dist/hex-gpu.js +382 -0
- package/dist/hex-gpu.js.map +1 -0
- package/dist/index.d.ts +21 -300
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -963
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/wasm-wrapper.d.ts +71 -0
- package/dist/wasm-wrapper.d.ts.map +1 -0
- package/dist/wasm-wrapper.js +76 -0
- package/dist/wasm-wrapper.js.map +1 -0
- package/dist/xbrz-gpu.d.ts +34 -0
- package/dist/xbrz-gpu.d.ts.map +1 -0
- package/dist/xbrz-gpu.js +640 -0
- package/dist/xbrz-gpu.js.map +1 -0
- package/package.json +48 -35
- package/src/crt-gpu.ts +313 -0
- package/src/hex-gpu.ts +426 -0
- package/src/index.ts +52 -0
- package/src/types.ts +90 -0
- package/src/wasm/crt.rs +181 -0
- package/src/wasm/hex.rs +324 -0
- package/src/wasm/lib.rs +285 -0
- package/src/wasm/xbrz.rs +262 -0
- package/src/wasm-wrapper.ts +195 -0
- package/src/xbrz-gpu.ts +671 -0
- package/dist/index.d.mts +0 -305
- package/dist/index.mjs +0 -948
- package/dist/index.mjs.map +0 -1
- package/pkg/LICENSE +0 -21
- package/pkg/README.md +0 -117
- package/pkg/renderart_wasm.d.ts +0 -52
- package/pkg/renderart_wasm.js +0 -5
- package/pkg/renderart_wasm_bg.js +0 -283
- package/pkg/renderart_wasm_bg.wasm +0 -0
- package/pkg/renderart_wasm_bg.wasm.d.ts +0 -24
package/src/wasm/crt.rs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
//! CRT Effect Rendering Engine
|
|
2
|
+
//!
|
|
3
|
+
//! Simulates CRT display characteristics.
|
|
4
|
+
|
|
5
|
+
/// CRT configuration
|
|
6
|
+
#[derive(Clone, Copy)]
|
|
7
|
+
pub struct CrtConfig {
|
|
8
|
+
pub warp_x: f32,
|
|
9
|
+
pub warp_y: f32,
|
|
10
|
+
pub scan_hardness: f32,
|
|
11
|
+
pub scan_opacity: f32,
|
|
12
|
+
pub mask_opacity: f32,
|
|
13
|
+
pub enable_warp: bool,
|
|
14
|
+
pub enable_scanlines: bool,
|
|
15
|
+
pub enable_mask: bool,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl Default for CrtConfig {
|
|
19
|
+
fn default() -> Self {
|
|
20
|
+
Self {
|
|
21
|
+
warp_x: 0.015,
|
|
22
|
+
warp_y: 0.02,
|
|
23
|
+
scan_hardness: -4.0,
|
|
24
|
+
scan_opacity: 0.5,
|
|
25
|
+
mask_opacity: 0.3,
|
|
26
|
+
enable_warp: true,
|
|
27
|
+
enable_scanlines: true,
|
|
28
|
+
enable_mask: true,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Core CRT rendering function
|
|
34
|
+
pub fn crt_upscale(
|
|
35
|
+
input: &[u8],
|
|
36
|
+
src_w: usize,
|
|
37
|
+
src_h: usize,
|
|
38
|
+
scale: usize,
|
|
39
|
+
config: &CrtConfig,
|
|
40
|
+
) -> Vec<u8> {
|
|
41
|
+
let scale = scale.clamp(2, 32);
|
|
42
|
+
let out_w = src_w * scale;
|
|
43
|
+
let out_h = src_h * scale;
|
|
44
|
+
let mut output = vec![0u8; out_w * out_h * 4];
|
|
45
|
+
|
|
46
|
+
// Pre-calculate scanline LUT
|
|
47
|
+
let scan_lut: Vec<f32> = (0..=100)
|
|
48
|
+
.map(|i| {
|
|
49
|
+
if !config.enable_scanlines {
|
|
50
|
+
return 1.0;
|
|
51
|
+
}
|
|
52
|
+
let v = i as f32 / 100.0;
|
|
53
|
+
let d = (v - 0.5).abs();
|
|
54
|
+
let line = (d * d * config.scan_hardness).exp();
|
|
55
|
+
1.0 * (1.0 - config.scan_opacity) + line * config.scan_opacity
|
|
56
|
+
})
|
|
57
|
+
.collect();
|
|
58
|
+
|
|
59
|
+
// Pre-calculate mask LUT
|
|
60
|
+
let mask_lut: [[f32; 3]; 6] = if config.enable_mask {
|
|
61
|
+
[
|
|
62
|
+
[1.0, 0.0, 0.0],
|
|
63
|
+
[1.0, 0.0, 0.0],
|
|
64
|
+
[0.0, 1.0, 0.0],
|
|
65
|
+
[0.0, 1.0, 0.0],
|
|
66
|
+
[0.0, 0.0, 1.0],
|
|
67
|
+
[0.0, 0.0, 1.0],
|
|
68
|
+
]
|
|
69
|
+
.map(|c| {
|
|
70
|
+
[
|
|
71
|
+
1.0 * (1.0 - config.mask_opacity) + c[0] * config.mask_opacity,
|
|
72
|
+
1.0 * (1.0 - config.mask_opacity) + c[1] * config.mask_opacity,
|
|
73
|
+
1.0 * (1.0 - config.mask_opacity) + c[2] * config.mask_opacity,
|
|
74
|
+
]
|
|
75
|
+
})
|
|
76
|
+
} else {
|
|
77
|
+
[[1.0, 1.0, 1.0]; 6]
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let src_w_f = src_w as f32;
|
|
81
|
+
let src_h_f = src_h as f32;
|
|
82
|
+
let out_w_f = out_w as f32;
|
|
83
|
+
let out_h_f = out_h as f32;
|
|
84
|
+
|
|
85
|
+
for y in 0..out_h {
|
|
86
|
+
let v_norm = y as f32 / out_h_f;
|
|
87
|
+
|
|
88
|
+
let dc_y = (v_norm - 0.5).abs();
|
|
89
|
+
let dc2_y = dc_y * dc_y;
|
|
90
|
+
let warp_x_factor = if config.enable_warp {
|
|
91
|
+
1.0 + (dc2_y * (0.3 * config.warp_x))
|
|
92
|
+
} else {
|
|
93
|
+
1.0
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
let src_y_pos = v_norm * src_h_f;
|
|
97
|
+
let scan_idx = ((src_y_pos.fract()) * 100.0) as usize;
|
|
98
|
+
let scan_val = scan_lut[scan_idx.min(100)];
|
|
99
|
+
|
|
100
|
+
for x in 0..out_w {
|
|
101
|
+
let u_norm = x as f32 / out_w_f;
|
|
102
|
+
|
|
103
|
+
let (warped_u, warped_v) = if config.enable_warp {
|
|
104
|
+
let dc_x = (u_norm - 0.5).abs();
|
|
105
|
+
let dc2_x = dc_x * dc_x;
|
|
106
|
+
|
|
107
|
+
let mut wu = u_norm - 0.5;
|
|
108
|
+
wu *= warp_x_factor;
|
|
109
|
+
wu += 0.5;
|
|
110
|
+
|
|
111
|
+
let mut wv = v_norm - 0.5;
|
|
112
|
+
wv *= 1.0 + (dc2_x * (0.4 * config.warp_y));
|
|
113
|
+
wv += 0.5;
|
|
114
|
+
|
|
115
|
+
(wu, wv)
|
|
116
|
+
} else {
|
|
117
|
+
(u_norm, v_norm)
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if warped_u < 0.0 || warped_u >= 1.0 || warped_v < 0.0 || warped_v >= 1.0 {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let src_x = warped_u * src_w_f;
|
|
125
|
+
let src_y = warped_v * src_h_f;
|
|
126
|
+
|
|
127
|
+
let x0 = src_x.floor() as usize;
|
|
128
|
+
let y0 = src_y.floor() as usize;
|
|
129
|
+
let x1 = (x0 + 1).min(src_w - 1);
|
|
130
|
+
let y1 = (y0 + 1).min(src_h - 1);
|
|
131
|
+
|
|
132
|
+
let wx = src_x - x0 as f32;
|
|
133
|
+
let wy = src_y - y0 as f32;
|
|
134
|
+
let iwx = 1.0 - wx;
|
|
135
|
+
let iwy = 1.0 - wy;
|
|
136
|
+
|
|
137
|
+
let idx00 = (y0 * src_w + x0) * 4;
|
|
138
|
+
let idx10 = (y0 * src_w + x1) * 4;
|
|
139
|
+
let idx01 = (y1 * src_w + x0) * 4;
|
|
140
|
+
let idx11 = (y1 * src_w + x1) * 4;
|
|
141
|
+
|
|
142
|
+
let mut r = (input[idx00] as f32 * iwx + input[idx10] as f32 * wx) * iwy
|
|
143
|
+
+ (input[idx01] as f32 * iwx + input[idx11] as f32 * wx) * wy;
|
|
144
|
+
let mut g = (input[idx00 + 1] as f32 * iwx + input[idx10 + 1] as f32 * wx) * iwy
|
|
145
|
+
+ (input[idx01 + 1] as f32 * iwx + input[idx11 + 1] as f32 * wx) * wy;
|
|
146
|
+
let mut b = (input[idx00 + 2] as f32 * iwx + input[idx10 + 2] as f32 * wx) * iwy
|
|
147
|
+
+ (input[idx01 + 2] as f32 * iwx + input[idx11 + 2] as f32 * wx) * wy;
|
|
148
|
+
let a = (input[idx00 + 3] as f32 * iwx + input[idx10 + 3] as f32 * wx) * iwy
|
|
149
|
+
+ (input[idx01 + 3] as f32 * iwx + input[idx11 + 3] as f32 * wx) * wy;
|
|
150
|
+
|
|
151
|
+
if a < 1.0 {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
r = (r / 255.0).powi(2);
|
|
156
|
+
g = (g / 255.0).powi(2);
|
|
157
|
+
b = (b / 255.0).powi(2);
|
|
158
|
+
|
|
159
|
+
let luma = r * 0.299 + g * 0.587 + b * 0.114;
|
|
160
|
+
let bloom = luma * 0.7;
|
|
161
|
+
|
|
162
|
+
let mask_val = mask_lut[x % 6];
|
|
163
|
+
|
|
164
|
+
r *= scan_val;
|
|
165
|
+
g *= scan_val;
|
|
166
|
+
b *= scan_val;
|
|
167
|
+
|
|
168
|
+
r *= mask_val[0] * (1.0 - bloom) + bloom;
|
|
169
|
+
g *= mask_val[1] * (1.0 - bloom) + bloom;
|
|
170
|
+
b *= mask_val[2] * (1.0 - bloom) + bloom;
|
|
171
|
+
|
|
172
|
+
let out_idx = (y * out_w + x) * 4;
|
|
173
|
+
output[out_idx] = (r.sqrt() * 255.0).min(255.0) as u8;
|
|
174
|
+
output[out_idx + 1] = (g.sqrt() * 255.0).min(255.0) as u8;
|
|
175
|
+
output[out_idx + 2] = (b.sqrt() * 255.0).min(255.0) as u8;
|
|
176
|
+
output[out_idx + 3] = a as u8;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
output
|
|
181
|
+
}
|
package/src/wasm/hex.rs
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
//! Hexagonal Pixel Art Upscaling Engine
|
|
2
|
+
|
|
3
|
+
/// Hexagon orientation
|
|
4
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
5
|
+
pub enum HexOrientation {
|
|
6
|
+
FlatTop = 0,
|
|
7
|
+
PointyTop = 1,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/// Offset coordinate system
|
|
11
|
+
#[derive(Clone, Copy)]
|
|
12
|
+
enum OffsetType {
|
|
13
|
+
Odd,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// HEX configuration
|
|
17
|
+
#[derive(Clone)]
|
|
18
|
+
pub struct HexConfig {
|
|
19
|
+
pub orientation: HexOrientation,
|
|
20
|
+
pub draw_borders: bool,
|
|
21
|
+
pub border_color: u32,
|
|
22
|
+
pub border_thickness: usize,
|
|
23
|
+
pub background_color: u32,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl Default for HexConfig {
|
|
27
|
+
fn default() -> Self {
|
|
28
|
+
Self {
|
|
29
|
+
orientation: HexOrientation::FlatTop,
|
|
30
|
+
draw_borders: false,
|
|
31
|
+
border_color: 0x282828FF,
|
|
32
|
+
border_thickness: 1,
|
|
33
|
+
background_color: 0x00000000,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fixed-point arithmetic
|
|
39
|
+
#[derive(Clone, Copy, Default)]
|
|
40
|
+
struct Fixed32(i32);
|
|
41
|
+
|
|
42
|
+
impl Fixed32 {
|
|
43
|
+
const FRAC_BITS: i32 = 16;
|
|
44
|
+
const SCALE: i64 = 1 << 16;
|
|
45
|
+
|
|
46
|
+
#[inline(always)]
|
|
47
|
+
fn from_int(v: i32) -> Self {
|
|
48
|
+
Self(v << Self::FRAC_BITS)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[inline(always)]
|
|
52
|
+
fn from_ratio(num: i32, denom: i32) -> Self {
|
|
53
|
+
Self((((num as i64) << Self::FRAC_BITS) / denom as i64) as i32)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[inline(always)]
|
|
57
|
+
fn floor(self) -> i32 {
|
|
58
|
+
self.0 >> Self::FRAC_BITS
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[inline(always)]
|
|
62
|
+
fn abs(self) -> Self {
|
|
63
|
+
Self(self.0.abs())
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Precomputed hexagon geometry
|
|
68
|
+
struct HexGeometry {
|
|
69
|
+
orientation: HexOrientation,
|
|
70
|
+
offset_type: OffsetType,
|
|
71
|
+
h_spacing: Fixed32,
|
|
72
|
+
v_spacing: Fixed32,
|
|
73
|
+
cell_width: Fixed32,
|
|
74
|
+
cell_height: Fixed32,
|
|
75
|
+
row_offset: Fixed32,
|
|
76
|
+
col_offset: Fixed32,
|
|
77
|
+
edge_slope: Fixed32,
|
|
78
|
+
quarter_w: Fixed32,
|
|
79
|
+
half_h: Fixed32,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
impl HexGeometry {
|
|
83
|
+
fn new(scale: u32, orientation: HexOrientation, offset_type: OffsetType) -> Self {
|
|
84
|
+
let scale = scale.max(2) as i32;
|
|
85
|
+
|
|
86
|
+
match orientation {
|
|
87
|
+
HexOrientation::FlatTop => {
|
|
88
|
+
let cell_width = Fixed32::from_int(scale * 2);
|
|
89
|
+
let cell_height = Fixed32::from_ratio(scale * 1732, 1000);
|
|
90
|
+
let h_spacing = Fixed32::from_ratio(scale * 3, 2);
|
|
91
|
+
let v_spacing = cell_height;
|
|
92
|
+
let row_offset = Fixed32(cell_height.0 / 2);
|
|
93
|
+
|
|
94
|
+
Self {
|
|
95
|
+
orientation,
|
|
96
|
+
offset_type,
|
|
97
|
+
cell_width,
|
|
98
|
+
cell_height,
|
|
99
|
+
h_spacing,
|
|
100
|
+
v_spacing,
|
|
101
|
+
row_offset,
|
|
102
|
+
col_offset: Fixed32::from_int(0),
|
|
103
|
+
edge_slope: Fixed32::from_ratio(1732, 1000),
|
|
104
|
+
quarter_w: Fixed32::from_ratio(scale, 2),
|
|
105
|
+
half_h: Fixed32(cell_height.0 / 2),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
HexOrientation::PointyTop => {
|
|
109
|
+
let cell_width = Fixed32::from_ratio(scale * 1732, 1000);
|
|
110
|
+
let cell_height = Fixed32::from_int(scale * 2);
|
|
111
|
+
let h_spacing = cell_width;
|
|
112
|
+
let v_spacing = Fixed32::from_ratio(scale * 3, 2);
|
|
113
|
+
let col_offset = Fixed32(cell_width.0 / 2);
|
|
114
|
+
|
|
115
|
+
Self {
|
|
116
|
+
orientation,
|
|
117
|
+
offset_type,
|
|
118
|
+
cell_width,
|
|
119
|
+
cell_height,
|
|
120
|
+
h_spacing,
|
|
121
|
+
v_spacing,
|
|
122
|
+
row_offset: Fixed32::from_int(0),
|
|
123
|
+
col_offset,
|
|
124
|
+
edge_slope: Fixed32::from_ratio(1000, 1732),
|
|
125
|
+
quarter_w: Fixed32(cell_width.0 / 4),
|
|
126
|
+
half_h: Fixed32::from_ratio(scale, 2),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fn output_dimensions(&self, input_width: u32, input_height: u32) -> (u32, u32) {
|
|
133
|
+
let w = input_width as i64;
|
|
134
|
+
let h = input_height as i64;
|
|
135
|
+
|
|
136
|
+
let out_w = ((w * self.h_spacing.0 as i64) >> Fixed32::FRAC_BITS) + self.cell_width.floor() as i64;
|
|
137
|
+
let out_h = ((h * self.v_spacing.0 as i64) >> Fixed32::FRAC_BITS) + self.cell_height.floor() as i64;
|
|
138
|
+
|
|
139
|
+
(out_w.max(1) as u32, out_h.max(1) as u32)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[inline(always)]
|
|
143
|
+
fn output_to_hex(&self, out_x: i32, out_y: i32) -> (i32, i32) {
|
|
144
|
+
match self.orientation {
|
|
145
|
+
HexOrientation::FlatTop => self.output_to_hex_flat(out_x, out_y),
|
|
146
|
+
HexOrientation::PointyTop => self.output_to_hex_pointy(out_x, out_y),
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[inline(always)]
|
|
151
|
+
fn output_to_hex_flat(&self, out_x: i32, out_y: i32) -> (i32, i32) {
|
|
152
|
+
let x = Fixed32::from_int(out_x);
|
|
153
|
+
let y = Fixed32::from_int(out_y);
|
|
154
|
+
|
|
155
|
+
let col = ((x.0 as i64 * Fixed32::SCALE) / self.h_spacing.0 as i64) as i32 >> Fixed32::FRAC_BITS;
|
|
156
|
+
|
|
157
|
+
let is_offset = match self.offset_type {
|
|
158
|
+
OffsetType::Odd => col & 1 == 1,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
let y_adj = if is_offset {
|
|
162
|
+
Fixed32(y.0 - self.row_offset.0)
|
|
163
|
+
} else {
|
|
164
|
+
y
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
let mut row = ((y_adj.0 as i64 * Fixed32::SCALE) / self.v_spacing.0 as i64) as i32 >> Fixed32::FRAC_BITS;
|
|
168
|
+
|
|
169
|
+
let cell_x = Fixed32(x.0 - ((col as i64 * self.h_spacing.0 as i64) >> Fixed32::FRAC_BITS) as i32);
|
|
170
|
+
let cell_y = if is_offset {
|
|
171
|
+
Fixed32(y.0 - ((row as i64 * self.v_spacing.0 as i64) >> Fixed32::FRAC_BITS) as i32 - self.row_offset.0)
|
|
172
|
+
} else {
|
|
173
|
+
Fixed32(y.0 - ((row as i64 * self.v_spacing.0 as i64) >> Fixed32::FRAC_BITS) as i32)
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if cell_x.0 < self.quarter_w.0 {
|
|
177
|
+
let half_h = self.half_h;
|
|
178
|
+
let dist_from_center = Fixed32(cell_y.0 - half_h.0).abs();
|
|
179
|
+
let edge_x = Fixed32(((dist_from_center.0 as i64 * Fixed32::SCALE) / self.edge_slope.0 as i64) as i32);
|
|
180
|
+
|
|
181
|
+
if cell_x.0 < self.quarter_w.0 - edge_x.0 {
|
|
182
|
+
if cell_y.0 < half_h.0 {
|
|
183
|
+
row -= 1;
|
|
184
|
+
}
|
|
185
|
+
return (col - 1, row);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
(col, row)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#[inline(always)]
|
|
193
|
+
fn output_to_hex_pointy(&self, out_x: i32, out_y: i32) -> (i32, i32) {
|
|
194
|
+
let x = Fixed32::from_int(out_x);
|
|
195
|
+
let y = Fixed32::from_int(out_y);
|
|
196
|
+
|
|
197
|
+
let row = ((y.0 as i64 * Fixed32::SCALE) / self.v_spacing.0 as i64) as i32 >> Fixed32::FRAC_BITS;
|
|
198
|
+
|
|
199
|
+
let is_offset = match self.offset_type {
|
|
200
|
+
OffsetType::Odd => row & 1 == 1,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
let x_adj = if is_offset {
|
|
204
|
+
Fixed32(x.0 - self.col_offset.0)
|
|
205
|
+
} else {
|
|
206
|
+
x
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
let mut col = ((x_adj.0 as i64 * Fixed32::SCALE) / self.h_spacing.0 as i64) as i32 >> Fixed32::FRAC_BITS;
|
|
210
|
+
|
|
211
|
+
let cell_x = if is_offset {
|
|
212
|
+
Fixed32(x.0 - ((col as i64 * self.h_spacing.0 as i64) >> Fixed32::FRAC_BITS) as i32 - self.col_offset.0)
|
|
213
|
+
} else {
|
|
214
|
+
Fixed32(x.0 - ((col as i64 * self.h_spacing.0 as i64) >> Fixed32::FRAC_BITS) as i32)
|
|
215
|
+
};
|
|
216
|
+
let cell_y = Fixed32(y.0 - ((row as i64 * self.v_spacing.0 as i64) >> Fixed32::FRAC_BITS) as i32);
|
|
217
|
+
|
|
218
|
+
if cell_y.0 < self.half_h.0 {
|
|
219
|
+
let half_w = Fixed32(self.cell_width.0 / 2);
|
|
220
|
+
let dist_from_center = Fixed32(cell_x.0 - half_w.0).abs();
|
|
221
|
+
let edge_y = Fixed32(((dist_from_center.0 as i64 * self.edge_slope.0 as i64) / Fixed32::SCALE) as i32);
|
|
222
|
+
|
|
223
|
+
if cell_y.0 < edge_y.0 {
|
|
224
|
+
if cell_x.0 < half_w.0 && is_offset {
|
|
225
|
+
col -= 1;
|
|
226
|
+
}
|
|
227
|
+
return (col, row - 1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
(col, row)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// Get output dimensions for hexagonal rendering
|
|
236
|
+
pub fn get_output_dimensions(
|
|
237
|
+
src_w: usize,
|
|
238
|
+
src_h: usize,
|
|
239
|
+
scale: usize,
|
|
240
|
+
orientation: &HexOrientation,
|
|
241
|
+
) -> (usize, usize) {
|
|
242
|
+
let scale = scale.clamp(2, 32) as u32;
|
|
243
|
+
let geometry = HexGeometry::new(scale, *orientation, OffsetType::Odd);
|
|
244
|
+
let (out_w, out_h) = geometry.output_dimensions(src_w as u32, src_h as u32);
|
|
245
|
+
(out_w as usize, out_h as usize)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// Core hexagonal rendering function
|
|
249
|
+
pub fn hex_upscale(
|
|
250
|
+
input: &[u8],
|
|
251
|
+
src_w: usize,
|
|
252
|
+
src_h: usize,
|
|
253
|
+
scale: usize,
|
|
254
|
+
config: &HexConfig,
|
|
255
|
+
) -> Vec<u8> {
|
|
256
|
+
let scale = scale.clamp(2, 32) as u32;
|
|
257
|
+
let geometry = HexGeometry::new(scale, config.orientation, OffsetType::Odd);
|
|
258
|
+
let (out_w, out_h) = geometry.output_dimensions(src_w as u32, src_h as u32);
|
|
259
|
+
let mut output = vec![0u8; (out_w * out_h * 4) as usize];
|
|
260
|
+
|
|
261
|
+
let bg_r = ((config.background_color >> 24) & 0xFF) as u8;
|
|
262
|
+
let bg_g = ((config.background_color >> 16) & 0xFF) as u8;
|
|
263
|
+
let bg_b = ((config.background_color >> 8) & 0xFF) as u8;
|
|
264
|
+
let bg_a = (config.background_color & 0xFF) as u8;
|
|
265
|
+
|
|
266
|
+
let border_r = ((config.border_color >> 24) & 0xFF) as u8;
|
|
267
|
+
let border_g = ((config.border_color >> 16) & 0xFF) as u8;
|
|
268
|
+
let border_b = ((config.border_color >> 8) & 0xFF) as u8;
|
|
269
|
+
let border_a = (config.border_color & 0xFF) as u8;
|
|
270
|
+
|
|
271
|
+
for y in 0..out_h as i32 {
|
|
272
|
+
for x in 0..out_w as i32 {
|
|
273
|
+
let (hex_col, hex_row) = geometry.output_to_hex(x, y);
|
|
274
|
+
let out_idx = ((y as u32 * out_w + x as u32) * 4) as usize;
|
|
275
|
+
|
|
276
|
+
if hex_col >= 0 && hex_row >= 0 && hex_col < src_w as i32 && hex_row < src_h as i32 {
|
|
277
|
+
if config.draw_borders && config.border_thickness > 0 {
|
|
278
|
+
if is_border_pixel(x, y, &geometry, config.border_thickness) {
|
|
279
|
+
output[out_idx] = border_r;
|
|
280
|
+
output[out_idx + 1] = border_g;
|
|
281
|
+
output[out_idx + 2] = border_b;
|
|
282
|
+
output[out_idx + 3] = border_a;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let src_idx = (hex_row as usize * src_w + hex_col as usize) * 4;
|
|
288
|
+
output[out_idx] = input[src_idx];
|
|
289
|
+
output[out_idx + 1] = input[src_idx + 1];
|
|
290
|
+
output[out_idx + 2] = input[src_idx + 2];
|
|
291
|
+
output[out_idx + 3] = input[src_idx + 3];
|
|
292
|
+
} else {
|
|
293
|
+
output[out_idx] = bg_r;
|
|
294
|
+
output[out_idx + 1] = bg_g;
|
|
295
|
+
output[out_idx + 2] = bg_b;
|
|
296
|
+
output[out_idx + 3] = bg_a;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
output
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
#[inline]
|
|
305
|
+
fn is_border_pixel(x: i32, y: i32, geometry: &HexGeometry, thickness: usize) -> bool {
|
|
306
|
+
let t = thickness as i32;
|
|
307
|
+
|
|
308
|
+
for dy in -t..=t {
|
|
309
|
+
for dx in -t..=t {
|
|
310
|
+
if dx == 0 && dy == 0 {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let (h1_col, h1_row) = geometry.output_to_hex(x, y);
|
|
315
|
+
let (h2_col, h2_row) = geometry.output_to_hex(x + dx, y + dy);
|
|
316
|
+
|
|
317
|
+
if h1_col != h2_col || h1_row != h2_row {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
false
|
|
324
|
+
}
|