Builds two maps: `importance_map` - approximation of areas with high-frequency noise, except straight edges. 1=flat, 0=noisy. edges - noise map including all edges
(&mut self)
| 236 | /// `importance_map` - approximation of areas with high-frequency noise, except straight edges. 1=flat, 0=noisy. |
| 237 | /// edges - noise map including all edges |
| 238 | pub(crate) fn contrast_maps(&mut self) -> Result<(), Error> { |
| 239 | let width = self.width(); |
| 240 | let height = self.height(); |
| 241 | if width < 4 || height < 4 || (3 * width * height) > LIQ_HIGH_MEMORY_LIMIT { |
| 242 | return Ok(()); // shrug |
| 243 | } |
| 244 | |
| 245 | let noise = if let Some(n) = self.importance_map.as_deref_mut() { n } else { |
| 246 | let vec = try_zero_vec(width * height)?; |
| 247 | self.importance_map.get_or_insert_with(move || vec.into_boxed_slice()) |
| 248 | }; |
| 249 | |
| 250 | let edges = if let Some(e) = self.edges.as_mut() { e } else { |
| 251 | let vec = try_zero_vec(width * height)?; |
| 252 | self.edges.get_or_insert_with(move || vec.into_boxed_slice()) |
| 253 | }; |
| 254 | |
| 255 | let mut rows_iter = self.px.all_rows_f()?.chunks_exact(width); |
| 256 | |
| 257 | let mut next_row = rows_iter.next().ok_or(Error::InvalidPointer)?; |
| 258 | let mut curr_row = next_row; |
| 259 | let mut prev_row; |
| 260 | |
| 261 | for (noise_row, edges_row) in noise[..width * height].chunks_exact_mut(width).zip(edges[..width * height].chunks_exact_mut(width)) { |
| 262 | prev_row = curr_row; |
| 263 | curr_row = next_row; |
| 264 | next_row = rows_iter.next().unwrap_or(next_row); |
| 265 | let mut prev; |
| 266 | let mut curr = curr_row[0].0; |
| 267 | let mut next = curr; |
| 268 | for i in 0..width { |
| 269 | prev = curr; |
| 270 | curr = next; |
| 271 | next = curr_row[(i + 1).min(width - 1)].0; |
| 272 | // contrast is difference between pixels neighbouring horizontally and vertically |
| 273 | let horiz = (prev + next - curr * 2.).map(f32::abs); // noise is amplified |
| 274 | let prevl = prev_row[i].0; |
| 275 | let nextl = next_row[i].0; |
| 276 | let vert = (prevl + nextl - curr * 2.).map(f32::abs); |
| 277 | let horiz = horiz.a.max(horiz.r).max(horiz.g.max(horiz.b)); |
| 278 | let vert = vert.a.max(vert.r).max(vert.g.max(vert.b)); |
| 279 | let edge = horiz.max(vert); |
| 280 | let mut z = (horiz - vert).abs().mul_add(-0.5, edge); |
| 281 | z = 1. - z.max(horiz.min(vert)); |
| 282 | z *= z; |
| 283 | z *= z; |
| 284 | // 85 is about 1/3rd of weight (not 0, because noisy pixels still need to be included, just not as precisely). |
| 285 | noise_row[i] = z.mul_add(176., 80.) as u8; |
| 286 | edges_row[i] = ((1. - edge) * 256.) as u8; |
| 287 | } |
| 288 | } |
| 289 | // noise areas are shrunk and then expanded to remove thin edges from the map |
| 290 | let mut tmp = try_zero_vec(width * height)?; |
| 291 | liq_max3(noise, &mut tmp, width, height); |
| 292 | liq_max3(&tmp, noise, width, height); |
| 293 | liq_blur(noise, &mut tmp, width, height, 3); |
| 294 | liq_max3(noise, &mut tmp, width, height); |
| 295 | liq_min3(&tmp, noise, width, height); |
no test coverage detected