ostromoukhov dither
This commit is contained in:
		
							parent
							
								
									f64365f5bd
								
							
						
					
					
						commit
						9d5b21673a
					
				
					 3 changed files with 492 additions and 190 deletions
				
			
		|  | @ -114,14 +114,6 @@ pub enum StreamCommand { | |||
| 
 | ||||
| #[derive(clap::Parser, std::fmt::Debug, Clone)] | ||||
| pub struct StreamScreenOptions { | ||||
|     #[arg(
 | ||||
|         long, | ||||
|         short, | ||||
|         default_value_t = false, | ||||
|         help = "Disable dithering - improves performance" | ||||
|     )] | ||||
|     pub no_dither: bool, | ||||
| 
 | ||||
|     #[arg(
 | ||||
|         long, | ||||
|         short, | ||||
|  |  | |||
|  | @ -1,17 +1,7 @@ | |||
| //! Based on https://github.com/WarkerAnhaltRanger/CCCB_Ledwand
 | ||||
| 
 | ||||
| use image::{GenericImage, GrayImage}; | ||||
| use servicepoint::{PIXEL_HEIGHT, PIXEL_WIDTH}; | ||||
| 
 | ||||
| pub struct LedwandDither { | ||||
|     options: LedwandDitherOptions, | ||||
|     tmpbuf: GrayImage, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Default)] | ||||
| pub struct LedwandDitherOptions { | ||||
|     pub size: Option<(u32, u32)>, | ||||
| } | ||||
| use image::GrayImage; | ||||
| use servicepoint::{BitVec, Bitmap, PIXEL_HEIGHT}; | ||||
| 
 | ||||
| type GrayHistogram = [usize; 256]; | ||||
| 
 | ||||
|  | @ -21,164 +11,495 @@ struct HistogramCorrection { | |||
|     factor: f32, | ||||
| } | ||||
| 
 | ||||
| impl LedwandDither { | ||||
|     pub fn new(options: LedwandDitherOptions) -> Self { | ||||
|         let (width, height) = options | ||||
|             .size | ||||
|             .unwrap_or((PIXEL_WIDTH as u32, PIXEL_HEIGHT as u32)); | ||||
|         Self { | ||||
|             tmpbuf: GrayImage::new(width, height), | ||||
|             options, | ||||
| pub fn histogram_correction(image: &mut GrayImage) { | ||||
|     let histogram = make_histogram(image); | ||||
|     let correction = determine_histogram_correction(image, histogram); | ||||
|     apply_histogram_correction(image, correction) | ||||
| } | ||||
| 
 | ||||
| fn make_histogram(image: &GrayImage) -> GrayHistogram { | ||||
|     let mut histogram = [0; 256]; | ||||
|     for pixel in image.pixels() { | ||||
|         histogram[pixel.0[0] as usize] += 1; | ||||
|     } | ||||
|     histogram | ||||
| } | ||||
| 
 | ||||
| fn determine_histogram_correction( | ||||
|     image: &GrayImage, | ||||
|     histogram: GrayHistogram, | ||||
| ) -> HistogramCorrection { | ||||
|     let adjustment_pixels = image.len() / PIXEL_HEIGHT; | ||||
| 
 | ||||
|     let mut num_pixels = 0; | ||||
|     let mut brightness = 0; | ||||
| 
 | ||||
|     let mincut = loop { | ||||
|         num_pixels += histogram[brightness as usize]; | ||||
|         brightness += 1; | ||||
|         if num_pixels >= adjustment_pixels { | ||||
|             break u8::min(brightness, 20); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     let minshift = loop { | ||||
|         num_pixels += histogram[brightness as usize]; | ||||
|         brightness += 1; | ||||
|         if num_pixels >= 2 * adjustment_pixels { | ||||
|             break u8::min(brightness, 64); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     brightness = u8::MAX; | ||||
|     num_pixels = 0; | ||||
|     let maxshift = loop { | ||||
|         num_pixels += histogram[brightness as usize]; | ||||
|         brightness -= 1; | ||||
|         if num_pixels >= 2 * adjustment_pixels { | ||||
|             break u8::max(brightness, 192); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     let pre_offset = -(mincut as f32 / 2.); | ||||
|     let post_offset = -(minshift as f32); | ||||
|     let factor = (255.0 - post_offset) / maxshift as f32; | ||||
|     HistogramCorrection { | ||||
|         pre_offset, | ||||
|         post_offset, | ||||
|         factor, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn apply_histogram_correction(image: &mut GrayImage, correction: HistogramCorrection) { | ||||
|     for pixel in image.pixels_mut() { | ||||
|         let pixel = &mut pixel.0[0]; | ||||
|         let value = | ||||
|             (*pixel as f32 + correction.pre_offset) * correction.factor + correction.post_offset; | ||||
|         *pixel = value.clamp(0f32, u8::MAX as f32) as u8; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn median_brightness(image: &GrayImage) -> u8 { | ||||
|     let histogram = make_histogram(image); | ||||
|     let midpoint = image.len() / 2; | ||||
| 
 | ||||
|     debug_assert_eq!( | ||||
|         image.len(), | ||||
|         histogram.iter().copied().map(usize::from).sum() | ||||
|     ); | ||||
| 
 | ||||
|     let mut num_pixels = 0; | ||||
|     for brightness in u8::MIN..=u8::MAX { | ||||
|         num_pixels += histogram[brightness as usize]; | ||||
|         if num_pixels >= midpoint { | ||||
|             return brightness; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn histogram_correction(image: &mut GrayImage) { | ||||
|         let histogram = Self::make_histogram(image); | ||||
|         let correction = Self::determine_histogram_correction(image, histogram); | ||||
|         Self::apply_histogram_correction(image, correction) | ||||
|     unreachable!("Somehow less pixels where counted in the histogram than exist in the image") | ||||
| } | ||||
| 
 | ||||
| pub fn blur(source: &GrayImage, destination: &mut GrayImage) { | ||||
|     assert_eq!(source.len(), destination.len()); | ||||
| 
 | ||||
|     copy_border(source, destination); | ||||
|     blur_inner_pixels(source, destination); | ||||
| } | ||||
| 
 | ||||
| pub fn sharpen(source: &GrayImage, destination: &mut GrayImage) { | ||||
|     assert_eq!(source.len(), destination.len()); | ||||
| 
 | ||||
|     copy_border(source, destination); | ||||
|     sharpen_inner_pixels(source, destination); | ||||
| } | ||||
| 
 | ||||
| fn copy_border(source: &GrayImage, destination: &mut GrayImage) { | ||||
|     let last_row = source.height() - 1; | ||||
|     for x in 0..source.width() { | ||||
|         destination[(x, 0)] = source[(x, 0)]; | ||||
|         destination[(x, last_row)] = source[(x, last_row)]; | ||||
|     } | ||||
| 
 | ||||
|     fn make_histogram(image: &GrayImage) -> GrayHistogram { | ||||
|         let mut histogram = [0; 256]; | ||||
|         for pixel in image.pixels() { | ||||
|             histogram[pixel.0[0] as usize] += 1; | ||||
|         } | ||||
|         histogram | ||||
|     let last_col = source.width() - 1; | ||||
|     for y in 0..source.height() { | ||||
|         destination[(0, y)] = source[(0, y)]; | ||||
|         destination[(last_col, y)] = source[(last_col, y)]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     fn determine_histogram_correction( | ||||
|         image: &GrayImage, | ||||
|         histogram: GrayHistogram, | ||||
|     ) -> HistogramCorrection { | ||||
|         let adjustment_pixels = image.len() / PIXEL_HEIGHT; | ||||
| 
 | ||||
|         let mut num_pixels = 0; | ||||
|         let mut brightness = 0; | ||||
| 
 | ||||
|         let mincut = loop { | ||||
|             num_pixels += histogram[brightness as usize]; | ||||
|             brightness += 1; | ||||
|             if num_pixels >= adjustment_pixels { | ||||
|                 break u8::min(brightness, 20); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         let minshift = loop { | ||||
|             num_pixels += histogram[brightness as usize]; | ||||
|             brightness += 1; | ||||
|             if num_pixels >= 2 * adjustment_pixels { | ||||
|                 break u8::min(brightness, 64); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         brightness = u8::MAX; | ||||
|         num_pixels = 0; | ||||
|         let maxshift = loop { | ||||
|             num_pixels += histogram[brightness as usize]; | ||||
|             brightness -= 1; | ||||
|             if num_pixels >= 2 * adjustment_pixels { | ||||
|                 break u8::max(brightness, 192); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         let pre_offset = -(mincut as f32 / 2.); | ||||
|         let post_offset = -(minshift as f32); | ||||
|         let factor = (255.0 - post_offset) / maxshift as f32; | ||||
|         HistogramCorrection { | ||||
|             pre_offset, | ||||
|             post_offset, | ||||
|             factor, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn apply_histogram_correction(image: &mut GrayImage, correction: HistogramCorrection) { | ||||
|         for pixel in image.pixels_mut() { | ||||
|             let pixel = &mut pixel.0[0]; | ||||
|             let value = (*pixel as f32 + correction.pre_offset) * correction.factor | ||||
|                 + correction.post_offset; | ||||
|             *pixel = value.clamp(0f32, u8::MAX as f32) as u8; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn median_brightness(image: &GrayImage) -> u8 { | ||||
|         let histogram = Self::make_histogram(image); | ||||
|         let midpoint = image.len() / 2; | ||||
| 
 | ||||
|         debug_assert_eq!( | ||||
|             image.len(), | ||||
|             histogram.iter().copied().map(usize::from).sum() | ||||
|         ); | ||||
| 
 | ||||
|         let mut num_pixels = 0; | ||||
|         for brightness in u8::MIN..=u8::MAX { | ||||
|             num_pixels += histogram[brightness as usize]; | ||||
|             if num_pixels >= midpoint { | ||||
|                 return brightness; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         unreachable!("Somehow less pixels where counted in the histogram than exist in the image") | ||||
|     } | ||||
| 
 | ||||
|     pub fn blur(source: &GrayImage, destination: &mut GrayImage) { | ||||
|         assert_eq!(source.len(), destination.len()); | ||||
| 
 | ||||
|         Self::copy_border(source, destination); | ||||
|         Self::blur_inner_pixels(source, destination); | ||||
|     } | ||||
| 
 | ||||
|     pub fn sharpen(source: &GrayImage, destination: &mut GrayImage) { | ||||
|         assert_eq!(source.len(), destination.len()); | ||||
| 
 | ||||
|         Self::copy_border(source, destination); | ||||
|         Self::sharpen_inner_pixels(source, destination); | ||||
|     } | ||||
| 
 | ||||
|     fn copy_border(source: &GrayImage, destination: &mut GrayImage) { | ||||
|         let last_row = source.height() -1; | ||||
|         for x in 0..source.width() { | ||||
|             destination[(x, 0)] = source[(x, 0)]; | ||||
|             destination[(x, last_row)] = source[(x, last_row)]; | ||||
|         } | ||||
|         let last_col = source.width() - 1; | ||||
|         for y in 0..source.height() { | ||||
|             destination[(0, y)] = source[(0, y)]; | ||||
|             destination[(last_col, y)] = source[(last_col, y)]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn blur_inner_pixels(source: &GrayImage, destination: &mut GrayImage) { | ||||
|         for y in 1..source.height() - 2 { | ||||
|             for x in 1..source.width() - 2 { | ||||
|                 let weighted_sum = source.get_pixel(x - 1, y - 1).0[0] as u32 | ||||
|                     + source.get_pixel(x, y - 1).0[0] as u32 | ||||
|                     + source.get_pixel(x + 1, y - 1).0[0] as u32 | ||||
|                     + source.get_pixel(x - 1, y).0[0] as u32 | ||||
|                     + 8 * source.get_pixel(x, y).0[0] as u32 | ||||
|                     + source.get_pixel(x + 1, y).0[0] as u32 | ||||
|                     + source.get_pixel(x - 1, y + 1).0[0] as u32 | ||||
|                     + source.get_pixel(x, y + 1).0[0] as u32 | ||||
|                     + source.get_pixel(x + 1, y + 1).0[0] as u32; | ||||
|                 let blurred = weighted_sum / 16; | ||||
|                 destination.get_pixel_mut(x, y).0[0] = blurred.clamp(u8::MIN as u32, u8::MAX as u32) as u8; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn sharpen_inner_pixels(source: &GrayImage, destination: &mut GrayImage) { | ||||
|         for y in 1..source.height() - 2 { | ||||
|             for x in 1..source.width() - 2 { | ||||
|                 let weighted_sum = -(source.get_pixel(x - 1, y - 1).0[0] as i32) | ||||
|                     - source.get_pixel(x, y - 1).0[0] as i32 | ||||
|                     - source.get_pixel(x + 1, y - 1).0[0] as i32 | ||||
|                     - source.get_pixel(x - 1, y).0[0] as i32 | ||||
|                     + 9 * source.get_pixel(x, y).0[0] as i32 | ||||
|                     - source.get_pixel(x + 1, y).0[0] as i32 | ||||
|                     - source.get_pixel(x - 1, y + 1).0[0] as i32 | ||||
|                     - source.get_pixel(x, y + 1).0[0] as i32 | ||||
|                     - source.get_pixel(x + 1, y + 1).0[0] as i32; | ||||
|                 destination.get_pixel_mut(x, y).0[0] = weighted_sum.clamp(u8::MIN as i32, u8::MAX as i32) as u8; | ||||
|             } | ||||
| fn blur_inner_pixels(source: &GrayImage, destination: &mut GrayImage) { | ||||
|     for y in 1..source.height() - 2 { | ||||
|         for x in 1..source.width() - 2 { | ||||
|             let weighted_sum = source.get_pixel(x - 1, y - 1).0[0] as u32 | ||||
|                 + source.get_pixel(x, y - 1).0[0] as u32 | ||||
|                 + source.get_pixel(x + 1, y - 1).0[0] as u32 | ||||
|                 + source.get_pixel(x - 1, y).0[0] as u32 | ||||
|                 + 8 * source.get_pixel(x, y).0[0] as u32 | ||||
|                 + source.get_pixel(x + 1, y).0[0] as u32 | ||||
|                 + source.get_pixel(x - 1, y + 1).0[0] as u32 | ||||
|                 + source.get_pixel(x, y + 1).0[0] as u32 | ||||
|                 + source.get_pixel(x + 1, y + 1).0[0] as u32; | ||||
|             let blurred = weighted_sum / 16; | ||||
|             destination.get_pixel_mut(x, y).0[0] = | ||||
|                 blurred.clamp(u8::MIN as u32, u8::MAX as u32) as u8; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn sharpen_inner_pixels(source: &GrayImage, destination: &mut GrayImage) { | ||||
|     for y in 1..source.height() - 2 { | ||||
|         for x in 1..source.width() - 2 { | ||||
|             let weighted_sum = -(source.get_pixel(x - 1, y - 1).0[0] as i32) | ||||
|                 - source.get_pixel(x, y - 1).0[0] as i32 | ||||
|                 - source.get_pixel(x + 1, y - 1).0[0] as i32 | ||||
|                 - source.get_pixel(x - 1, y).0[0] as i32 | ||||
|                 + 9 * source.get_pixel(x, y).0[0] as i32 | ||||
|                 - source.get_pixel(x + 1, y).0[0] as i32 | ||||
|                 - source.get_pixel(x - 1, y + 1).0[0] as i32 | ||||
|                 - source.get_pixel(x, y + 1).0[0] as i32 | ||||
|                 - source.get_pixel(x + 1, y + 1).0[0] as i32; | ||||
|             destination.get_pixel_mut(x, y).0[0] = | ||||
|                 weighted_sum.clamp(u8::MIN as i32, u8::MAX as i32) as u8; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) fn ostromoukhov_dither(source: GrayImage, bias: u8) -> Bitmap { | ||||
|     let width = source.width(); | ||||
|     let height = source.height(); | ||||
|     assert_eq!(width % 8, 0); | ||||
| 
 | ||||
|     let mut source = source.into_raw(); | ||||
|     let mut destination = BitVec::repeat(false, source.len()); | ||||
| 
 | ||||
|     for y in 0..height as usize { | ||||
|         let start = y * width as usize; | ||||
|         if y % 2 == 0 { | ||||
|             for x in 0..width as usize { | ||||
|                 ostromoukhov_dither_pixel( | ||||
|                     &mut source, | ||||
|                     &mut destination, | ||||
|                     start + x, | ||||
|                     width as usize, | ||||
|                     y == (height - 1) as usize, | ||||
|                     1, | ||||
|                     bias, | ||||
|                 ); | ||||
|             } | ||||
|         } else { | ||||
|             for x in (0..width as usize).rev() { | ||||
|                 ostromoukhov_dither_pixel( | ||||
|                     &mut source, | ||||
|                     &mut destination, | ||||
|                     start + x, | ||||
|                     width as usize, | ||||
|                     y == (height - 1) as usize, | ||||
|                     -1, | ||||
|                     bias, | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Bitmap::from_bitvec(width as usize, destination) | ||||
| } | ||||
| 
 | ||||
| #[inline] | ||||
| fn ostromoukhov_dither_pixel( | ||||
|     source: &mut [u8], | ||||
|     destination: &mut BitVec, | ||||
|     position: usize, | ||||
|     width: usize, | ||||
|     last_row: bool, | ||||
|     direction: isize, | ||||
|     bias: u8, | ||||
| ) { | ||||
|     let old_pixel = source[position]; | ||||
| 
 | ||||
|     let destination_value = old_pixel > bias; | ||||
|     destination.set(position, destination_value); | ||||
| 
 | ||||
|     let error = if destination_value { | ||||
|         255 - old_pixel | ||||
|     } else { | ||||
|         old_pixel | ||||
|     }; | ||||
| 
 | ||||
|     let mut diffuse = |to: usize, mat: i16| { | ||||
|         let diffuse_value = source[to] as i16 + mat; | ||||
|         source[to] = diffuse_value.clamp(u8::MIN.into(), u8::MAX.into()) as u8; | ||||
|     }; | ||||
| 
 | ||||
|     let lookup = if destination_value { | ||||
|         ERROR_DIFFUSION_MATRIX[error as usize].map(move |i| -i) | ||||
|     } else { | ||||
|         ERROR_DIFFUSION_MATRIX[error as usize] | ||||
|     }; | ||||
|     diffuse((position as isize + direction) as usize, lookup[0]); | ||||
| 
 | ||||
|     if !last_row { | ||||
|         diffuse( | ||||
|             ((position + width) as isize - direction) as usize, | ||||
|             lookup[1], | ||||
|         ); | ||||
|         diffuse(((position + width) as isize) as usize, lookup[2]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const ERROR_DIFFUSION_MATRIX: [[i16; 3]; 256] = [ | ||||
|     [0, 1, 0], | ||||
|     [1, 0, 0], | ||||
|     [1, 0, 1], | ||||
|     [2, 0, 1], | ||||
|     [2, 0, 2], | ||||
|     [3, 0, 2], | ||||
|     [4, 0, 2], | ||||
|     [4, 1, 2], | ||||
|     [5, 1, 2], | ||||
|     [5, 2, 2], | ||||
|     [5, 3, 2], | ||||
|     [6, 3, 2], | ||||
|     [6, 3, 3], | ||||
|     [7, 3, 3], | ||||
|     [7, 4, 3], | ||||
|     [8, 4, 3], | ||||
|     [8, 5, 3], | ||||
|     [9, 5, 3], | ||||
|     [9, 5, 4], | ||||
|     [10, 6, 3], | ||||
|     [10, 6, 4], | ||||
|     [11, 7, 3], | ||||
|     [11, 7, 4], | ||||
|     [11, 8, 4], | ||||
|     [12, 7, 5], | ||||
|     [12, 7, 6], | ||||
|     [12, 7, 7], | ||||
|     [12, 7, 8], | ||||
|     [12, 7, 9], | ||||
|     [13, 7, 9], | ||||
|     [13, 7, 10], | ||||
|     [13, 7, 11], | ||||
|     [13, 7, 12], | ||||
|     [14, 7, 12], | ||||
|     [14, 8, 12], | ||||
|     [15, 8, 12], | ||||
|     [15, 9, 12], | ||||
|     [16, 9, 12], | ||||
|     [16, 10, 12], | ||||
|     [17, 10, 12], | ||||
|     [17, 11, 12], | ||||
|     [18, 12, 11], | ||||
|     [19, 12, 11], | ||||
|     [19, 13, 11], | ||||
|     [20, 13, 11], | ||||
|     [20, 14, 11], | ||||
|     [21, 15, 10], | ||||
|     [22, 15, 10], | ||||
|     [22, 17, 9], | ||||
|     [23, 17, 9], | ||||
|     [24, 18, 8], | ||||
|     [24, 19, 8], | ||||
|     [25, 19, 8], | ||||
|     [26, 20, 7], | ||||
|     [26, 21, 7], | ||||
|     [27, 22, 6], | ||||
|     [28, 23, 5], | ||||
|     [28, 24, 5], | ||||
|     [29, 25, 4], | ||||
|     [30, 26, 3], | ||||
|     [31, 26, 3], | ||||
|     [31, 28, 2], | ||||
|     [32, 28, 2], | ||||
|     [33, 29, 1], | ||||
|     [34, 30, 0], | ||||
|     [33, 31, 1], | ||||
|     [32, 33, 1], | ||||
|     [32, 33, 2], | ||||
|     [31, 34, 3], | ||||
|     [30, 36, 3], | ||||
|     [29, 37, 4], | ||||
|     [29, 37, 5], | ||||
|     [28, 39, 5], | ||||
|     [32, 34, 7], | ||||
|     [37, 29, 8], | ||||
|     [42, 23, 10], | ||||
|     [46, 19, 11], | ||||
|     [51, 13, 12], | ||||
|     [52, 14, 13], | ||||
|     [53, 13, 12], | ||||
|     [53, 14, 13], | ||||
|     [54, 14, 13], | ||||
|     [55, 14, 13], | ||||
|     [55, 14, 13], | ||||
|     [56, 15, 14], | ||||
|     [57, 14, 13], | ||||
|     [56, 15, 15], | ||||
|     [55, 17, 15], | ||||
|     [54, 18, 16], | ||||
|     [53, 20, 16], | ||||
|     [52, 21, 17], | ||||
|     [52, 22, 17], | ||||
|     [51, 24, 17], | ||||
|     [50, 25, 18], | ||||
|     [49, 27, 18], | ||||
|     [47, 29, 19], | ||||
|     [48, 29, 19], | ||||
|     [48, 29, 20], | ||||
|     [49, 29, 20], | ||||
|     [49, 30, 20], | ||||
|     [50, 31, 20], | ||||
|     [50, 31, 20], | ||||
|     [51, 31, 20], | ||||
|     [51, 31, 21], | ||||
|     [52, 31, 21], | ||||
|     [52, 32, 21], | ||||
|     [53, 32, 21], | ||||
|     [53, 32, 22], | ||||
|     [55, 32, 21], | ||||
|     [56, 31, 22], | ||||
|     [58, 31, 21], | ||||
|     [59, 30, 22], | ||||
|     [61, 30, 21], | ||||
|     [62, 29, 22], | ||||
|     [64, 29, 21], | ||||
|     [65, 28, 22], | ||||
|     [67, 28, 21], | ||||
|     [68, 27, 22], | ||||
|     [70, 27, 21], | ||||
|     [71, 26, 22], | ||||
|     [73, 26, 21], | ||||
|     [75, 25, 21], | ||||
|     [76, 25, 21], | ||||
|     [78, 24, 21], | ||||
|     [80, 23, 21], | ||||
|     [81, 23, 21], | ||||
|     [83, 22, 21], | ||||
|     [85, 21, 20], | ||||
|     [85, 22, 21], | ||||
|     [85, 22, 22], | ||||
|     [84, 24, 22], | ||||
|     [84, 24, 23], | ||||
|     [84, 25, 23], | ||||
|     [83, 27, 23], | ||||
|     [83, 28, 23], | ||||
|     [82, 29, 24], | ||||
|     [82, 30, 24], | ||||
|     [81, 31, 25], | ||||
|     [80, 32, 26], | ||||
|     [80, 33, 26], | ||||
|     [79, 35, 26], | ||||
|     [79, 36, 26], | ||||
|     [78, 37, 27], | ||||
|     [77, 38, 28], | ||||
|     [77, 39, 28], | ||||
|     [76, 41, 28], | ||||
|     [75, 42, 29], | ||||
|     [75, 43, 29], | ||||
|     [74, 44, 30], | ||||
|     [74, 45, 30], | ||||
|     [75, 46, 30], | ||||
|     [75, 46, 30], | ||||
|     [76, 46, 30], | ||||
|     [76, 46, 31], | ||||
|     [77, 46, 31], | ||||
|     [77, 47, 31], | ||||
|     [78, 47, 31], | ||||
|     [78, 47, 32], | ||||
|     [79, 47, 32], | ||||
|     [79, 48, 32], | ||||
|     [80, 49, 32], | ||||
|     [83, 46, 32], | ||||
|     [86, 44, 32], | ||||
|     [90, 42, 31], | ||||
|     [93, 40, 31], | ||||
|     [96, 39, 30], | ||||
|     [100, 36, 30], | ||||
|     [103, 35, 29], | ||||
|     [106, 33, 29], | ||||
|     [110, 30, 29], | ||||
|     [113, 29, 28], | ||||
|     [114, 29, 28], | ||||
|     [115, 29, 28], | ||||
|     [115, 29, 28], | ||||
|     [116, 30, 29], | ||||
|     [117, 29, 28], | ||||
|     [117, 30, 29], | ||||
|     [118, 30, 29], | ||||
|     [119, 30, 29], | ||||
|     [109, 43, 27], | ||||
|     [100, 57, 23], | ||||
|     [90, 71, 20], | ||||
|     [80, 85, 17], | ||||
|     [70, 99, 14], | ||||
|     [74, 98, 12], | ||||
|     [78, 97, 10], | ||||
|     [81, 96, 9], | ||||
|     [85, 95, 7], | ||||
|     [89, 94, 5], | ||||
|     [92, 93, 4], | ||||
|     [96, 92, 2], | ||||
|     [100, 91, 0], | ||||
|     [100, 90, 2], | ||||
|     [100, 88, 5], | ||||
|     [100, 87, 7], | ||||
|     [99, 86, 10], | ||||
|     [99, 85, 12], | ||||
|     [99, 84, 14], | ||||
|     [99, 82, 17], | ||||
|     [98, 81, 20], | ||||
|     [98, 80, 22], | ||||
|     [98, 79, 24], | ||||
|     [98, 77, 27], | ||||
|     [98, 76, 29], | ||||
|     [97, 75, 32], | ||||
|     [97, 73, 35], | ||||
|     [97, 72, 37], | ||||
|     [96, 71, 40], | ||||
|     [96, 69, 43], | ||||
|     [96, 67, 46], | ||||
|     [96, 66, 48], | ||||
|     [95, 65, 51], | ||||
|     [95, 63, 54], | ||||
|     [95, 61, 57], | ||||
|     [94, 60, 60], | ||||
|     [94, 58, 63], | ||||
|     [94, 57, 65], | ||||
|     [93, 55, 69], | ||||
|     [93, 54, 71], | ||||
|     [93, 52, 74], | ||||
|     [92, 51, 77], | ||||
|     [92, 49, 80], | ||||
|     [91, 47, 84], | ||||
|     [91, 46, 86], | ||||
|     [93, 49, 82], | ||||
|     [96, 52, 77], | ||||
|     [98, 55, 73], | ||||
|     [101, 58, 68], | ||||
|     [104, 61, 63], | ||||
|     [106, 65, 58], | ||||
|     [109, 68, 53], | ||||
|     [111, 71, 49], | ||||
|     [114, 74, 44], | ||||
|     [116, 78, 39], | ||||
|     [118, 76, 40], | ||||
|     [119, 74, 42], | ||||
|     [120, 73, 43], | ||||
|     [122, 71, 44], | ||||
|     [123, 69, 46], | ||||
|     [124, 67, 48], | ||||
|     [125, 66, 49], | ||||
|     [127, 64, 50], | ||||
|     [128, 62, 52], | ||||
|     [129, 60, 54], | ||||
|     [131, 58, 55], | ||||
|     [132, 57, 56], | ||||
|     [136, 47, 63], | ||||
|     [139, 38, 70], | ||||
|     [143, 29, 76], | ||||
|     [147, 19, 83], | ||||
|     [151, 9, 90], | ||||
|     [154, 0, 97], | ||||
|     [160, 0, 92], | ||||
|     [171, 0, 82], | ||||
|     [183, 0, 71], | ||||
|     [184, 0, 71], | ||||
| ]; | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| use crate::cli::StreamScreenOptions; | ||||
| use crate::ledwand_dither::{LedwandDither, LedwandDitherOptions}; | ||||
| use crate::ledwand_dither::*; | ||||
| use image::{ | ||||
|     imageops::{dither, resize, BiLevel, FilterType}, | ||||
|     imageops::{resize, FilterType}, | ||||
|     DynamicImage, ImageBuffer, Luma, Rgb, Rgba, | ||||
| }; | ||||
| use log::{error, info, warn}; | ||||
|  | @ -11,42 +11,31 @@ use scap::{ | |||
|     frame::Frame, | ||||
| }; | ||||
| use servicepoint::{ | ||||
|     Bitmap, Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH, | ||||
|     Command, CompressionCode, Connection, Origin, FRAME_PACING, PIXEL_HEIGHT, PIXEL_WIDTH, | ||||
| }; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| pub fn stream_window(connection: &Connection, options: StreamScreenOptions) { | ||||
|     info!("Starting capture with options: {:?}", options); | ||||
|     warn!("this implementation does not drop any frames - set a lower fps or disable dithering if your computer cannot keep up."); | ||||
| 
 | ||||
|     let capturer = match start_capture(&options) { | ||||
|         Some(value) => value, | ||||
|         None => return, | ||||
|     }; | ||||
| 
 | ||||
|     let mut bitmap = Bitmap::new(PIXEL_WIDTH, PIXEL_HEIGHT); | ||||
|     info!("now starting to stream images"); | ||||
|     loop { | ||||
|         let mut frame = get_next_frame(&capturer); | ||||
| 
 | ||||
|         LedwandDither::histogram_correction(&mut frame); | ||||
|         histogram_correction(&mut frame); | ||||
| 
 | ||||
|         let mut orig = frame.clone(); | ||||
|         LedwandDither::blur(&orig, &mut frame); | ||||
|         blur(&orig, &mut frame); | ||||
| 
 | ||||
|         std::mem::swap(&mut frame, &mut orig); | ||||
|         LedwandDither::sharpen(&orig, &mut frame); | ||||
|         sharpen(&orig, &mut frame); | ||||
| 
 | ||||
|         let cutoff = if options.no_dither { | ||||
|             LedwandDither::median_brightness(&frame) | ||||
|         } else { | ||||
|             dither(&mut frame, &BiLevel); | ||||
|             u8::MAX / 2 | ||||
|         }; | ||||
| 
 | ||||
|         for (mut dest, src) in bitmap.iter_mut().zip(frame.pixels()) { | ||||
|             *dest = src.0[0] > cutoff; | ||||
|         } | ||||
|         let bitmap = ostromoukhov_dither(frame, u8::MAX / 2); | ||||
| 
 | ||||
|         connection | ||||
|             .send(Command::BitmapLinearWin( | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter