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 fn quantize_channel_value(value: u8, levels: usize) -> u8 {
574 if levels <= 1 {
575 return 0;
579 }
580 let steps = (levels - 1) as f64;
581 let level = (f64::from(value) / 255.0 * steps).round();
582 (level / steps * 255.0).round().clamp(0.0, 255.0) as u8
583 }
584
585 pub fn quantize(data: &mut [u8], num_colors: usize) -> WasmResult<()> {
592 if num_colors == 0 {
593 return Err(WasmError::Canvas(CanvasError::InvalidDimensions {
594 width: 0,
595 height: 0,
596 reason: "Number of colors must be greater than 0".to_string(),
597 }));
598 }
599 for chunk in data.chunks_exact_mut(4) {
600 chunk[0] = Self::quantize_channel_value(chunk[0], num_colors);
601 chunk[1] = Self::quantize_channel_value(chunk[1], num_colors);
602 chunk[2] = Self::quantize_channel_value(chunk[2], num_colors);
603 }
604 Ok(())
605 }
606 pub fn posterize(data: &mut [u8], levels: u8) {
611 if levels == 0 {
612 return;
613 }
614 let levels = levels as usize;
615 for chunk in data.chunks_exact_mut(4) {
616 for i in 0..3 {
617 chunk[i] = Self::quantize_channel_value(chunk[i], levels);
618 }
619 }
620 }
621}
622pub struct ChannelOps;
624impl ChannelOps {
625 pub fn swap_channels(data: &mut [u8], channel_a: usize, channel_b: usize) {
627 if channel_a >= 3 || channel_b >= 3 {
628 return;
629 }
630 for chunk in data.chunks_exact_mut(4) {
631 chunk.swap(channel_a, channel_b);
632 }
633 }
634 pub fn extract_channel(data: &[u8], channel: usize) -> Vec<u8> {
636 if channel >= 3 {
637 return Vec::new();
638 }
639 let mut result = Vec::with_capacity(data.len());
640 for chunk in data.chunks_exact(4) {
641 let value = chunk[channel];
642 result.extend_from_slice(&[value, value, value, 255]);
643 }
644 result
645 }
646 pub fn mix_channels(data: &mut [u8], r_mix: [f64; 3], g_mix: [f64; 3], b_mix: [f64; 3]) {
648 for chunk in data.chunks_exact_mut(4) {
649 let r = f64::from(chunk[0]);
650 let g = f64::from(chunk[1]);
651 let b = f64::from(chunk[2]);
652 let new_r = (r * r_mix[0] + g * r_mix[1] + b * r_mix[2]).clamp(0.0, 255.0);
653 let new_g = (r * g_mix[0] + g * g_mix[1] + b * g_mix[2]).clamp(0.0, 255.0);
654 let new_b = (r * b_mix[0] + g * b_mix[1] + b * b_mix[2]).clamp(0.0, 255.0);
655 chunk[0] = new_r as u8;
656 chunk[1] = new_g as u8;
657 chunk[2] = new_b as u8;
658 }
659 }
660}
661#[wasm_bindgen]
663pub struct WasmColorPalette {
664 palette: ColorPalette,
665}
666#[wasm_bindgen]
667impl WasmColorPalette {
668 #[wasm_bindgen(js_name = createViridis)]
670 pub fn create_viridis() -> Self {
671 Self {
672 palette: ColorPalette::viridis(),
673 }
674 }
675 #[wasm_bindgen(js_name = createPlasma)]
677 pub fn create_plasma() -> Self {
678 Self {
679 palette: ColorPalette::plasma(),
680 }
681 }
682 #[wasm_bindgen(js_name = createTerrain)]
684 pub fn create_terrain() -> Self {
685 Self {
686 palette: ColorPalette::terrain(),
687 }
688 }
689 #[wasm_bindgen(js_name = applyToGrayscale)]
691 pub fn apply_to_grayscale(&self, data: &mut [u8]) -> Result<(), JsValue> {
692 self.palette
693 .apply_to_grayscale(data)
694 .map_err(|e| JsValue::from_str(&e.to_string()))
695 }
696}
697#[cfg(test)]
698mod tests {
699 use super::*;
700 #[test]
701 fn test_color_palette_interpolation() {
702 let mut palette = ColorPalette::new("test");
703 palette.add_entry(0.0, Rgb::new(0, 0, 0));
704 palette.add_entry(1.0, Rgb::new(255, 255, 255));
705 let mid = palette.interpolate(0.5).expect("Interpolation failed");
706 assert!(mid.r > 120 && mid.r < 135);
707 assert!(mid.g > 120 && mid.g < 135);
708 assert!(mid.b > 120 && mid.b < 135);
709 }
710 #[test]
711 fn test_gradient_generator() {
712 let gradient_gen = GradientGenerator::new(Rgb::new(0, 0, 0), Rgb::new(255, 255, 255), 11);
713 let gradient = gradient_gen.generate();
714 assert_eq!(gradient.len(), 11);
715 assert_eq!(gradient[0], Rgb::new(0, 0, 0));
716 assert_eq!(gradient[10], Rgb::new(255, 255, 255));
717 }
718 #[test]
719 fn test_color_correction_identity() {
720 let matrix = ColorCorrectionMatrix::identity();
721 let color = Rgb::new(128, 64, 192);
722 let result = matrix.apply(color);
723 assert_eq!(result, color);
724 }
725 #[test]
726 fn test_color_correction_brightness() {
727 let matrix = ColorCorrectionMatrix::brightness(1.5);
728 let color = Rgb::new(100, 100, 100);
729 let result = matrix.apply(color);
730 assert!(result.r > 100);
731 assert!(result.g > 100);
732 assert!(result.b > 100);
733 }
734 #[test]
735 fn test_color_temperature() {
736 let color = Rgb::new(128, 128, 128);
737 let warm = ColorTemperature::adjust(color, 0.5);
738 let cool = ColorTemperature::adjust(color, -0.5);
739 assert!(warm.r > color.r);
740 assert!(cool.b > color.b);
741 }
742 #[test]
743 fn test_channel_swap() {
744 let mut data = vec![255, 0, 0, 255];
745 ChannelOps::swap_channels(&mut data, 0, 2);
746 assert_eq!(data[0], 0);
747 assert_eq!(data[2], 255);
748 }
749 #[test]
750 fn test_color_quantization() {
751 let mut data = vec![100, 150, 200, 255];
752 ColorQuantizer::quantize(&mut data, 4).expect("Quantization failed");
753 assert_eq!(data[0], 85);
756 assert_eq!(data[1], 170);
757 assert_eq!(data[2], 170);
758 assert_eq!(data[3], 255, "alpha channel must be untouched");
759 }
760 #[test]
761 fn test_color_quantization_reaches_full_range() {
762 let mut data = vec![255, 255, 255, 255, 0, 0, 0, 255];
767 ColorQuantizer::quantize(&mut data, 4).expect("Quantization failed");
768 assert_eq!(&data[0..3], &[255, 255, 255], "white must stay 255");
769 assert_eq!(&data[4..7], &[0, 0, 0], "black must stay 0");
770 }
771 #[test]
772 fn test_color_quantization_single_color() {
773 let mut data = vec![10, 128, 255, 255];
776 ColorQuantizer::quantize(&mut data, 1).expect("Quantization failed");
777 assert_eq!(&data[0..3], &[0, 0, 0]);
778 }
779 #[test]
780 fn test_color_quantization_rejects_zero_colors() {
781 let mut data = vec![10, 128, 255, 255];
782 assert!(ColorQuantizer::quantize(&mut data, 0).is_err());
783 }
784 #[test]
785 fn test_posterize() {
786 let mut data = vec![10, 50, 100, 255, 150, 200, 250, 255];
787 ColorQuantizer::posterize(&mut data, 4);
788 assert_eq!(&data[0..4], &[0, 85, 85, 255]);
790 assert_eq!(&data[4..8], &[170, 170, 255, 255]);
791 }
792 #[test]
793 fn test_posterize_reaches_full_range() {
794 let mut data = vec![255, 255, 255, 255, 0, 0, 0, 255];
795 ColorQuantizer::posterize(&mut data, 4);
796 assert_eq!(&data[0..3], &[255, 255, 255]);
797 assert_eq!(&data[4..7], &[0, 0, 0]);
798 }
799 #[test]
800 fn test_posterize_zero_levels_is_noop() {
801 let mut data = vec![10, 50, 100, 255];
802 ColorQuantizer::posterize(&mut data, 0);
803 assert_eq!(data, vec![10, 50, 100, 255]);
804 }
805}