(row_pixels: &[f_pixel], output_pixels_row: &mut [MaybeUninit<PalIndexRemap>], width: u32, dither_map: &[u8], base_dithering_level: f32, max_dither_error: f32, n: &Nearest, palette: &[f_pixel], transp
| 233 | |
| 234 | #[inline(never)] |
| 235 | fn dither_row(row_pixels: &[f_pixel], output_pixels_row: &mut [MaybeUninit<PalIndexRemap>], width: u32, dither_map: &[u8], base_dithering_level: f32, max_dither_error: f32, n: &Nearest, palette: &[f_pixel], transparent_index: PalIndexRemap, bg_pixels: &[f_pixel], guess_from_remapped_pixels: bool, diffusion: &mut [f_pixel], even_row: bool) { |
| 236 | let width = width as usize; |
| 237 | assert_eq!(row_pixels.len(), width); |
| 238 | assert_eq!(output_pixels_row.len(), width); |
| 239 | |
| 240 | let (thiserr, nexterr) = { |
| 241 | // +2 saves from checking out of bounds access |
| 242 | let (d1, d2) = diffusion.split_at_mut(width + 2); |
| 243 | if even_row { (d1, d2) } else { (d2, d1) } |
| 244 | }; |
| 245 | |
| 246 | nexterr.fill_with(f_pixel::default); |
| 247 | |
| 248 | let mut undithered_bg_used = 0u8; |
| 249 | let mut last_match = 0; |
| 250 | for x in 0..width { |
| 251 | let col = if even_row { x } else { width - 1 - x }; |
| 252 | let thiserr = &mut thiserr[col .. col + 3]; |
| 253 | let nexterr = &mut nexterr[col .. col + 3]; |
| 254 | let input_px = row_pixels[col]; |
| 255 | |
| 256 | let mut dither_level = base_dithering_level; |
| 257 | if let Some(&l) = dither_map.get(col) { |
| 258 | dither_level *= f32::from(l); |
| 259 | } |
| 260 | |
| 261 | let spx = get_dithered_pixel(dither_level, max_dither_error, thiserr[1], input_px); |
| 262 | let guessed_match = if guess_from_remapped_pixels { |
| 263 | unsafe { output_pixels_row[col].assume_init() } |
| 264 | } else { |
| 265 | last_match |
| 266 | }; |
| 267 | let (matched, dither_diff) = n.search(&spx, guessed_match as _); |
| 268 | let mut matched = matched as PalIndexRemap; |
| 269 | last_match = matched as PalIndexRemap; |
| 270 | let mut output_px = palette[last_match as usize]; |
| 271 | if let Some(bg_pixel) = bg_pixels.get(col) { |
| 272 | // if the background makes better match *with* dithering, it's a definitive win |
| 273 | let bg_for_dither_diff = spx.diff(bg_pixel); |
| 274 | if bg_for_dither_diff <= dither_diff { |
| 275 | output_px = *bg_pixel; |
| 276 | matched = transparent_index; |
| 277 | } else if undithered_bg_used > 1 { |
| 278 | // the undithered fallback can cause artifacts when too many undithered pixels accumulate a big dithering error |
| 279 | // so periodically ignore undithered fallback to prevent that |
| 280 | undithered_bg_used = 0; |
| 281 | } else { |
| 282 | // if dithering is not applied, there's a high risk of creating artifacts (flat areas, error accumulating badly), |
| 283 | // OTOH poor dithering disturbs static backgrounds and creates oscilalting frames that break backgrounds |
| 284 | // back and forth in two differently bad ways |
| 285 | let max_diff = input_px.diff(bg_pixel); |
| 286 | let dithered_diff = input_px.diff(&output_px); |
| 287 | // if dithering is worse than natural difference between frames |
| 288 | // (this rule dithers moving areas, but does not dither static areas) |
| 289 | if dithered_diff > max_diff { |
| 290 | // then see if an undithered color is closer to the ideal |
| 291 | let guessed_px = palette[guessed_match as usize]; |
| 292 | let undithered_diff = input_px.diff(&guessed_px); // If dithering error is crazy high, don't propagate it that much |
no test coverage detected