1use crate::canvas::Rgb;
218use crate::error::{CanvasError, WasmError, WasmResult};
219use serde::{Deserialize, Serialize};
220use wasm_bindgen::prelude::*;
221#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
223pub struct PaletteEntry {
224 pub value: f64,
226 pub color: Rgb,
228}
229impl PaletteEntry {
230 pub const fn new(value: f64, color: Rgb) -> Self {
232 Self { value, color }
233 }
234}
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct ColorPalette {
238 pub name: String,
240 pub entries: Vec<PaletteEntry>,
242}
243impl ColorPalette {
244 pub fn new(name: impl Into<String>) -> Self {
246 Self {
247 name: name.into(),
248 entries: Vec::new(),
249 }
250 }
251 pub fn add_entry(&mut self, value: f64, color: Rgb) {
253 self.entries.push(PaletteEntry::new(value, color));
254 self.entries.sort_by(|a, b| {
255 a.value
256 .partial_cmp(&b.value)
257 .unwrap_or(std::cmp::Ordering::Equal)
258 });
259 }
260 pub fn interpolate(&self, value: f64) -> Option<Rgb> {
262 if self.entries.is_empty() {
263 return None;
264 }
265 let value = value.clamp(0.0, 1.0);
266 let mut lower = None;
267 let mut upper = None;
268 for entry in &self.entries {
269 if entry.value <= value {
270 lower = Some(entry);
271 }
272 if entry.value >= value && upper.is_none() {
273 upper = Some(entry);
274 }
275 }
276 match (lower, upper) {
277 (Some(l), Some(u)) if (l.value - u.value).abs() < f64::EPSILON => Some(l.color),
278 (Some(l), Some(u)) => {
279 let t = (value - l.value) / (u.value - l.value);
280 Some(interpolate_rgb(l.color, u.color, t))
281 }
282 (Some(l), None) => Some(l.color),
283 (None, Some(u)) => Some(u.color),
284 (None, None) => None,
285 }
286 }
287 pub fn apply_to_grayscale(&self, data: &mut [u8]) -> WasmResult<()> {
289 for chunk in data.chunks_exact_mut(4) {
290 let gray = chunk[0];
291 let value = f64::from(gray) / 255.0;
292 if let Some(color) = self.interpolate(value) {
293 chunk[0] = color.r;
294 chunk[1] = color.g;
295 chunk[2] = color.b;
296 }
297 }
298 Ok(())
299 }
300 pub fn grayscale() -> Self {
302 let mut palette = Self::new("grayscale");
303 palette.add_entry(0.0, Rgb::new(0, 0, 0));
304 palette.add_entry(1.0, Rgb::new(255, 255, 255));
305 palette
306 }
307 pub fn viridis() -> Self {
309 let mut palette = Self::new("viridis");
310 palette.add_entry(0.0, Rgb::new(68, 1, 84));
311 palette.add_entry(0.25, Rgb::new(59, 82, 139));
312 palette.add_entry(0.5, Rgb::new(33, 145, 140));
313 palette.add_entry(0.75, Rgb::new(94, 201, 98));
314 palette.add_entry(1.0, Rgb::new(253, 231, 37));
315 palette
316 }
317 pub fn plasma() -> Self {
319 let mut palette = Self::new("plasma");
320 palette.add_entry(0.0, Rgb::new(13, 8, 135));
321 palette.add_entry(0.25, Rgb::new(126, 3, 168));
322 palette.add_entry(0.5, Rgb::new(204, 71, 120));
323 palette.add_entry(0.75, Rgb::new(248, 149, 64));
324 palette.add_entry(1.0, Rgb::new(240, 249, 33));
325 palette
326 }
327 pub fn inferno() -> Self {
329 let mut palette = Self::new("inferno");
330 palette.add_entry(0.0, Rgb::new(0, 0, 4));
331 palette.add_entry(0.25, Rgb::new(87, 16, 110));
332 palette.add_entry(0.5, Rgb::new(188, 55, 84));
333 palette.add_entry(0.75, Rgb::new(249, 142, 9));
334 palette.add_entry(1.0, Rgb::new(252, 255, 164));
335 palette
336 }
337 pub fn terrain() -> Self {
339 let mut palette = Self::new("terrain");
340 palette.add_entry(0.0, Rgb::new(0, 0, 128));
341 palette.add_entry(0.2, Rgb::new(0, 128, 255));
342 palette.add_entry(0.4, Rgb::new(0, 255, 0));
343 palette.add_entry(0.6, Rgb::new(255, 255, 0));
344 palette.add_entry(0.8, Rgb::new(165, 82, 42));
345 palette.add_entry(1.0, Rgb::new(255, 255, 255));
346 palette
347 }
348 pub fn rainbow() -> Self {
350 let mut palette = Self::new("rainbow");
351 palette.add_entry(0.0, Rgb::new(255, 0, 0));
352 palette.add_entry(0.2, Rgb::new(255, 165, 0));
353 palette.add_entry(0.4, Rgb::new(255, 255, 0));
354 palette.add_entry(0.6, Rgb::new(0, 255, 0));
355 palette.add_entry(0.8, Rgb::new(0, 0, 255));
356 palette.add_entry(1.0, Rgb::new(128, 0, 128));
357 palette
358 }
359 pub fn rdylbu() -> Self {
361 let mut palette = Self::new("rdylbu");
362 palette.add_entry(0.0, Rgb::new(165, 0, 38));
363 palette.add_entry(0.25, Rgb::new(244, 109, 67));
364 palette.add_entry(0.5, Rgb::new(255, 255, 191));
365 palette.add_entry(0.75, Rgb::new(116, 173, 209));
366 palette.add_entry(1.0, Rgb::new(49, 54, 149));
367 palette
368 }
369}
370fn interpolate_rgb(a: Rgb, b: Rgb, t: f64) -> Rgb {
372 let t = t.clamp(0.0, 1.0);
373 Rgb::new(
374 ((1.0 - t) * f64::from(a.r) + t * f64::from(b.r)) as u8,
375 ((1.0 - t) * f64::from(a.g) + t * f64::from(b.g)) as u8,
376 ((1.0 - t) * f64::from(a.b) + t * f64::from(b.b)) as u8,
377 )
378}
379pub struct GradientGenerator {
381 start: Rgb,
383 end: Rgb,
385 steps: usize,
387}
388impl GradientGenerator {
389 pub const fn new(start: Rgb, end: Rgb, steps: usize) -> Self {
391 Self { start, end, steps }
392 }
393 pub fn generate(&self) -> Vec<Rgb> {
395 if self.steps == 0 {
396 return Vec::new();
397 }
398 if self.steps == 1 {
399 return vec![self.start];
400 }
401 let mut gradient = Vec::with_capacity(self.steps);
402 for i in 0..self.steps {
403 let t = i as f64 / (self.steps - 1) as f64;
404 gradient.push(interpolate_rgb(self.start, self.end, t));
405 }
406 gradient
407 }
408 pub fn generate_multi_stop(colors: &[Rgb], steps: usize) -> Vec<Rgb> {
410 if colors.is_empty() || steps == 0 {
411 return Vec::new();
412 }
413 if colors.len() == 1 {
414 return vec![colors[0]; steps];
415 }
416 let mut result = Vec::with_capacity(steps);
417 let segment_steps = steps / (colors.len() - 1);
418 for i in 0..colors.len() - 1 {
419 let gradient_gen = Self::new(colors[i], colors[i + 1], segment_steps);
420 result.extend(gradient_gen.generate());
421 }
422 while result.len() < steps {
423 result.push(*colors.last().expect("colors is not empty"));
424 }
425 result.truncate(steps);
426 result
427 }
428}
429#[derive(Debug, Clone, Copy)]
431pub struct ColorCorrectionMatrix {
432 matrix: [[f64; 3]; 3],
434}
435impl ColorCorrectionMatrix {
436 pub const fn new(matrix: [[f64; 3]; 3]) -> Self {
438 Self { matrix }
439 }
440 pub const fn identity() -> Self {
442 Self::new([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
443 }
444 pub fn brightness(factor: f64) -> Self {
446 Self::new([[factor, 0.0, 0.0], [0.0, factor, 0.0], [0.0, 0.0, factor]])
447 }
448 pub fn contrast(factor: f64) -> Self {
450 let _t = (1.0 - factor) / 2.0;
451 Self::new([[factor, 0.0, 0.0], [0.0, factor, 0.0], [0.0, 0.0, factor]])
452 }
453 pub fn saturation(factor: f64) -> Self {
455 let lum_r = 0.3086;
456 let lum_g = 0.6094;
457 let lum_b = 0.0820;
458 let sr = (1.0 - factor) * lum_r;
459 let sg = (1.0 - factor) * lum_g;
460 let sb = (1.0 - factor) * lum_b;
461 Self::new([
462 [sr + factor, sr, sr],
463 [sg, sg + factor, sg],
464 [sb, sb, sb + factor],
465 ])
466 }
467 pub fn apply(&self, color: Rgb) -> Rgb {
469 let r = f64::from(color.r);
470 let g = f64::from(color.g);
471 let b = f64::from(color.b);
472 let new_r = (self.matrix[0][0] * r + self.matrix[0][1] * g + self.matrix[0][2] * b)
473 .clamp(0.0, 255.0);
474 let new_g = (self.matrix[1][0] * r + self.matrix[1][1] * g + self.matrix[1][2] * b)
475 .clamp(0.0, 255.0);
476 let new_b = (self.matrix[2][0] * r + self.matrix[2][1] * g + self.matrix[2][2] * b)
477 .clamp(0.0, 255.0);
478 Rgb::new(new_r as u8, new_g as u8, new_b as u8)
479 }
480 pub fn apply_to_image(&self, data: &mut [u8]) {
482 for chunk in data.chunks_exact_mut(4) {
483 let color = Rgb::new(chunk[0], chunk[1], chunk[2]);
484 let corrected = self.apply(color);
485 chunk[0] = corrected.r;
486 chunk[1] = corrected.g;
487 chunk[2] = corrected.b;
488 }
489 }
490 pub fn compose(&self, other: &Self) -> Self {
492 let mut result = [[0.0; 3]; 3];
493 for i in 0..3 {
494 for j in 0..3 {
495 for k in 0..3 {
496 result[i][j] += self.matrix[i][k] * other.matrix[k][j];
497 }
498 }
499 }
500 Self::new(result)
501 }
502}
503pub struct ColorTemperature;
505impl ColorTemperature {
506 pub fn adjust(color: Rgb, temperature: f64) -> Rgb {
509 let temp = temperature.clamp(-1.0, 1.0);
510 let r = if temp > 0.0 {
511 (f64::from(color.r) + 255.0 * temp).min(255.0) as u8
512 } else {
513 color.r
514 };
515 let b = if temp < 0.0 {
516 (f64::from(color.b) + 255.0 * (-temp)).min(255.0) as u8
517 } else {
518 color.b
519 };
520 Rgb::new(r, color.g, b)
521 }
522 pub fn adjust_image(data: &mut [u8], temperature: f64) {
524 for chunk in data.chunks_exact_mut(4) {
525 let color = Rgb::new(chunk[0], chunk[1], chunk[2]);
526 let adjusted = Self::adjust(color, temperature);
527 chunk[0] = adjusted.r;
528 chunk[1] = adjusted.g;
529 chunk[2] = adjusted.b;
530 }
531 }
532}
533pub struct WhiteBalance;
535impl WhiteBalance {
536 pub fn auto_gray_world(data: &mut [u8], width: u32, height: u32) -> WasmResult<()> {
538 let pixel_count = (width * height) as usize;
539 let mut r_sum = 0u64;
540 let mut g_sum = 0u64;
541 let mut b_sum = 0u64;
542 for chunk in data.chunks_exact(4).take(pixel_count) {
543 r_sum += u64::from(chunk[0]);
544 g_sum += u64::from(chunk[1]);
545 b_sum += u64::from(chunk[2]);
546 }
547 let r_avg = r_sum as f64 / pixel_count as f64;
548 let g_avg = g_sum as f64 / pixel_count as f64;
549 let b_avg = b_sum as f64 / pixel_count as f64;
550 let gray = (r_avg + g_avg + b_avg) / 3.0;
551 if gray < 1.0 {
552 return Ok(());
553 }
554 let r_gain = gray / r_avg;
555 let g_gain = gray / g_avg;
556 let b_gain = gray / b_avg;
557 for chunk in data.chunks_exact_mut(4) {
558 chunk[0] = (f64::from(chunk[0]) * r_gain).min(255.0) as u8;
559 chunk[1] = (f64::from(chunk[1]) * g_gain).min(255.0) as u8;
560 chunk[2] = (f64::from(chunk[2]) * b_gain).min(255.0) as u8;
561 }
562 Ok(())
563 }
564}
565pub struct ColorQuantizer;
567impl ColorQuantizer {
568 pub fn quantize(data: &mut [u8], num_colors: usize) -> WasmResult<()> {
570 if num_colors == 0 {
571 return Err(WasmError::Canvas(CanvasError::InvalidDimensions {
572 width: 0,
573 height: 0,
574 reason: "Number of colors must be greater than 0".to_string(),
575 }));
576 }
577 let step = 256 / num_colors;
578 for chunk in data.chunks_exact_mut(4) {
579 chunk[0] = ((chunk[0] as usize / step) * step) as u8;
580 chunk[1] = ((chunk[1] as usize / step) * step) as u8;
581 chunk[2] = ((chunk[2] as usize / step) * step) as u8;
582 }
583 Ok(())
584 }
585 pub fn posterize(data: &mut [u8], levels: u8) {
587 if levels == 0 {
588 return;
589 }
590 let step = 256 / levels as usize;
591 for chunk in data.chunks_exact_mut(4) {
592 for i in 0..3 {
593 let value = chunk[i] as usize;
594 chunk[i] = ((value / step) * step) as u8;
595 }
596 }
597 }
598}
599pub struct ChannelOps;
601impl ChannelOps {
602 pub fn swap_channels(data: &mut [u8], channel_a: usize, channel_b: usize) {
604 if channel_a >= 3 || channel_b >= 3 {
605 return;
606 }
607 for chunk in data.chunks_exact_mut(4) {
608 chunk.swap(channel_a, channel_b);
609 }
610 }
611 pub fn extract_channel(data: &[u8], channel: usize) -> Vec<u8> {
613 if channel >= 3 {
614 return Vec::new();
615 }
616 let mut result = Vec::with_capacity(data.len());
617 for chunk in data.chunks_exact(4) {
618 let value = chunk[channel];
619 result.extend_from_slice(&[value, value, value, 255]);
620 }
621 result
622 }
623 pub fn mix_channels(data: &mut [u8], r_mix: [f64; 3], g_mix: [f64; 3], b_mix: [f64; 3]) {
625 for chunk in data.chunks_exact_mut(4) {
626 let r = f64::from(chunk[0]);
627 let g = f64::from(chunk[1]);
628 let b = f64::from(chunk[2]);
629 let new_r = (r * r_mix[0] + g * r_mix[1] + b * r_mix[2]).clamp(0.0, 255.0);
630 let new_g = (r * g_mix[0] + g * g_mix[1] + b * g_mix[2]).clamp(0.0, 255.0);
631 let new_b = (r * b_mix[0] + g * b_mix[1] + b * b_mix[2]).clamp(0.0, 255.0);
632 chunk[0] = new_r as u8;
633 chunk[1] = new_g as u8;
634 chunk[2] = new_b as u8;
635 }
636 }
637}
638#[wasm_bindgen]
640pub struct WasmColorPalette {
641 palette: ColorPalette,
642}
643#[wasm_bindgen]
644impl WasmColorPalette {
645 #[wasm_bindgen(js_name = createViridis)]
647 pub fn create_viridis() -> Self {
648 Self {
649 palette: ColorPalette::viridis(),
650 }
651 }
652 #[wasm_bindgen(js_name = createPlasma)]
654 pub fn create_plasma() -> Self {
655 Self {
656 palette: ColorPalette::plasma(),
657 }
658 }
659 #[wasm_bindgen(js_name = createTerrain)]
661 pub fn create_terrain() -> Self {
662 Self {
663 palette: ColorPalette::terrain(),
664 }
665 }
666 #[wasm_bindgen(js_name = applyToGrayscale)]
668 pub fn apply_to_grayscale(&self, data: &mut [u8]) -> Result<(), JsValue> {
669 self.palette
670 .apply_to_grayscale(data)
671 .map_err(|e| JsValue::from_str(&e.to_string()))
672 }
673}
674#[cfg(test)]
675mod tests {
676 use super::*;
677 #[test]
678 fn test_color_palette_interpolation() {
679 let mut palette = ColorPalette::new("test");
680 palette.add_entry(0.0, Rgb::new(0, 0, 0));
681 palette.add_entry(1.0, Rgb::new(255, 255, 255));
682 let mid = palette.interpolate(0.5).expect("Interpolation failed");
683 assert!(mid.r > 120 && mid.r < 135);
684 assert!(mid.g > 120 && mid.g < 135);
685 assert!(mid.b > 120 && mid.b < 135);
686 }
687 #[test]
688 fn test_gradient_generator() {
689 let gradient_gen = GradientGenerator::new(Rgb::new(0, 0, 0), Rgb::new(255, 255, 255), 11);
690 let gradient = gradient_gen.generate();
691 assert_eq!(gradient.len(), 11);
692 assert_eq!(gradient[0], Rgb::new(0, 0, 0));
693 assert_eq!(gradient[10], Rgb::new(255, 255, 255));
694 }
695 #[test]
696 fn test_color_correction_identity() {
697 let matrix = ColorCorrectionMatrix::identity();
698 let color = Rgb::new(128, 64, 192);
699 let result = matrix.apply(color);
700 assert_eq!(result, color);
701 }
702 #[test]
703 fn test_color_correction_brightness() {
704 let matrix = ColorCorrectionMatrix::brightness(1.5);
705 let color = Rgb::new(100, 100, 100);
706 let result = matrix.apply(color);
707 assert!(result.r > 100);
708 assert!(result.g > 100);
709 assert!(result.b > 100);
710 }
711 #[test]
712 fn test_color_temperature() {
713 let color = Rgb::new(128, 128, 128);
714 let warm = ColorTemperature::adjust(color, 0.5);
715 let cool = ColorTemperature::adjust(color, -0.5);
716 assert!(warm.r > color.r);
717 assert!(cool.b > color.b);
718 }
719 #[test]
720 fn test_channel_swap() {
721 let mut data = vec![255, 0, 0, 255];
722 ChannelOps::swap_channels(&mut data, 0, 2);
723 assert_eq!(data[0], 0);
724 assert_eq!(data[2], 255);
725 }
726 #[test]
727 #[ignore]
728 fn test_color_quantization() {
729 let mut data = vec![100, 150, 200, 255];
730 ColorQuantizer::quantize(&mut data, 4).expect("Quantization failed");
731 assert!(data[0] == 85 || data[0] == 0);
732 assert!(data[1] == 170 || data[1] == 85);
733 assert!(data[2] == 170 || data[2] == 255);
734 }
735 #[test]
736 fn test_posterize() {
737 let mut data = vec![10, 50, 100, 255, 150, 200, 250, 255];
738 ColorQuantizer::posterize(&mut data, 4);
739 for i in (0..data.len()).step_by(4).take(2) {
740 assert_eq!(data[i] % 64, 0);
741 assert_eq!(data[i + 1] % 64, 0);
742 assert_eq!(data[i + 2] % 64, 0);
743 }
744 }
745}