1use std::f32::consts::PI;
28
29use image::{
30 Rgb,
31 RgbImage,
32};
33use libblur::{
34 stack_blur_f32,
35 AnisotropicRadius,
36 BlurImageMut,
37 FastBlurChannels,
38 ThreadingPolicy,
39};
40use palette::{
41 color_difference::EuclideanDistance,
42 Lab,
43};
44use rayon::{
45 iter::{
46 IndexedParallelIterator,
47 IntoParallelIterator,
48 IntoParallelRefIterator,
49 IntoParallelRefMutIterator,
50 ParallelIterator,
51 },
52 slice::{
53 ParallelSlice,
54 ParallelSliceMut,
55 },
56};
57use rustfft::{
58 num_complex::Complex,
59 num_traits::Zero,
60 FftPlanner,
61};
62
63use crate::{
64 dither::Sierra,
65 kmeans::parallel_kmeans,
66 private,
67 rgb_to_lab,
68 PaletteBuilder,
69 SixelEncoder,
70};
71
72pub type FocalSixelEncoderMono<D = Sierra> = SixelEncoder<FocalPaletteBuilder<2>, D>;
73pub type FocalSixelEncoder4<D = Sierra> = SixelEncoder<FocalPaletteBuilder<4>, D>;
74pub type FocalSixelEncoder8<D = Sierra> = SixelEncoder<FocalPaletteBuilder<8>, D>;
75pub type FocalSixelEncoder16<D = Sierra> = SixelEncoder<FocalPaletteBuilder<16>, D>;
76pub type FocalSixelEncoder32<D = Sierra> = SixelEncoder<FocalPaletteBuilder<32>, D>;
77pub type FocalSixelEncoder64<D = Sierra> = SixelEncoder<FocalPaletteBuilder<64>, D>;
78pub type FocalSixelEncoder128<D = Sierra> = SixelEncoder<FocalPaletteBuilder<128>, D>;
79pub type FocalSixelEncoder256<D = Sierra> = SixelEncoder<FocalPaletteBuilder<256>, D>;
80
81pub struct FocalPaletteBuilder<const PALETTE_SIZE: usize = 256>;
82
83impl<const PALETTE_SIZE: usize> private::Sealed for FocalPaletteBuilder<PALETTE_SIZE> {}
84
85impl<const PALETTE_SIZE: usize> PaletteBuilder for FocalPaletteBuilder<PALETTE_SIZE> {
86 const PALETTE_SIZE: usize = PALETTE_SIZE;
87
88 #[inline(never)]
89 fn build_palette(image: &RgbImage) -> Vec<Lab> {
90 let image_width = image.width();
91 let image_height = image.height();
92 let image = image.to_vec();
93
94 let mut pixels =
95 vec![<Lab>::new(0.0, 0.0, 0.0); image_width as usize * image_height as usize];
96
97 image.par_chunks(3).zip(&mut pixels).for_each(|(p, dest)| {
98 *dest = rgb_to_lab(Rgb([p[0], p[1], p[2]]));
99 });
100
101 let window_radius = (pixels.len() as f32).ln().clamp(2.0, 16.0) as u32 / 2;
102
103 let mut planner = FftPlanner::new();
104
105 let mut l_values = vec![0.0f32; pixels.len()];
106 let mut a_values = vec![0.0f32; pixels.len()];
107 let mut b_values = vec![0.0f32; pixels.len()];
108 pixels
109 .par_iter()
110 .copied()
111 .zip(&mut l_values)
112 .zip(&mut a_values)
113 .zip(&mut b_values)
114 .for_each(|(((lab, dest_l), dest_a), dest_b)| {
115 *dest_l = lab.l;
116 *dest_a = lab.a;
117 *dest_b = lab.b;
118 });
119
120 let l_saliency = compute_saliency(
121 &mut planner,
122 &l_values,
123 image_width,
124 image_height,
125 window_radius >> 1,
126 );
127
128 let a_saliency = compute_saliency(
129 &mut planner,
130 &a_values,
131 image_width,
132 image_height,
133 window_radius >> 1,
134 );
135
136 let b_saliency = compute_saliency(
137 &mut planner,
138 &b_values,
139 image_width,
140 image_height,
141 window_radius >> 1,
142 );
143
144 #[cfg(feature = "dump_l_saliency")]
145 {
146 let mut quant_l_saliency = vec![0; pixels.len()];
147
148 l_saliency
149 .spectral_residual
150 .par_iter()
151 .copied()
152 .zip(&mut quant_l_saliency)
153 .for_each(|(d, dest)| {
154 *dest = (d * u8::MAX as f32).round() as u8;
155 });
156 dump_intermediate("l_sr_aliency", &quant_l_saliency, image_width, image_height);
157
158 l_saliency
159 .mod_spectral_residual
160 .par_iter()
161 .copied()
162 .zip(&mut quant_l_saliency)
163 .for_each(|(d, dest)| {
164 *dest = (d * u8::MAX as f32).round() as u8;
165 });
166 dump_intermediate(
167 "l_mod_sr_saliency",
168 &quant_l_saliency,
169 image_width,
170 image_height,
171 );
172
173 l_saliency
174 .phase_spectrum
175 .par_iter()
176 .copied()
177 .zip(&mut quant_l_saliency)
178 .for_each(|(d, dest)| {
179 *dest = (d * u8::MAX as f32).round() as u8;
180 });
181 dump_intermediate(
182 "l_phase_saliency",
183 &quant_l_saliency,
184 image_width,
185 image_height,
186 );
187
188 l_saliency
189 .amplitude_spectrum
190 .par_iter()
191 .copied()
192 .zip(&mut quant_l_saliency)
193 .for_each(|(d, dest)| {
194 *dest = (d * u8::MAX as f32).round() as u8;
195 });
196 dump_intermediate(
197 "l_amplitude_saliency",
198 &quant_l_saliency,
199 image_width,
200 image_height,
201 );
202 }
203
204 #[cfg(feature = "dump_a_saliency")]
205 {
206 let mut quant_a_saliency = vec![0; pixels.len()];
207
208 a_saliency
209 .spectral_residual
210 .par_iter()
211 .copied()
212 .zip(&mut quant_a_saliency)
213 .for_each(|(d, dest)| {
214 *dest = (d * u8::MAX as f32).round() as u8;
215 });
216 dump_intermediate(
217 "a_sr_saliency",
218 &quant_a_saliency,
219 image_width,
220 image_height,
221 );
222
223 a_saliency
224 .mod_spectral_residual
225 .par_iter()
226 .copied()
227 .zip(&mut quant_a_saliency)
228 .for_each(|(d, dest)| {
229 *dest = (d * u8::MAX as f32).round() as u8;
230 });
231 dump_intermediate(
232 "a_mod_sr_saliency",
233 &quant_a_saliency,
234 image_width,
235 image_height,
236 );
237
238 a_saliency
239 .phase_spectrum
240 .par_iter()
241 .copied()
242 .zip(&mut quant_a_saliency)
243 .for_each(|(d, dest)| {
244 *dest = (d * u8::MAX as f32).round() as u8;
245 });
246 dump_intermediate(
247 "a_phase_saliency",
248 &quant_a_saliency,
249 image_width,
250 image_height,
251 );
252
253 a_saliency
254 .amplitude_spectrum
255 .par_iter()
256 .copied()
257 .zip(&mut quant_a_saliency)
258 .for_each(|(d, dest)| {
259 *dest = (d * u8::MAX as f32).round() as u8;
260 });
261 dump_intermediate(
262 "a_amplitude_saliency",
263 &quant_a_saliency,
264 image_width,
265 image_height,
266 );
267 }
268
269 #[cfg(feature = "dump_b_saliency")]
270 {
271 let mut quant_b_saliency = vec![0; pixels.len()];
272
273 b_saliency
274 .spectral_residual
275 .par_iter()
276 .copied()
277 .zip(&mut quant_b_saliency)
278 .for_each(|(d, dest)| {
279 *dest = (d * u8::MAX as f32).round() as u8;
280 });
281 dump_intermediate(
282 "b_sr_saliency",
283 &quant_b_saliency,
284 image_width,
285 image_height,
286 );
287
288 b_saliency
289 .mod_spectral_residual
290 .par_iter()
291 .copied()
292 .zip(&mut quant_b_saliency)
293 .for_each(|(d, dest)| {
294 *dest = (d * u8::MAX as f32).round() as u8;
295 });
296 dump_intermediate(
297 "b_mod_sr_saliency",
298 &quant_b_saliency,
299 image_width,
300 image_height,
301 );
302
303 b_saliency
304 .phase_spectrum
305 .par_iter()
306 .copied()
307 .zip(&mut quant_b_saliency)
308 .for_each(|(d, dest)| {
309 *dest = (d * u8::MAX as f32).round() as u8;
310 });
311 dump_intermediate(
312 "b_phase_saliency",
313 &quant_b_saliency,
314 image_width,
315 image_height,
316 );
317
318 b_saliency
319 .amplitude_spectrum
320 .par_iter()
321 .copied()
322 .zip(&mut quant_b_saliency)
323 .for_each(|(d, dest)| {
324 *dest = (d * u8::MAX as f32).round() as u8;
325 });
326 dump_intermediate(
327 "b_amplitude_saliency",
328 &quant_b_saliency,
329 image_width,
330 image_height,
331 );
332 }
333
334 let centroid = pixels.par_iter().copied().reduce(
335 || <Lab>::new(0.0, 0.0, 0.0),
336 |mut acc, color| {
337 acc.l += color.l;
338 acc.a += color.a;
339 acc.b += color.b;
340 acc
341 },
342 ) / pixels.len() as f32;
343
344 let mut dists = vec![0.0f32; pixels.len()];
345 pixels
346 .par_iter()
347 .copied()
348 .zip(&mut dists)
349 .for_each(|(lab, dest)| {
350 *dest = lab.distance(centroid);
351 });
352
353 let max_dist = dists.par_iter().copied().reduce(|| f32::MIN, f32::max);
354 let min_dist = dists.par_iter().copied().reduce(|| f32::MAX, f32::min);
355 dists.par_iter_mut().for_each(|d| {
356 *d = (*d - min_dist) / (max_dist - min_dist).max(f32::EPSILON);
357 });
358
359 #[cfg(feature = "dump_dist_saliency")]
360 {
361 let mut quant_dists = vec![0; pixels.len()];
362 dists
363 .par_iter()
364 .copied()
365 .zip(&mut quant_dists)
366 .for_each(|(d, dest)| {
367 *dest = (d * u8::MAX as f32).round() as u8;
368 });
369
370 dump_intermediate("dists", &quant_dists, image_width, image_height);
371 }
372
373 let mut local_dists = vec![0.0f32; pixels.len()];
374
375 (0..pixels.len())
376 .into_par_iter()
377 .zip(&mut local_dists)
378 .for_each(|(idx, dest)| {
379 let x = idx as isize % image_width as isize;
380 let y = idx as isize / image_height as isize;
381
382 let mut sum = 0.0;
383 let mut count = 0;
384 for dx in -(window_radius as isize)..=window_radius as isize {
385 for dy in -(window_radius as isize)..=window_radius as isize {
386 let x = x + dx;
387 let y = y + dy;
388
389 if (dx == 0 && dy == 0)
390 || x < 0
391 || y < 0
392 || x >= image_width as isize
393 || y >= image_height as isize
394 {
395 continue;
396 }
397 let jdx = y * image_width as isize + x;
398 sum += pixels[jdx as usize].distance(pixels[idx]);
399 count += 1;
400 }
401 }
402
403 *dest = sum / count as f32
404 });
405
406 let min_local_dist = local_dists
407 .par_iter()
408 .copied()
409 .reduce(|| f32::MAX, f32::min);
410 let max_local_dist = local_dists
411 .par_iter()
412 .copied()
413 .reduce(|| f32::MIN, f32::max);
414
415 local_dists.par_iter_mut().for_each(|d| {
416 *d = (*d - min_local_dist) / (max_local_dist - min_local_dist).max(f32::EPSILON);
417 });
418
419 #[cfg(feature = "dump_local_saliency")]
420 {
421 let mut quant_local_dists = vec![0; pixels.len()];
422 local_dists
423 .par_iter()
424 .copied()
425 .zip(&mut quant_local_dists)
426 .for_each(|(d, dest)| {
427 *dest = (d * u8::MAX as f32).round() as u8;
428 });
429
430 dump_intermediate("local_dists", &quant_local_dists, image_width, image_height);
431 }
432
433 let avg_local_dist =
434 local_dists.par_iter().copied().sum::<f32>() / local_dists.len() as f32;
435 let avg_dist = dists.par_iter().copied().sum::<f32>() / dists.len() as f32;
436
437 let mut candidates = vec![(<Lab>::new(0.0, 0.0, 0.0), 0.0); pixels.len()];
438 pixels
439 .into_par_iter()
440 .zip(l_saliency.spectral_residual)
441 .zip(l_saliency.mod_spectral_residual)
442 .zip(l_saliency.phase_spectrum)
443 .zip(l_saliency.amplitude_spectrum)
444 .zip(a_saliency.spectral_residual)
445 .zip(a_saliency.mod_spectral_residual)
446 .zip(a_saliency.phase_spectrum)
447 .zip(a_saliency.amplitude_spectrum)
448 .zip(b_saliency.spectral_residual)
449 .zip(b_saliency.mod_spectral_residual)
450 .zip(b_saliency.phase_spectrum)
451 .zip(b_saliency.amplitude_spectrum)
452 .zip(local_dists)
453 .zip(&mut candidates)
454 .for_each(
455 |(
456 (
457 (
458 (
459 (
460 (
461 (
462 (
463 ((((((lab, l_sr), l_msr), l_p), l_a), a_sr), a_msr),
464 a_p,
465 ),
466 a_a,
467 ),
468 b_sr,
469 ),
470 b_msr,
471 ),
472 b_p,
473 ),
474 b_a,
475 ),
476 local,
477 ),
478 dest,
479 )| {
480 let outlier_outlier = l_msr.max(a_msr).max(b_msr);
481 let outliers = l_sr.max(a_sr).max(b_sr).max(outlier_outlier);
482 let edges = l_p.max(a_p).max(b_p);
483 let blobs = l_a.max(a_a).max(b_a);
484
485 let w = if local <= 0.5 {
489 blobs + 2.0 * local * (outliers - blobs)
490 } else {
491 outliers + 2.0 * (local - 0.5) * (edges - outliers)
492 };
493
494 *dest = (lab, w.powf(0.25 + (avg_local_dist + avg_dist) * 4.0));
495 },
496 );
497
498 #[cfg(feature = "dump_weights")]
499 {
500 let mut quant_candidates = vec![0; candidates.len()];
501 candidates
502 .par_iter()
503 .copied()
504 .zip(&mut quant_candidates)
505 .for_each(|((_, w), dest)| {
506 *dest = (w * u8::MAX as f32).round() as u8;
507 });
508
509 dump_intermediate("candidates", &quant_candidates, image_width, image_height);
510 }
511
512 parallel_kmeans::<PALETTE_SIZE>(&candidates)
513 }
514}
515
516struct Saliency {
517 spectral_residual: Vec<f32>,
518 mod_spectral_residual: Vec<f32>,
519 phase_spectrum: Vec<f32>,
520 amplitude_spectrum: Vec<f32>,
521}
522
523fn compute_saliency(
524 planner: &mut FftPlanner<f32>,
525 channel_values: &[f32],
526 image_width: u32,
527 image_height: u32,
528 window_radius: u32,
529) -> Saliency {
530 let buffer = {
531 let fft_row = planner.plan_fft_forward(image_width as usize);
532 let fft_col = planner.plan_fft_forward(image_height as usize);
533
534 let mut buffer = vec![Complex::zero(); channel_values.len()];
535 channel_values
536 .par_iter()
537 .copied()
538 .zip(&mut buffer)
539 .for_each(|(v, dest)| {
540 *dest = Complex { re: v, im: 0.0 };
541 });
542
543 buffer
544 .par_chunks_mut(image_width as usize)
545 .for_each(|chunk| {
546 fft_row.process(chunk);
547 });
548
549 let mut transpose = vec![Complex::zero(); buffer.len()];
550 (0..image_width as usize)
551 .into_par_iter()
552 .zip(transpose.par_chunks_mut(image_height as usize))
553 .for_each(|(x, col)| {
554 (0..image_height as usize)
555 .into_par_iter()
556 .zip(col)
557 .for_each(|(y, dest)| {
558 *dest = buffer[y * image_width as usize + x];
559 });
560 });
561
562 transpose
563 .par_chunks_mut(image_height as usize)
564 .for_each(|chunk| {
565 fft_col.process(chunk);
566 });
567
568 (0..image_height as usize)
569 .into_par_iter()
570 .zip(buffer.par_chunks_mut(image_width as usize))
571 .for_each(|(y, row)| {
572 (0..image_width as usize)
573 .into_par_iter()
574 .zip(row)
575 .for_each(|(x, dest)| {
576 *dest = transpose[x * image_height as usize + y];
577 });
578 });
579
580 transpose
581 };
582
583 let mut amplitude = vec![0.0f32; buffer.len()];
584 buffer
585 .par_iter()
586 .copied()
587 .zip(&mut amplitude)
588 .for_each(|(c, dest)| *dest = c.norm());
589
590 let pft = {
591 let mut ifft_buffer = vec![Complex::zero(); buffer.len()];
592
593 amplitude
594 .par_iter()
595 .copied()
596 .zip(&buffer)
597 .zip(&mut ifft_buffer)
598 .for_each(|((a, c), dest)| {
599 *dest = Complex {
600 re: if a > f32::EPSILON { c.re / a } else { 0.0 },
601 im: if a > f32::EPSILON { c.im / a } else { 0.0 },
602 };
603 });
604
605 let ifft_row = planner.plan_fft_inverse(image_width as usize);
606 let ifft_col = planner.plan_fft_inverse(image_height as usize);
607
608 ifft_buffer
609 .par_chunks_mut(image_height as usize)
610 .for_each(|chunk| {
611 ifft_col.process(chunk);
612 });
613
614 let mut transpose = vec![Complex::zero(); ifft_buffer.len()];
615 (0..image_width as usize)
616 .into_par_iter()
617 .zip(transpose.par_chunks_mut(image_height as usize))
618 .for_each(|(x, col)| {
619 (0..image_height as usize)
620 .into_par_iter()
621 .zip(col)
622 .for_each(|(y, dest)| {
623 *dest = ifft_buffer[y * image_width as usize + x];
624 });
625 });
626
627 transpose
628 .par_chunks_mut(image_height as usize)
629 .for_each(|chunk| {
630 ifft_row.process(chunk);
631 });
632
633 (0..image_height as usize)
634 .into_par_iter()
635 .zip(ifft_buffer.par_chunks_mut(image_width as usize))
636 .for_each(|(y, row)| {
637 (0..image_width as usize)
638 .into_par_iter()
639 .zip(row)
640 .for_each(|(x, dest)| {
641 *dest = transpose[x * image_height as usize + y];
642 });
643 });
644
645 let mut pft = vec![0.0f32; transpose.len()];
646
647 transpose
648 .par_iter()
649 .copied()
650 .zip(&mut pft)
651 .for_each(|(c, dest)| *dest = c.norm_sqr());
652
653 {
654 let mut pft =
655 BlurImageMut::borrow(&mut pft, image_width, image_height, FastBlurChannels::Plane);
656 stack_blur_f32(
657 &mut pft,
658 AnisotropicRadius::new(window_radius),
659 ThreadingPolicy::Adaptive,
660 )
661 .unwrap();
662 }
663
664 let max_pft = pft.par_iter().copied().reduce(|| f32::MIN, f32::max);
665 let min_pft = pft.par_iter().copied().reduce(|| f32::MAX, f32::min);
666 pft.par_iter_mut().for_each(|a| {
667 *a = (*a - min_pft) / (max_pft - min_pft).max(f32::EPSILON);
668 });
669
670 pft
671 };
672
673 let mut phase = vec![0.0f32; buffer.len()];
674 buffer
675 .into_par_iter()
676 .zip(&mut phase)
677 .for_each(|(c, dest)| *dest = c.arg());
678
679 let mut mod_amplitude = vec![0.0f32; phase.len()];
680 let max_amplitude = amplitude.par_iter().copied().reduce(|| f32::MIN, f32::max);
681 let min_amplitude = amplitude.par_iter().copied().reduce(|| f32::MAX, f32::min);
682 amplitude
683 .par_iter()
684 .copied()
685 .zip(&mut mod_amplitude)
686 .for_each(|(a, dest)| {
687 let x = ((a - min_amplitude) / (max_amplitude - min_amplitude).max(f32::EPSILON) - 0.5)
688 * 2.0;
689 *dest = ((x * PI / 2.0).sin().powi(15) / 2.0 + 0.5) * (max_amplitude - min_amplitude)
690 + min_amplitude
691 });
692
693 let asr = {
694 let mut ifft_buffer = vec![Complex::zero(); phase.len()];
695 mod_amplitude
696 .par_iter()
697 .copied()
698 .zip(&phase)
699 .zip(&mut ifft_buffer)
700 .for_each(|((a, p), dest)| {
701 *dest = Complex {
702 re: a * p.cos(),
703 im: a * p.sin(),
704 };
705 });
706
707 let ifft_row = planner.plan_fft_inverse(image_width as usize);
708 let ifft_col = planner.plan_fft_inverse(image_height as usize);
709 ifft_buffer
710 .par_chunks_mut(image_height as usize)
711 .for_each(|chunk| {
712 ifft_col.process(chunk);
713 });
714
715 let mut transpose = vec![Complex::zero(); ifft_buffer.len()];
716 (0..image_width as usize)
717 .into_par_iter()
718 .zip(transpose.par_chunks_mut(image_height as usize))
719 .for_each(|(x, col)| {
720 (0..image_height as usize)
721 .into_par_iter()
722 .zip(col)
723 .for_each(|(y, dest)| {
724 *dest = ifft_buffer[y * image_width as usize + x];
725 });
726 });
727
728 transpose
729 .par_chunks_mut(image_height as usize)
730 .for_each(|chunk| {
731 ifft_row.process(chunk);
732 });
733
734 (0..image_height as usize)
735 .into_par_iter()
736 .zip(ifft_buffer.par_chunks_mut(image_width as usize))
737 .for_each(|(y, row)| {
738 (0..image_width as usize)
739 .into_par_iter()
740 .zip(row)
741 .for_each(|(x, dest)| {
742 *dest = transpose[x * image_height as usize + y];
743 });
744 });
745
746 let mut asr = vec![0.0f32; transpose.len()];
747 transpose
748 .par_iter()
749 .copied()
750 .zip(&mut asr)
751 .for_each(|(c, dest)| *dest = c.norm_sqr());
752
753 {
754 let mut asr =
755 BlurImageMut::borrow(&mut asr, image_width, image_height, FastBlurChannels::Plane);
756 stack_blur_f32(
757 &mut asr,
758 AnisotropicRadius::new(window_radius),
759 ThreadingPolicy::Adaptive,
760 )
761 .unwrap();
762 }
763
764 let max_asr = asr.par_iter().copied().reduce(|| f32::MIN, f32::max);
765 let min_asr = asr.par_iter().copied().reduce(|| f32::MAX, f32::min);
766 asr.par_iter_mut().for_each(|a| {
767 *a = (*a - min_asr) / (max_asr - min_asr).max(f32::EPSILON);
768 });
769
770 asr
771 };
772
773 let sr = {
774 let mut log_amplitude = vec![0.0f32; phase.len()];
775 amplitude
776 .into_par_iter()
777 .zip(&mut log_amplitude)
778 .for_each(|(a, dest)| *dest = a.max(f32::EPSILON).ln());
779
780 let mut log_blurred = log_amplitude.clone();
781
782 {
783 let mut amplitude = BlurImageMut::borrow(
784 &mut log_blurred,
785 image_width,
786 image_height,
787 FastBlurChannels::Plane,
788 );
789 stack_blur_f32(
790 &mut amplitude,
791 AnisotropicRadius::new(window_radius),
792 ThreadingPolicy::Adaptive,
793 )
794 .unwrap();
795 }
796
797 let mut residual = vec![0.0f32; image_width as usize * image_height as usize];
798 log_amplitude
799 .into_par_iter()
800 .zip(log_blurred)
801 .zip(&mut residual)
802 .for_each(|((a, b), dest)| {
803 *dest = a - b;
804 });
805
806 let sr_buffer = {
807 let ifft_row = planner.plan_fft_inverse(image_width as usize);
808 let ifft_col = planner.plan_fft_inverse(image_height as usize);
809
810 let mut buffer = vec![Complex::zero(); residual.len()];
811 residual
812 .into_par_iter()
813 .zip(phase.par_iter())
814 .zip(&mut buffer)
815 .for_each(|((a, p), dest)| {
816 *dest = Complex {
817 re: a.exp() * p.cos(),
818 im: a.exp() * p.sin(),
819 };
820 });
821
822 buffer
823 .par_chunks_mut(image_height as usize)
824 .for_each(|chunk| {
825 ifft_col.process(chunk);
826 });
827
828 let mut transpose = vec![Complex::zero(); buffer.len()];
829 (0..image_width as usize)
830 .into_par_iter()
831 .zip(transpose.par_chunks_mut(image_height as usize))
832 .for_each(|(x, col)| {
833 (0..image_height as usize)
834 .into_par_iter()
835 .zip(col)
836 .for_each(|(y, dest)| {
837 *dest = buffer[y * image_width as usize + x];
838 });
839 });
840
841 transpose
842 .par_chunks_mut(image_height as usize)
843 .for_each(|chunk| {
844 ifft_row.process(chunk);
845 });
846
847 (0..image_height as usize)
848 .into_par_iter()
849 .zip(buffer.par_chunks_mut(image_width as usize))
850 .for_each(|(y, row)| {
851 (0..image_width as usize)
852 .into_par_iter()
853 .zip(row)
854 .for_each(|(x, dest)| {
855 *dest = transpose[x * image_height as usize + y];
856 });
857 });
858
859 transpose
860 };
861
862 let mut saliency = vec![0.0f32; image_width as usize * image_height as usize];
863 sr_buffer
864 .into_par_iter()
865 .zip(&mut saliency)
866 .for_each(|(c, dest)| *dest = c.norm_sqr());
867
868 {
869 let mut saliency = BlurImageMut::borrow(
870 &mut saliency,
871 image_width,
872 image_height,
873 FastBlurChannels::Plane,
874 );
875 stack_blur_f32(
876 &mut saliency,
877 AnisotropicRadius::new(window_radius),
878 ThreadingPolicy::Adaptive,
879 )
880 .unwrap();
881 }
882
883 let max_saliency = saliency.par_iter().copied().reduce(|| f32::MIN, f32::max);
884 let min_saliency = saliency.par_iter().copied().reduce(|| f32::MAX, f32::min);
885 saliency.par_iter_mut().for_each(|s| {
886 *s = (*s - min_saliency) / (max_saliency - min_saliency).max(f32::EPSILON);
887 });
888
889 saliency
890 };
891
892 let msr = {
893 let mut log_amplitude = vec![0.0f32; phase.len()];
894 mod_amplitude
895 .into_par_iter()
896 .zip(&mut log_amplitude)
897 .for_each(|(a, dest)| *dest = a.max(f32::EPSILON).ln());
898
899 let mut log_blurred = log_amplitude.clone();
900
901 {
902 let mut amplitude = BlurImageMut::borrow(
903 &mut log_blurred,
904 image_width,
905 image_height,
906 FastBlurChannels::Plane,
907 );
908 stack_blur_f32(
909 &mut amplitude,
910 AnisotropicRadius::new(window_radius),
911 ThreadingPolicy::Adaptive,
912 )
913 .unwrap();
914 }
915
916 let mut residual = vec![0.0f32; image_width as usize * image_height as usize];
917 log_amplitude
918 .into_par_iter()
919 .zip(log_blurred)
920 .zip(&mut residual)
921 .for_each(|((a, b), dest)| {
922 *dest = a - b;
923 });
924
925 let mut mod_phase = vec![0.0f32; phase.len()];
926
927 let max_phase = phase.par_iter().copied().reduce(|| f32::MIN, f32::max);
928 let min_phase = phase.par_iter().copied().reduce(|| f32::MAX, f32::min);
929
930 phase
931 .into_par_iter()
932 .zip(&mut mod_phase)
933 .for_each(|(p, dest)| {
934 let x = ((p - min_phase) / (max_phase - min_phase).max(f32::EPSILON) - 0.5) * 2.0;
935 *dest = ((x * PI / 2.0).sin().powi(15) / 2.0 + 0.5) * (max_phase - min_phase)
936 + min_phase;
937 });
938
939 let mut msr_buffer = vec![Complex::zero(); mod_phase.len()];
940 residual
941 .into_par_iter()
942 .zip(mod_phase)
943 .zip(&mut msr_buffer)
944 .for_each(|((a, p), dest)| {
945 *dest = Complex {
946 re: a * p.cos(),
947 im: a * p.sin(),
948 };
949 });
950
951 let ifft_row = planner.plan_fft_inverse(image_width as usize);
952 let ifft_col = planner.plan_fft_inverse(image_height as usize);
953 msr_buffer
954 .par_chunks_mut(image_height as usize)
955 .for_each(|chunk| {
956 ifft_col.process(chunk);
957 });
958
959 let mut transpose = vec![Complex::zero(); msr_buffer.len()];
960 (0..image_width as usize)
961 .into_par_iter()
962 .zip(transpose.par_chunks_mut(image_height as usize))
963 .for_each(|(x, col)| {
964 (0..image_height as usize)
965 .into_par_iter()
966 .zip(col)
967 .for_each(|(y, dest)| {
968 *dest = msr_buffer[y * image_width as usize + x];
969 });
970 });
971
972 transpose
973 .par_chunks_mut(image_height as usize)
974 .for_each(|chunk| {
975 ifft_row.process(chunk);
976 });
977
978 (0..image_height as usize)
979 .into_par_iter()
980 .zip(msr_buffer.par_chunks_mut(image_width as usize))
981 .for_each(|(y, row)| {
982 (0..image_width as usize)
983 .into_par_iter()
984 .zip(row)
985 .for_each(|(x, dest)| {
986 *dest = transpose[x * image_height as usize + y];
987 });
988 });
989
990 let mut msr = vec![0.0f32; transpose.len()];
991 transpose
992 .par_iter()
993 .copied()
994 .zip(&mut msr)
995 .for_each(|(c, dest)| *dest = c.norm_sqr());
996
997 {
998 let mut msr =
999 BlurImageMut::borrow(&mut msr, image_width, image_height, FastBlurChannels::Plane);
1000 stack_blur_f32(
1001 &mut msr,
1002 AnisotropicRadius::new(window_radius),
1003 ThreadingPolicy::Adaptive,
1004 )
1005 .unwrap();
1006 }
1007
1008 let max_msr = msr.par_iter().copied().reduce(|| f32::MIN, f32::max);
1009 let min_msr = msr.par_iter().copied().reduce(|| f32::MAX, f32::min);
1010 msr.par_iter_mut().for_each(|s| {
1011 *s = (*s - min_msr) / (max_msr - min_msr).max(f32::EPSILON);
1012 });
1013
1014 msr
1015 };
1016
1017 Saliency {
1018 spectral_residual: sr,
1019 mod_spectral_residual: msr,
1020 phase_spectrum: pft,
1021 amplitude_spectrum: asr,
1022 }
1023}
1024
1025#[cfg(any(
1026 feature = "dump_l_saliency",
1027 feature = "dump_a_saliency",
1028 feature = "dump_b_saliency",
1029 feature = "dump_dist_saliency",
1030 feature = "dump_local_saliency",
1031 feature = "dump_weights"
1032))]
1033fn dump_intermediate(name: &str, buffer: &[u8], width: u32, height: u32) {
1034 use std::hash::{
1035 BuildHasher,
1036 Hasher,
1037 RandomState,
1038 };
1039 let rand = BuildHasher::build_hasher(&RandomState::new()).finish();
1040 image::save_buffer(
1041 format!("{name}-{rand}.png"),
1042 buffer,
1043 width,
1044 height,
1045 image::ColorType::L8,
1046 )
1047 .unwrap()
1048}