1use crate::assets::RgbaImage;
2use crate::image_manager::ImageManager;
3use crate::layer::{ClipRect, RenderSprite, SpriteBlend, SpriteFit, SpriteSizeMode};
4use crate::render_math::sprite_quad_points;
5
6fn uses_depth_pipeline(sprite: &crate::layer::Sprite) -> bool {
7 sprite.camera_enabled
8 || sprite.billboard
9 || sprite.z.abs() > f32::EPSILON
10 || sprite.pivot_z.abs() > f32::EPSILON
11 || (sprite.scale_z - 1.0).abs() > 1e-6
12 || sprite.rotate_x.abs() > f32::EPSILON
13 || sprite.rotate_y.abs() > f32::EPSILON
14}
15
16pub fn render_to_image(
17 images: &ImageManager,
18 sprites: &[RenderSprite],
19 width: u32,
20 height: u32,
21) -> RgbaImage {
22 let mut out = vec![
23 0u8;
24 (width as usize)
25 .saturating_mul(height as usize)
26 .saturating_mul(4)
27 ];
28 let win_w = width as i32;
29 let win_h = height as i32;
30 let mut depth_buf = vec![f32::INFINITY; (width as usize).saturating_mul(height as usize)];
31
32 for s in sprites {
33 let sprite = &s.sprite;
34 let Some(img_id) = sprite.image_id else {
35 continue;
36 };
37 let Some(img) = images.get(img_id) else {
38 continue;
39 };
40 let tone_img = sprite.tonecurve_image_id.and_then(|id| images.get(id));
41 let mask_img = sprite.mask_image_id.and_then(|id| images.get(id));
42 let wipe_src_img = sprite.wipe_src_image_id.and_then(|id| images.get(id));
43
44 let (src_left, src_top, src_right, src_bottom) =
45 match src_clip_rect(sprite.src_clip, img.width, img.height) {
46 Ok(v) => v,
47 Err(_) => continue,
48 };
49 let src_w = (src_right - src_left).max(1.0);
50 let src_h = (src_bottom - src_top).max(1.0);
51
52 let (dst_x, dst_y, dst_w, dst_h) = match sprite.fit {
53 SpriteFit::FullScreen => (0.0f32, 0.0f32, win_w as f32, win_h as f32),
54 SpriteFit::PixelRect => {
55 let (w, h) = match sprite.size_mode {
56 SpriteSizeMode::Intrinsic => (src_w, src_h),
57 SpriteSizeMode::Explicit { width, height } => (width as f32, height as f32),
58 };
59 (sprite.x as f32, sprite.y as f32, w, h)
60 }
61 };
62
63 if dst_w <= 0.0 || dst_h <= 0.0 {
64 continue;
65 }
66
67 let Some([p0, p1, p2, p3]) =
68 sprite_quad_points(sprite, dst_x, dst_y, dst_w, dst_h, width as f32, height as f32)
69 else {
70 continue;
71 };
72
73 let mut min_x = p0.x.min(p1.x).min(p2.x).min(p3.x).floor() as i32;
74 let mut max_x = p0.x.max(p1.x).max(p2.x).max(p3.x).ceil() as i32;
75 let mut min_y = p0.y.min(p1.y).min(p2.y).min(p3.y).floor() as i32;
76 let mut max_y = p0.y.max(p1.y).max(p2.y).max(p3.y).ceil() as i32;
77
78 min_x = min_x.clamp(0, win_w);
79 max_x = max_x.clamp(0, win_w);
80 min_y = min_y.clamp(0, win_h);
81 max_y = max_y.clamp(0, win_h);
82
83 if let Some(sci) = dst_scissor_rect(sprite.dst_clip, width, height) {
84 let sci_left = sci.x as i32;
85 let sci_top = sci.y as i32;
86 let sci_right = (sci.x + sci.w) as i32;
87 let sci_bottom = (sci.y + sci.h) as i32;
88 min_x = min_x.max(sci_left);
89 max_x = max_x.min(sci_right);
90 min_y = min_y.max(sci_top);
91 max_y = max_y.min(sci_bottom);
92 }
93
94 if min_x >= max_x || min_y >= max_y {
95 continue;
96 }
97
98 let alpha = (sprite.alpha as f32) / 255.0;
99 let tr = (sprite.tr as f32) / 255.0;
100 let mono = (sprite.mono as f32) / 255.0;
101 let reverse = (sprite.reverse as f32) / 255.0;
102 let bright = (sprite.bright as f32) / 255.0;
103 let dark = (sprite.dark as f32) / 255.0;
104 let color_rate = (sprite.color_rate as f32) / 255.0;
105 let color_add_r = (sprite.color_add_r as f32) / 255.0;
106 let color_add_g = (sprite.color_add_g as f32) / 255.0;
107 let color_add_b = (sprite.color_add_b as f32) / 255.0;
108 let color_r = (sprite.color_r as f32) / 255.0;
109 let color_g = (sprite.color_g as f32) / 255.0;
110 let color_b = (sprite.color_b as f32) / 255.0;
111
112 let use_depth = uses_depth_pipeline(sprite);
113 let tris = [
114 (
115 [
116 (p0.x, p0.y, p0.depth),
117 (p1.x, p1.y, p1.depth),
118 (p2.x, p2.y, p2.depth),
119 ],
120 [
121 (src_left, src_top),
122 (src_right, src_top),
123 (src_right, src_bottom),
124 ],
125 ),
126 (
127 [
128 (p0.x, p0.y, p0.depth),
129 (p2.x, p2.y, p2.depth),
130 (p3.x, p3.y, p3.depth),
131 ],
132 [
133 (src_left, src_top),
134 (src_right, src_bottom),
135 (src_left, src_bottom),
136 ],
137 ),
138 ];
139
140 for (tri, uv_tri) in tris {
141 let area = edge(
142 (tri[0].0, tri[0].1),
143 (tri[1].0, tri[1].1),
144 (tri[2].0, tri[2].1),
145 );
146 if area.abs() <= f32::EPSILON {
147 continue;
148 }
149 for y in min_y..max_y {
150 for x in min_x..max_x {
151 let p = (x as f32 + 0.5, y as f32 + 0.5);
152 let w0 = edge((tri[1].0, tri[1].1), (tri[2].0, tri[2].1), p) / area;
153 let w1 = edge((tri[2].0, tri[2].1), (tri[0].0, tri[0].1), p) / area;
154 let w2 = edge((tri[0].0, tri[0].1), (tri[1].0, tri[1].1), p) / area;
155 if w0 < -1e-5 || w1 < -1e-5 || w2 < -1e-5 {
156 continue;
157 }
158
159 let depth = w0 * tri[0].2 + w1 * tri[1].2 + w2 * tri[2].2;
160 let u = w0 * uv_tri[0].0 + w1 * uv_tri[1].0 + w2 * uv_tri[2].0;
161 let v = w0 * uv_tri[0].1 + w1 * uv_tri[1].1 + w2 * uv_tri[2].1;
162 if u < src_left || v < src_top || u >= src_right || v >= src_bottom {
163 continue;
164 }
165 let sx = u.floor() as i32;
166 let sy = v.floor() as i32;
167 if sx < 0 || sy < 0 || sx >= img.width as i32 || sy >= img.height as i32 {
168 continue;
169 }
170
171 let z_idx = (y as u32 * width + x as u32) as usize;
172 if use_depth {
173 if depth > depth_buf[z_idx] + 1e-6 {
174 continue;
175 }
176 depth_buf[z_idx] = depth;
177 }
178
179 let src = sample_base_with_wipe(
180 sprite,
181 img,
182 wipe_src_img.as_deref().map(|v| &**v),
183 u,
184 v,
185 );
186 let sr = src[0];
187 let sg = src[1];
188 let sb = src[2];
189 let sa = src[3];
190
191 let mut r = sr;
192 let mut g = sg;
193 let mut b = sb;
194
195 if let Some(tone_img) = tone_img.as_ref() {
196 let sat = sprite.tonecurve_sat.clamp(0.0, 1.0);
197 if sat > 0.0 {
198 let gray = r * 0.299 + g * 0.587 + b * 0.114;
199 r = r + sat * (gray - r);
200 g = g + sat * (gray - g);
201 b = b + sat * (gray - b);
202 }
203 let row = (sprite.tonecurve_row.clamp(0.0, 1.0) * tone_img.height as f32)
204 .floor()
205 .clamp(0.0, (tone_img.height.saturating_sub(1)) as f32)
206 as u32;
207 let ri = (r.clamp(0.0, 1.0) * 255.0).round().clamp(0.0, 255.0) as u32;
208 let gi = (g.clamp(0.0, 1.0) * 255.0).round().clamp(0.0, 255.0) as u32;
209 let bi = (b.clamp(0.0, 1.0) * 255.0).round().clamp(0.0, 255.0) as u32;
210 r = tone_img.rgba[((row * tone_img.width
211 + ri.min(tone_img.width.saturating_sub(1)))
212 * 4) as usize] as f32
213 / 255.0;
214 g = tone_img.rgba[((row * tone_img.width
215 + gi.min(tone_img.width.saturating_sub(1)))
216 * 4
217 + 1) as usize] as f32
218 / 255.0;
219 b = tone_img.rgba[((row * tone_img.width
220 + bi.min(tone_img.width.saturating_sub(1)))
221 * 4
222 + 2) as usize] as f32
223 / 255.0;
224 }
225
226 if reverse > 0.0 {
227 r = r + reverse * ((1.0 - r) - r);
228 g = g + reverse * ((1.0 - g) - g);
229 b = b + reverse * ((1.0 - b) - b);
230 }
231 if mono > 0.0 {
232 let gray = r * 0.299 + g * 0.587 + b * 0.114;
233 r = r + mono * (gray - r);
234 g = g + mono * (gray - g);
235 b = b + mono * (gray - b);
236 }
237
238 r = (r + bright).clamp(0.0, 1.0);
239 g = (g + bright).clamp(0.0, 1.0);
240 b = (b + bright).clamp(0.0, 1.0);
241 r = (r - dark).clamp(0.0, 1.0);
242 g = (g - dark).clamp(0.0, 1.0);
243 b = (b - dark).clamp(0.0, 1.0);
244
245 if color_rate > 0.0 {
246 r = r + color_rate * (color_r - r);
247 g = g + color_rate * (color_g - g);
248 b = b + color_rate * (color_b - b);
249 }
250 r = (r + color_add_r).clamp(0.0, 1.0);
251 g = (g + color_add_g).clamp(0.0, 1.0);
252 b = (b + color_add_b).clamp(0.0, 1.0);
253
254 let mut mask_alpha = sa;
255 if let Some(mask_img) = mask_img.as_ref() {
256 let mx = sx + sprite.mask_offset_x;
257 let my = sy + sprite.mask_offset_y;
258 if mx >= 0
259 && my >= 0
260 && mx < mask_img.width as i32
261 && my < mask_img.height as i32
262 {
263 let mi = ((my as u32 * mask_img.width + mx as u32) * 4) as usize;
264 let mr = mask_img.rgba[mi] as f32 / 255.0;
265 let mg = mask_img.rgba[mi + 1] as f32 / 255.0;
266 let mb = mask_img.rgba[mi + 2] as f32 / 255.0;
267 let ma = mask_img.rgba[mi + 3] as f32 / 255.0;
268 mask_alpha *=
269 ((mr * 0.299 + mg * 0.587 + mb * 0.114) * ma).clamp(0.0, 1.0);
270 } else {
271 mask_alpha = 0.0;
272 }
273 }
274 if sprite.mask_mode == 1 {
275 mask_alpha = sr * 0.299 + sg * 0.587 + sb * 0.114;
276 }
277 if sprite.alpha_test && mask_alpha <= 0.0 {
278 continue;
279 }
280 let a = (mask_alpha * alpha * tr).clamp(0.0, 1.0);
281 if a <= 0.0 {
282 continue;
283 }
284
285 let src_premul = [r * a, g * a, b * a, a];
286 let dst_idx = ((y as u32 * width + x as u32) * 4) as usize;
287 let dr = out[dst_idx] as f32 / 255.0;
288 let dg = out[dst_idx + 1] as f32 / 255.0;
289 let db = out[dst_idx + 2] as f32 / 255.0;
290 let da = out[dst_idx + 3] as f32 / 255.0;
291
292 let dst = [dr, dg, db, da];
293 let blended = if sprite.alpha_blend {
294 blend_pixel(dst, src_premul, sprite.blend)
295 } else {
296 src_premul
297 };
298
299 out[dst_idx] = (blended[0].clamp(0.0, 1.0) * 255.0).round() as u8;
300 out[dst_idx + 1] = (blended[1].clamp(0.0, 1.0) * 255.0).round() as u8;
301 out[dst_idx + 2] = (blended[2].clamp(0.0, 1.0) * 255.0).round() as u8;
302 out[dst_idx + 3] = (blended[3].clamp(0.0, 1.0) * 255.0).round() as u8;
303 }
304 }
305 }
306 }
307
308 RgbaImage {
309 width,
310 height,
311 center_x: 0,
312 center_y: 0,
313 rgba: out,
314 }
315}
316
317fn sample_rgba_norm(img: &RgbaImage, u: f32, v: f32) -> [f32; 4] {
318 if !(0.0..=1.0).contains(&u) || !(0.0..=1.0).contains(&v) {
319 return [0.0, 0.0, 0.0, 0.0];
320 }
321 let x = (u * img.width as f32)
322 .floor()
323 .clamp(0.0, (img.width.saturating_sub(1)) as f32) as u32;
324 let y = (v * img.height as f32)
325 .floor()
326 .clamp(0.0, (img.height.saturating_sub(1)) as f32) as u32;
327 let i = ((y * img.width + x) * 4) as usize;
328 [
329 img.rgba[i] as f32 / 255.0,
330 img.rgba[i + 1] as f32 / 255.0,
331 img.rgba[i + 2] as f32 / 255.0,
332 img.rgba[i + 3] as f32 / 255.0,
333 ]
334}
335
336fn raster_amp(progress: f32) -> f32 {
337 let rp = (1.0 - progress).clamp(1e-4, 1.0);
338 1.0 - (((1.0 - rp) * 100.0).log10() + 1.0) / 3.0
339}
340
341fn sample_base_with_wipe(
342 sprite: &crate::layer::Sprite,
343 img: &RgbaImage,
344 wipe_src_img: Option<&RgbaImage>,
345 u_px: f32,
346 v_px: f32,
347) -> [f32; 4] {
348 let mut u = (u_px / img.width as f32).clamp(0.0, 1.0);
349 let mut v = (v_px / img.height as f32).clamp(0.0, 1.0);
350 match sprite.wipe_fx_mode {
351 1 => {
352 let cu = sprite.wipe_fx_params[0].max(1e-5);
353 let cv = (cu * sprite.wipe_fx_params[1].max(1e-5)).max(1e-5);
354 u = (u / cu).floor() * cu;
355 v = (v / cv).floor() * cv;
356 sample_rgba_norm(img, u, v)
357 }
358 2 | 3 => {
359 let fraction_num = sprite.wipe_fx_params[0].max(1.0);
360 let wave_num = sprite.wipe_fx_params[1];
361 let power = sprite.wipe_fx_params[2];
362 let progress = sprite.wipe_fx_params[3];
363 let mut tex_coord_for_sin = if sprite.wipe_fx_mode == 2 {
364 v * fraction_num
365 } else {
366 u * fraction_num
367 };
368 tex_coord_for_sin = tex_coord_for_sin.fract();
369 tex_coord_for_sin = (tex_coord_for_sin - fraction_num * 0.1) / fraction_num;
370 let delta = (std::f32::consts::PI * progress * power
371 + tex_coord_for_sin * std::f32::consts::PI * wave_num)
372 .sin()
373 * raster_amp(progress);
374 if sprite.wipe_fx_mode == 2 {
375 u += delta;
376 } else {
377 v += delta;
378 }
379 sample_rgba_norm(img, u, v)
380 }
381 4 => {
382 let center = (sprite.wipe_fx_params[0], sprite.wipe_fx_params[1]);
383 let blur_power = sprite.wipe_fx_params[2];
384 let blur_coeff = sprite.wipe_fx_params[3].max(0.0);
385 let mut dir = (center.0 - u, center.1 - v);
386 let len = (dir.0 * dir.0 + dir.1 * dir.1).sqrt();
387 if len <= 1e-5 || blur_power <= 1e-5 {
388 return sample_rgba_norm(img, u, v);
389 }
390 let texel = 1.0 / (img.width.max(img.height).max(1) as f32);
391 dir.0 = dir.0 / len * texel * blur_power * len * blur_coeff;
392 dir.1 = dir.1 / len * texel * blur_power * len * blur_coeff;
393 let taps = [
394 (0.0, 0.19f32),
395 (1.0, 0.17),
396 (2.0, 0.15),
397 (3.0, 0.13),
398 (4.0, 0.11),
399 (5.0, 0.09),
400 (6.0, 0.07),
401 (7.0, 0.05),
402 (8.0, 0.03),
403 (9.0, 0.01),
404 ];
405 let mut out = [0.0f32; 4];
406 for (k, w) in taps {
407 let s = sample_rgba_norm(img, u + dir.0 * k, v + dir.1 * k);
408 for i in 0..4 {
409 out[i] += s[i] * w;
410 }
411 }
412 out
413 }
414 5 => {
415 let mut c = sample_rgba_norm(img, u, v);
416 let fade = sprite.wipe_fx_params[0].clamp(0.0, 1.0);
417 let progress = sprite.wipe_fx_params[1].clamp(0.0, 1.0);
418 let brightness = 0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2];
419 if brightness > progress {
420 c[3] *= (fade * (1.0 - progress)).clamp(0.0, 1.0);
421 }
422 c
423 }
424 6 => {
425 let mut c = sample_rgba_norm(img, u, v);
426 let fade = sprite.wipe_fx_params[0].clamp(0.0, 1.0);
427 let progress = sprite.wipe_fx_params[1].clamp(0.0, 1.0);
428 let brightness = 0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2];
429 if brightness < 1.0 - progress {
430 c[3] *= (fade * (1.0 - progress)).clamp(0.0, 1.0);
431 }
432 c
433 }
434 10 => {
435 let Some(src) = wipe_src_img else {
436 return sample_rgba_norm(img, u, v);
437 };
438 let cut = sprite.wipe_fx_params[0].max(1e-5);
439 let aspect = sprite.wipe_fx_params[1].max(1e-5);
440 let progress = sprite.wipe_fx_params[2].clamp(0.0, 1.0);
441 let variant = sprite.wipe_fx_params[3] as i32;
442 let cu = cut;
443 let cv = (cut * aspect).max(1e-5);
444 let uu = (u / cu).floor() * cu;
445 let vv = (v / cv).floor() * cv;
446 let oldc = sample_rgba_norm(src, uu, vv);
447 let newc = sample_rgba_norm(img, uu, vv);
448 if variant == 230 {
449 if progress < 0.5 {
450 oldc
451 } else {
452 newc
453 }
454 } else if sprite.tonecurve_sat < 0.5 {
455 mix_rgba(newc, oldc, 1.0 - progress)
456 } else {
457 mix_rgba(oldc, newc, progress)
458 }
459 }
460 11 | 12 => {
461 let Some(src) = wipe_src_img else {
462 return sample_rgba_norm(img, u, v);
463 };
464 let fraction_num = sprite.wipe_fx_params[0].max(1.0);
465 let wave_num = sprite.wipe_fx_params[1];
466 let power = sprite.wipe_fx_params[2];
467 let progress = sprite.wipe_fx_params[3].clamp(0.0, 1.0);
468 let mut tex_coord_for_sin = if sprite.wipe_fx_mode == 11 {
469 v * fraction_num
470 } else {
471 u * fraction_num
472 };
473 tex_coord_for_sin = tex_coord_for_sin.fract();
474 tex_coord_for_sin = (tex_coord_for_sin - fraction_num * 0.1) / fraction_num;
475 let delta = (std::f32::consts::PI * progress * power
476 + tex_coord_for_sin * std::f32::consts::PI * wave_num)
477 .sin()
478 * raster_amp(progress);
479 let (nu, nv) = if sprite.wipe_fx_mode == 11 {
480 (u + delta, v)
481 } else {
482 (u, v + delta)
483 };
484 let oldc = sample_rgba_norm(src, nu, nv);
485 let newc = sample_rgba_norm(img, nu, nv);
486 mix_rgba(oldc, newc, progress)
487 }
488 13 => {
489 let Some(src) = wipe_src_img else {
490 return sample_rgba_norm(img, u, v);
491 };
492 let center = (sprite.wipe_fx_params[0], sprite.wipe_fx_params[1]);
493 let blur_power = sprite.wipe_fx_params[2];
494 let blur_coeff = sprite.wipe_fx_params[3].max(0.0);
495 let mixv = sprite.tonecurve_row.clamp(0.0, 1.0);
496 let oldc = sample_explosion_src(src, u, v, center, blur_power, blur_coeff);
497 let newc = sample_explosion_src(img, u, v, center, blur_power, blur_coeff);
498 mix_rgba(oldc, newc, mixv)
499 }
500 _ => sample_rgba_norm(img, u, v),
501 }
502}
503
504fn mix_rgba(a: [f32; 4], b: [f32; 4], t: f32) -> [f32; 4] {
505 let tt = t.clamp(0.0, 1.0);
506 [
507 a[0] * (1.0 - tt) + b[0] * tt,
508 a[1] * (1.0 - tt) + b[1] * tt,
509 a[2] * (1.0 - tt) + b[2] * tt,
510 a[3] * (1.0 - tt) + b[3] * tt,
511 ]
512}
513
514fn sample_explosion_src(
515 img: &RgbaImage,
516 u: f32,
517 v: f32,
518 center: (f32, f32),
519 blur_power: f32,
520 blur_coeff: f32,
521) -> [f32; 4] {
522 let mut dir = (center.0 - u, center.1 - v);
523 let len = (dir.0 * dir.0 + dir.1 * dir.1).sqrt();
524 if len <= 1e-5 || blur_power <= 1e-5 {
525 return sample_rgba_norm(img, u, v);
526 }
527 let texel = 1.0 / (img.width.max(img.height).max(1) as f32);
528 dir.0 = dir.0 / len * texel * blur_power * len * blur_coeff;
529 dir.1 = dir.1 / len * texel * blur_power * len * blur_coeff;
530 let taps = [
531 (0.0, 0.19f32),
532 (1.0, 0.17),
533 (2.0, 0.15),
534 (3.0, 0.13),
535 (4.0, 0.11),
536 (5.0, 0.09),
537 (6.0, 0.07),
538 (7.0, 0.05),
539 (8.0, 0.03),
540 (9.0, 0.01),
541 ];
542 let mut out = [0.0f32; 4];
543 for (k, w) in taps {
544 let s = sample_rgba_norm(img, u + dir.0 * k, v + dir.1 * k);
545 for i in 0..4 {
546 out[i] += s[i] * w;
547 }
548 }
549 out
550}
551
552fn edge(a: (f32, f32), b: (f32, f32), p: (f32, f32)) -> f32 {
553 (p.0 - a.0) * (b.1 - a.1) - (p.1 - a.1) * (b.0 - a.0)
554}
555
556fn src_clip_rect(
557 clip: Option<ClipRect>,
558 img_w: u32,
559 img_h: u32,
560) -> Result<(f32, f32, f32, f32), ()> {
561 if let Some(c) = clip {
562 let mut left = c.left.max(0) as f32;
563 let mut top = c.top.max(0) as f32;
564 let mut right = c.right.max(0) as f32;
565 let mut bottom = c.bottom.max(0) as f32;
566 let max_w = img_w as f32;
567 let max_h = img_h as f32;
568 left = left.min(max_w);
569 right = right.min(max_w);
570 top = top.min(max_h);
571 bottom = bottom.min(max_h);
572 if right <= left || bottom <= top {
573 return Ok((0.0, 0.0, max_w, max_h));
574 }
575 Ok((left, top, right, bottom))
576 } else {
577 Ok((0.0, 0.0, img_w as f32, img_h as f32))
578 }
579}
580
581fn dst_scissor_rect(clip: Option<ClipRect>, win_w: u32, win_h: u32) -> Option<ScissorRect> {
582 let c = clip?;
583 let mut left = c.left.max(0) as i64;
584 let mut top = c.top.max(0) as i64;
585 let mut right = c.right.max(0) as i64;
586 let mut bottom = c.bottom.max(0) as i64;
587 let max_w = win_w as i64;
588 let max_h = win_h as i64;
589 left = left.min(max_w);
590 right = right.min(max_w);
591 top = top.min(max_h);
592 bottom = bottom.min(max_h);
593 if right <= left || bottom <= top {
594 return Some(ScissorRect {
595 x: 0,
596 y: 0,
597 w: 0,
598 h: 0,
599 });
600 }
601 Some(ScissorRect {
602 x: left as u32,
603 y: top as u32,
604 w: (right - left) as u32,
605 h: (bottom - top) as u32,
606 })
607}
608
609#[derive(Debug, Copy, Clone)]
610struct ScissorRect {
611 x: u32,
612 y: u32,
613 w: u32,
614 h: u32,
615}
616
617fn blend_pixel(dst: [f32; 4], src: [f32; 4], blend: SpriteBlend) -> [f32; 4] {
618 let sa = src[3].clamp(0.0, 1.0);
619 let da = dst[3].clamp(0.0, 1.0);
620 let out_a = sa + da * (1.0 - sa);
621 match blend {
622 SpriteBlend::Normal => [
623 src[0] + dst[0] * (1.0 - sa),
624 src[1] + dst[1] * (1.0 - sa),
625 src[2] + dst[2] * (1.0 - sa),
626 out_a,
627 ],
628 SpriteBlend::Add => [
629 (src[0] + dst[0]).min(1.0),
630 (src[1] + dst[1]).min(1.0),
631 (src[2] + dst[2]).min(1.0),
632 out_a,
633 ],
634 SpriteBlend::Sub => [
635 (dst[0] - src[0]).max(0.0),
636 (dst[1] - src[1]).max(0.0),
637 (dst[2] - src[2]).max(0.0),
638 out_a,
639 ],
640 SpriteBlend::Mul => [
641 dst[0] * ((1.0 - sa) + src[0]),
642 dst[1] * ((1.0 - sa) + src[1]),
643 dst[2] * ((1.0 - sa) + src[2]),
644 out_a,
645 ],
646 SpriteBlend::Screen => [
647 src[0] * (1.0 - dst[0]) + dst[0],
648 src[1] * (1.0 - dst[1]) + dst[1],
649 src[2] * (1.0 - dst[2]) + dst[2],
650 out_a,
651 ],
652 SpriteBlend::Overlay => {
653 let overlay = |d: f32, s: f32| {
654 if d <= 0.5 {
655 2.0 * d * s
656 } else {
657 1.0 - 2.0 * (1.0 - d) * (1.0 - s)
658 }
659 };
660 [
661 overlay(dst[0], src[0]) * sa + dst[0] * (1.0 - sa),
662 overlay(dst[1], src[1]) * sa + dst[1] * (1.0 - sa),
663 overlay(dst[2], src[2]) * sa + dst[2] * (1.0 - sa),
664 out_a,
665 ]
666 }
667 }
668}