oxedyne/fe2o3/fe2o3_graphics/src/hevc/intra.rs
23.2 KiB, 20 runs
created by r1870400018:20497, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! Predicting a block from the samples already decoded around it. |
| 2 | //! |
| 3 | //! A still picture refers to nothing but itself, so every block in it is predicted from its own |
| 4 | //! neighbours: the row above and the column to the left, both taken from samples already |
| 5 | //! reconstructed and **before** the deblocking filter has touched them. Thirty-five ways of doing it |
| 6 | //! exist -- planar, flat, and thirty-three directions -- and which one a block uses is the largest |
| 7 | //! part of what its syntax says. |
| 8 | //! |
| 9 | //! Three things happen before a single sample is predicted, and each of them changes the answer: |
| 10 | //! |
| 11 | //! - **Substitution** (§8.4.4.2.2). A block at the edge of the picture, or one whose neighbours have |
| 12 | //! not been decoded yet, has no samples to predict from. Rather than refuse, the missing ones are |
| 13 | //! filled in from whichever neighbour *is* available, working round the boundary from the bottom |
| 14 | //! left; a block with no available neighbour at all is predicted from half of full scale. |
| 15 | //! - **Filtering** (§8.4.4.2.3). For all but the smallest blocks and the flattest directions, the |
| 16 | //! boundary is smoothed with a three-tap filter first, because the prediction is about to be |
| 17 | //! stretched across as much as thirty-two samples and a step in the reference becomes a step in |
| 18 | //! the block. At thirty-two, and only for luma, a boundary that is already nearly a straight line |
| 19 | //! is replaced by the straight line exactly -- which is what keeps a clear sky from banding. |
| 20 | //! - **The boundary filter** (§8.4.4.2.5, §8.4.4.2.6). The first row or column of a flat, vertical or |
| 21 | //! horizontal prediction is nudged towards the neighbour it abuts, because those three modes |
| 22 | //! otherwise leave a visible edge at the block boundary. |
| 23 | //! |
| 24 | //! Every one of those has a "not for chroma" or "not at thirty-two" or "not at four" attached to it, |
| 25 | //! and getting one wrong produces a picture that is *almost* right -- which then predicts the next |
| 26 | //! block, and the next. |
| 27 | //! |
| 28 | //! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\ |
| 29 | //! Anthropic Claude |
| 30 | |
| 31 | use oxedyne_fe2o3_core::prelude::*; |
| 32 | |
| 33 | pub const MAX_TB: usize = 32; // the largest block predicted at once, samples each way |
| 34 | pub const DC: u8 = 1; // the flat prediction: the average of the boundary |
| 35 | pub const PLANAR: u8 = 0; // the plane through the boundary |
| 36 | pub const VERTICAL: u8 = 26; // straight down from the row above |
| 37 | pub const HORIZONTAL: u8 = 10; // straight across from the column to the left |
| 38 | |
| 39 | // How far each direction moves, in thirty-seconds of a sample per row (§8.4.4.2.6, Table 8-5), |
| 40 | // indexed by the prediction mode. Modes 0 and 1 are planar and flat and have no angle; 10 and 26 |
| 41 | // are exactly horizontal and vertical and so have an angle of nought. |
| 42 | const ANGLE: [i32; 35] = [ |
| 43 | 0, 0, |
| 44 | 32, 26, 21, 17, 13, 9, 5, 2, 0, -2, -5, -9, -13, -17, -21, -26, |
| 45 | -32, -26, -21, -17, -13, -9, -5, -2, 0, 2, 5, 9, 13, 17, 21, 26, 32, |
| 46 | ]; |
| 47 | |
| 48 | // The reciprocal of the angle, in two hundred and fifty-sixths (Table 8-6). Only the modes whose |
| 49 | // angle is negative need it, which is 11 to 25; it is what projects the other boundary into the |
| 50 | // reference array, so that a direction pointing up and to the left can still be followed past the |
| 51 | // corner. Nought where it does not apply. |
| 52 | const INV_ANGLE: [i32; 35] = [ |
| 53 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 54 | -4096, -1638, -910, -630, -482, -390, -315, -256, -315, -390, -482, -630, -910, -1638, -4096, |
| 55 | 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 56 | ]; |
| 57 | |
| 58 | /// How far from vertical or horizontal a direction has to be before the boundary is smoothed, |
| 59 | /// by block size (§8.4.4.2.3, Table 8-4). |
| 60 | /// |
| 61 | /// Indexed by the base-two logarithm of the size. At four the boundary is never filtered, at eight |
| 62 | /// only the diagonals are, at sixteen nearly everything is and at thirty-two everything is. |
| 63 | fn smoothing_threshold(size: usize) -> Option<i32> { |
| 64 | match size { |
| 65 | 8 => Some(7), |
| 66 | 16 => Some(1), |
| 67 | 32 => Some(0), |
| 68 | _ => None, |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | /// The samples around a block, in the arrangement the prediction reads them. |
| 73 | /// |
| 74 | /// The specification indexes these as `p[x][y]` with one of the two being −1, which is one row |
| 75 | /// above and one column to the left of the block, each twice as long as the block is wide. Here |
| 76 | /// they are three runs in one array, because that is what they are. |
| 77 | #[derive(Clone, Debug)] |
| 78 | pub struct Around { |
| 79 | size: usize, // the block's side |
| 80 | corner: i32, // p[-1][-1] |
| 81 | left: [i32; MAX_TB * 2], // p[-1][y] for y = 0 to 2 * size - 1, going down |
| 82 | top: [i32; MAX_TB * 2], // p[x][-1] for x = 0 to 2 * size - 1, going right |
| 83 | } |
| 84 | |
| 85 | impl Around { |
| 86 | |
| 87 | /// Room for a block of `size`, with nothing in it and nothing available. |
| 88 | pub fn new(size: usize) -> Self { |
| 89 | Self { |
| 90 | size, |
| 91 | corner: -1, |
| 92 | left: [-1; MAX_TB * 2], |
| 93 | top: [-1; MAX_TB * 2], |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /// Takes a sample known to be available. A negative value means it is not. |
| 98 | pub fn set_corner(&mut self, v: i32) { |
| 99 | self.corner = v; |
| 100 | } |
| 101 | |
| 102 | /// The same for one of the column to the left, `y` from nought. |
| 103 | pub fn set_left(&mut self, y: usize, v: i32) { |
| 104 | if y < self.left.len() { |
| 105 | self.left[y] = v; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | /// And for one of the row above, `x` from nought. |
| 110 | pub fn set_top(&mut self, x: usize, v: i32) { |
| 111 | if x < self.top.len() { |
| 112 | self.top[x] = v; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | /// `p[-1][-1]`. |
| 117 | pub fn corner(&self) -> i32 { |
| 118 | self.corner |
| 119 | } |
| 120 | |
| 121 | /// `p[-1][y]`, where `y` of −1 is the corner. |
| 122 | pub fn left(&self, y: i32) -> i32 { |
| 123 | if y < 0 { |
| 124 | self.corner |
| 125 | } else { |
| 126 | self.left[(y as usize).min(self.left.len() - 1)] |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | /// `p[x][-1]`, where `x` of −1 is the corner. |
| 131 | pub fn top(&self, x: i32) -> i32 { |
| 132 | if x < 0 { |
| 133 | self.corner |
| 134 | } else { |
| 135 | self.top[(x as usize).min(self.top.len() - 1)] |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | /// Fills in every sample that is not available, from the ones that are (§8.4.4.2.2). |
| 140 | /// |
| 141 | /// The walk is anticlockwise from the bottom of the left column, round the corner and along the |
| 142 | /// top: each missing sample takes the value of the one behind it on that path. So a block with |
| 143 | /// only a row above it predicts from that row extended downwards, and one with nothing at all |
| 144 | /// predicts from mid-grey. |
| 145 | pub fn substitute(&mut self, depth: u32) { |
| 146 | let n = self.size * 2; |
| 147 | let none = self.corner < 0 |
| 148 | && self.left[..n].iter().all(|v| *v < 0) |
| 149 | && self.top[..n].iter().all(|v| *v < 0); |
| 150 | if none { |
| 151 | let half = 1i32 << (depth - 1); |
| 152 | self.corner = half; |
| 153 | self.left[..n].fill(half); |
| 154 | self.top[..n].fill(half); |
| 155 | return; |
| 156 | } |
| 157 | // The bottom of the left column is the start of the path, so if it is missing it takes the |
| 158 | // first available sample found anywhere along the path. |
| 159 | if self.left[n - 1] < 0 { |
| 160 | let mut found = -1; |
| 161 | for y in (0..n - 1).rev() { |
| 162 | if self.left[y] >= 0 { |
| 163 | found = self.left[y]; |
| 164 | break; |
| 165 | } |
| 166 | } |
| 167 | if found < 0 && self.corner >= 0 { |
| 168 | found = self.corner; |
| 169 | } |
| 170 | if found < 0 { |
| 171 | for x in 0..n { |
| 172 | if self.top[x] >= 0 { |
| 173 | found = self.top[x]; |
| 174 | break; |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | self.left[n - 1] = found; |
| 179 | } |
| 180 | // Then up the column, round the corner, and along the row. |
| 181 | for y in (0..n - 1).rev() { |
| 182 | if self.left[y] < 0 { |
| 183 | self.left[y] = self.left[y + 1]; |
| 184 | } |
| 185 | } |
| 186 | if self.corner < 0 { |
| 187 | self.corner = self.left[0]; |
| 188 | } |
| 189 | for x in 0..n { |
| 190 | if self.top[x] < 0 { |
| 191 | self.top[x] = if x == 0 { self.corner } else { self.top[x - 1] }; |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | /// Smooths the boundary where the mode and the size call for it (§8.4.4.2.3). |
| 197 | /// |
| 198 | /// `strong` is the sequence's strong-smoothing flag, which only ever applies to a |
| 199 | /// thirty-two-sample luma block whose boundary is already within a small step of a straight |
| 200 | /// line -- there it is replaced by the straight line exactly, which is what stops a clear sky |
| 201 | /// from banding. |
| 202 | pub fn smooth(&mut self, mode: u8, chroma: bool, strong: bool, depth: u32) { |
| 203 | if chroma || mode == DC { |
| 204 | return; |
| 205 | } |
| 206 | let n = self.size * 2; |
| 207 | let threshold = match smoothing_threshold(self.size) { |
| 208 | Some(t) => t, |
| 209 | None => return, |
| 210 | }; |
| 211 | if mode != PLANAR { |
| 212 | let from_flat = ((mode as i32) - 26).abs().min(((mode as i32) - 10).abs()); |
| 213 | if from_flat <= threshold { |
| 214 | return; |
| 215 | } |
| 216 | } |
| 217 | if strong && self.size == 32 { |
| 218 | let step = 1i32 << (depth - 5); |
| 219 | let flat_top = (self.corner + self.top[n - 1] - 2 * self.top[self.size - 1]).abs() < step; |
| 220 | let flat_left = |
| 221 | (self.corner + self.left[n - 1] - 2 * self.left[self.size - 1]).abs() < step; |
| 222 | if flat_top && flat_left { |
| 223 | let (c, r, b) = (self.corner, self.top[n - 1], self.left[n - 1]); |
| 224 | for y in 0..n - 1 { |
| 225 | self.left[y] = ((63 - y as i32) * c + (y as i32 + 1) * b + 32) >> 6; |
| 226 | } |
| 227 | for x in 0..n - 1 { |
| 228 | self.top[x] = ((63 - x as i32) * c + (x as i32 + 1) * r + 32) >> 6; |
| 229 | } |
| 230 | return; |
| 231 | } |
| 232 | } |
| 233 | // The ordinary three-tap filter. Taken from copies, because each output reads its |
| 234 | // neighbours' unfiltered values. |
| 235 | let (was_corner, was_left, was_top) = (self.corner, self.left, self.top); |
| 236 | self.corner = (was_left[0] + 2 * was_corner + was_top[0] + 2) >> 2; |
| 237 | for y in 0..n - 1 { |
| 238 | let above = if y == 0 { was_corner } else { was_left[y - 1] }; |
| 239 | self.left[y] = (was_left[y + 1] + 2 * was_left[y] + above + 2) >> 2; |
| 240 | } |
| 241 | for x in 0..n - 1 { |
| 242 | let before = if x == 0 { was_corner } else { was_top[x - 1] }; |
| 243 | self.top[x] = (was_top[x + 1] + 2 * was_top[x] + before + 2) >> 2; |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | /// Predicts a block of `size` in `mode` from the samples around it. |
| 249 | /// |
| 250 | /// `out` takes `size * size` samples in raster order. `depth` is the bit depth of the component, |
| 251 | /// which is what the prediction is clipped to. |
| 252 | pub fn predict( |
| 253 | around: &Around, |
| 254 | mode: u8, |
| 255 | size: usize, |
| 256 | chroma: bool, |
| 257 | depth: u32, |
| 258 | out: &mut [i32], |
| 259 | ) |
| 260 | -> Outcome<()> |
| 261 | { |
| 262 | if mode as usize >= ANGLE.len() { |
| 263 | return Err(err!("Intra prediction mode {} does not exist.", mode; Invalid, Input)); |
| 264 | } |
| 265 | if out.len() < size * size { |
| 266 | return Err(err!( |
| 267 | "A block of {0} wants {1} samples and was given {2}.", |
| 268 | size, size * size, out.len(); Invalid, Input)); |
| 269 | } |
| 270 | match mode { |
| 271 | PLANAR => planar(around, size, out), |
| 272 | DC => flat(around, size, chroma, out), |
| 273 | _ => angular(around, mode, size, chroma, depth, out), |
| 274 | } |
| 275 | Ok(()) |
| 276 | } |
| 277 | |
| 278 | /// The plane through the four boundaries (§8.4.4.2.4). |
| 279 | /// |
| 280 | /// Each sample is a weighted average of the four samples the block's edges point at: left, right, |
| 281 | /// above and below. The right and below ones do not exist, so the sample past the end of the row |
| 282 | /// above stands in for the right edge and the one past the bottom of the left column for the |
| 283 | /// bottom -- which is why the boundary is twice as long as the block. |
| 284 | fn planar(around: &Around, size: usize, out: &mut [i32]) { |
| 285 | let n = size as i32; |
| 286 | let shift = size.trailing_zeros() + 1; |
| 287 | let right = around.top(n); |
| 288 | let below = around.left(n); |
| 289 | for y in 0..size { |
| 290 | for x in 0..size { |
| 291 | let v = (n - 1 - x as i32) * around.left(y as i32) |
| 292 | + (x as i32 + 1) * right |
| 293 | + (n - 1 - y as i32) * around.top(x as i32) |
| 294 | + (y as i32 + 1) * below |
| 295 | + n; |
| 296 | out[y * size + x] = v >> shift; |
| 297 | } |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | /// The average of the boundary, with the first row and column pulled towards it (§8.4.4.2.5). |
| 302 | fn flat(around: &Around, size: usize, chroma: bool, out: &mut [i32]) { |
| 303 | let n = size as i32; |
| 304 | let mut sum = n; |
| 305 | for i in 0..size { |
| 306 | sum += around.top(i as i32) + around.left(i as i32); |
| 307 | } |
| 308 | let dc = sum >> (size.trailing_zeros() + 1); |
| 309 | for v in out.iter_mut().take(size * size) { |
| 310 | *v = dc; |
| 311 | } |
| 312 | // The boundary filter, which is luma only and not at the largest size: a flat block against a |
| 313 | // detailed neighbour otherwise leaves a visible step exactly at the block edge. |
| 314 | if chroma || size >= 32 { |
| 315 | return; |
| 316 | } |
| 317 | out[0] = (around.left(0) + 2 * dc + around.top(0) + 2) >> 2; |
| 318 | for x in 1..size { |
| 319 | out[x] = (around.top(x as i32) + 3 * dc + 2) >> 2; |
| 320 | } |
| 321 | for y in 1..size { |
| 322 | out[y * size] = (around.left(y as i32) + 3 * dc + 2) >> 2; |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | /// One of the thirty-three directions (§8.4.4.2.6). |
| 327 | /// |
| 328 | /// The samples are read along the boundary the direction points at, at a position that moves by |
| 329 | /// `intraPredAngle` thirty-seconds of a sample for every row (or column) crossed, with a two-tap |
| 330 | /// interpolation where the position lands between two samples. Where the direction points up and to |
| 331 | /// the left, the *other* boundary is projected into the reference array behind the corner, so that |
| 332 | /// following the direction backwards still finds samples. |
| 333 | fn angular(around: &Around, mode: u8, size: usize, chroma: bool, depth: u32, out: &mut [i32]) { |
| 334 | let angle = ANGLE[mode as usize]; |
| 335 | let n = size as i32; |
| 336 | // The reference runs from -size to 2*size, and index 0 of the array is position -size. |
| 337 | let mut ref_: [i32; MAX_TB * 4 + 1] = [0; MAX_TB * 4 + 1]; |
| 338 | let base = size; |
| 339 | let down = mode >= 18; |
| 340 | let main = |i: i32| if down { around.top(i) } else { around.left(i) }; |
| 341 | let side = |i: i32| if down { around.left(i) } else { around.top(i) }; |
| 342 | |
| 343 | for x in 0..=n { |
| 344 | ref_[base + x as usize] = main(x - 1); |
| 345 | } |
| 346 | if angle < 0 { |
| 347 | let reach = (n * angle) >> 5; |
| 348 | if reach < -1 { |
| 349 | let inv = INV_ANGLE[mode as usize]; |
| 350 | for x in reach..=-1 { |
| 351 | let at = ((x * inv + 128) >> 8) - 1; |
| 352 | ref_[(base as i32 + x) as usize] = side(at); |
| 353 | } |
| 354 | } |
| 355 | } else { |
| 356 | for x in n + 1..=2 * n { |
| 357 | ref_[base + x as usize] = main(x - 1); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | for y in 0..size { |
| 362 | for x in 0..size { |
| 363 | // Which of the two axes counts the steps depends on which boundary is being read. |
| 364 | let along = if down { y as i32 } else { x as i32 }; |
| 365 | let across = if down { x as i32 } else { y as i32 }; |
| 366 | let pos = (along + 1) * angle; |
| 367 | let idx = pos >> 5; |
| 368 | let frac = pos & 31; |
| 369 | let at = base as i32 + across + idx + 1; |
| 370 | let v = if frac != 0 { |
| 371 | ((32 - frac) * ref_[at as usize] + frac * ref_[(at + 1) as usize] + 16) >> 5 |
| 372 | } else { |
| 373 | ref_[at as usize] |
| 374 | }; |
| 375 | out[y * size + x] = v; |
| 376 | } |
| 377 | } |
| 378 | |
| 379 | // The boundary filter for exactly vertical and exactly horizontal, luma only, under |
| 380 | // thirty-two: the prediction copies one edge and would otherwise ignore the other entirely. |
| 381 | if chroma || size >= 32 { |
| 382 | return; |
| 383 | } |
| 384 | let top = (1i32 << depth) - 1; |
| 385 | if mode == VERTICAL { |
| 386 | for y in 0..size { |
| 387 | let v = around.top(0) + ((around.left(y as i32) - around.corner()) >> 1); |
| 388 | out[y * size] = v.clamp(0, top); |
| 389 | } |
| 390 | } else if mode == HORIZONTAL { |
| 391 | for x in 0..size { |
| 392 | let v = around.left(0) + ((around.top(x as i32) - around.corner()) >> 1); |
| 393 | out[x] = v.clamp(0, top); |
| 394 | } |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | #[cfg(test)] |
| 399 | mod tests { |
| 400 | use super::*; |
| 401 | |
| 402 | /// A boundary whose every sample is the same value, all of it available. |
| 403 | fn flat_around(size: usize, v: i32) -> Around { |
| 404 | let mut a = Around::new(size); |
| 405 | a.set_corner(v); |
| 406 | for i in 0..size * 2 { |
| 407 | a.set_left(i, v); |
| 408 | a.set_top(i, v); |
| 409 | } |
| 410 | a |
| 411 | } |
| 412 | |
| 413 | #[test] |
| 414 | fn test_a_flat_neighbourhood_predicts_a_flat_block_00() -> Outcome<()> { |
| 415 | // The property every mode shares and none may break: if everything around a block is the |
| 416 | // same value, the block is that value. It holds for planar, for flat and for all |
| 417 | // thirty-three directions, and it catches an interpolation whose weights do not sum to |
| 418 | // thirty-two, a reference array read one sample out of step, and a boundary filter applied |
| 419 | // where it should not be. |
| 420 | for size in [4usize, 8, 16, 32] { |
| 421 | for mode in 0..35u8 { |
| 422 | let mut around = flat_around(size, 128); |
| 423 | around.smooth(mode, false, true, 8); |
| 424 | let mut out = vec![0i32; size * size]; |
| 425 | res!(predict(&around, mode, size, false, 8, &mut out)); |
| 426 | for (i, v) in out.iter().enumerate() { |
| 427 | req!(*v, 128, |
| 428 | "mode {} at {} put {} at sample {} of a uniform neighbourhood", |
| 429 | mode, size, v, i); |
| 430 | } |
| 431 | } |
| 432 | } |
| 433 | Ok(()) |
| 434 | } |
| 435 | |
| 436 | #[test] |
| 437 | fn test_the_two_flat_directions_copy_the_boundary_they_point_at_01() -> Outcome<()> { |
| 438 | // Mode 26 is straight down and mode 10 is straight across, so each column (or row) of the |
| 439 | // block is a copy of the sample it points at. Checked on chroma, where the boundary filter |
| 440 | // that would otherwise nudge the first row or column is not applied -- the filter itself is |
| 441 | // checked separately below. |
| 442 | let size = 8; |
| 443 | let mut around = Around::new(size); |
| 444 | around.set_corner(100); |
| 445 | for i in 0..size * 2 { |
| 446 | around.set_left(i, 40 + i as i32); |
| 447 | around.set_top(i, 10 * (i as i32 + 1)); |
| 448 | } |
| 449 | let mut out = vec![0i32; size * size]; |
| 450 | res!(predict(&around, VERTICAL, size, true, 8, &mut out)); |
| 451 | for y in 0..size { |
| 452 | for x in 0..size { |
| 453 | req!(out[y * size + x], around.top(x as i32), |
| 454 | "straight down put the wrong sample at ({}, {})", x, y); |
| 455 | } |
| 456 | } |
| 457 | res!(predict(&around, HORIZONTAL, size, true, 8, &mut out)); |
| 458 | for y in 0..size { |
| 459 | for x in 0..size { |
| 460 | req!(out[y * size + x], around.left(y as i32), |
| 461 | "straight across put the wrong sample at ({}, {})", x, y); |
| 462 | } |
| 463 | } |
| 464 | Ok(()) |
| 465 | } |
| 466 | |
| 467 | #[test] |
| 468 | fn test_the_two_diagonals_shift_by_one_sample_a_row_02() -> Outcome<()> { |
| 469 | // Modes 2 and 34 are the forty-five degree directions, whose angle is exactly thirty-two |
| 470 | // thirty-seconds -- one whole sample a row, so no interpolation happens and the block is |
| 471 | // the boundary shifted. That makes them the two directions whose answer can be written down |
| 472 | // without doing any arithmetic, which is what makes them worth asserting. |
| 473 | let size = 8; |
| 474 | let mut around = Around::new(size); |
| 475 | around.set_corner(1); |
| 476 | for i in 0..size * 2 { |
| 477 | around.set_left(i, 100 + i as i32); |
| 478 | around.set_top(i, 200 + i as i32); |
| 479 | } |
| 480 | let mut out = vec![0i32; size * size]; |
| 481 | // Thirty-four points up and to the right, reading the row above. |
| 482 | res!(predict(&around, 34, size, true, 8, &mut out)); |
| 483 | for y in 0..size { |
| 484 | for x in 0..size { |
| 485 | req!(out[y * size + x], around.top((x + y + 1) as i32), |
| 486 | "mode 34 at ({}, {})", x, y); |
| 487 | } |
| 488 | } |
| 489 | // Two points down and to the left, reading the column. |
| 490 | res!(predict(&around, 2, size, true, 8, &mut out)); |
| 491 | for y in 0..size { |
| 492 | for x in 0..size { |
| 493 | req!(out[y * size + x], around.left((x + y + 1) as i32), |
| 494 | "mode 2 at ({}, {})", x, y); |
| 495 | } |
| 496 | } |
| 497 | Ok(()) |
| 498 | } |
| 499 | |
| 500 | #[test] |
| 501 | fn test_a_missing_neighbourhood_is_filled_from_what_there_is_03() -> Outcome<()> { |
| 502 | // A block at the very top left of a picture has nothing around it and predicts from half |
| 503 | // scale; one with only a row above it extends that row round the corner and down the side. |
| 504 | // Both are the ordinary case at a picture's edge, not an error. |
| 505 | let size = 4; |
| 506 | let mut nothing = Around::new(size); |
| 507 | nothing.substitute(8); |
| 508 | req!(nothing.corner(), 128); |
| 509 | req!(nothing.left(0), 128); |
| 510 | req!(nothing.top(7), 128); |
| 511 | |
| 512 | let mut only_above = Around::new(size); |
| 513 | only_above.set_corner(60); |
| 514 | for x in 0..size * 2 { |
| 515 | only_above.set_top(x, 70 + x as i32); |
| 516 | } |
| 517 | only_above.substitute(8); |
| 518 | // The corner was available, so the whole left column takes it -- the walk runs from the |
| 519 | // bottom of the column upwards, and every one of them is missing. |
| 520 | for y in 0..size * 2 { |
| 521 | req!(only_above.left(y as i32), 60, "the left column at {} was not filled", y); |
| 522 | } |
| 523 | req!(only_above.top(0), 70, "an available sample was overwritten"); |
| 524 | |
| 525 | // And a block with only a left column extends the bottom sample nowhere but keeps what it |
| 526 | // has, filling the row above from the corner along. |
| 527 | let mut only_left = Around::new(size); |
| 528 | for y in 0..size * 2 { |
| 529 | only_left.set_left(y, 90 + y as i32); |
| 530 | } |
| 531 | only_left.substitute(8); |
| 532 | req!(only_left.corner(), 90, "the corner did not take the top of the column"); |
| 533 | for x in 0..size * 2 { |
| 534 | req!(only_left.top(x as i32), 90, "the row above at {} was not filled", x); |
| 535 | } |
| 536 | Ok(()) |
| 537 | } |
| 538 | |
| 539 | #[test] |
| 540 | fn test_the_boundary_is_smoothed_only_where_it_should_be_04() -> Outcome<()> { |
| 541 | // The three-tap filter is not applied to chroma, nor to the smallest blocks, nor to |
| 542 | // directions close to vertical or horizontal, nor to the flat mode. Each of those |
| 543 | // exceptions is a line in the specification and each changes the picture. |
| 544 | let step = |size: usize| { |
| 545 | let mut a = Around::new(size); |
| 546 | a.set_corner(0); |
| 547 | for i in 0..size * 2 { |
| 548 | // A step in the middle of each boundary, which a filter would round off. |
| 549 | a.set_left(i, if i < size { 0 } else { 255 }); |
| 550 | a.set_top(i, if i < size { 0 } else { 255 }); |
| 551 | } |
| 552 | a |
| 553 | }; |
| 554 | // Four is never filtered. |
| 555 | let mut small = step(4); |
| 556 | small.smooth(PLANAR, false, false, 8); |
| 557 | req!(small.left(4), 255, "a four-sample boundary was filtered"); |
| 558 | // Chroma is never filtered. |
| 559 | let mut chroma = step(16); |
| 560 | chroma.smooth(PLANAR, true, false, 8); |
| 561 | req!(chroma.left(16), 255, "a chroma boundary was filtered"); |
| 562 | // The flat mode never filters. |
| 563 | let mut dc = step(16); |
| 564 | dc.smooth(DC, false, false, 8); |
| 565 | req!(dc.left(16), 255, "the flat mode filtered its boundary"); |
| 566 | // Straight down at eight is within the threshold of seven, so it does not filter. |
| 567 | let mut near = step(8); |
| 568 | near.smooth(VERTICAL, false, false, 8); |
| 569 | req!(near.left(8), 255, "a direction inside the threshold was filtered"); |
| 570 | // A diagonal at eight is outside it, so it does. |
| 571 | let mut far = step(8); |
| 572 | far.smooth(2, false, false, 8); |
| 573 | let softened = far.left(8) != 255; |
| 574 | req!(softened, true, "a diagonal at eight was not filtered"); |
| 575 | Ok(()) |
| 576 | } |
| 577 | |
| 578 | #[test] |
| 579 | fn test_the_flat_mode_pulls_its_first_row_towards_the_neighbour_05() -> Outcome<()> { |
| 580 | // The boundary filter on the flat mode, which is luma only and not at thirty-two. A block |
| 581 | // whose average is one thing and whose neighbour is another must not meet that neighbour |
| 582 | // with a step, so the first row and column are moved three quarters of the way to the |
| 583 | // average and a quarter of the way to the neighbour. |
| 584 | let size = 8; |
| 585 | let mut around = Around::new(size); |
| 586 | around.set_corner(0); |
| 587 | for i in 0..size * 2 { |
| 588 | around.set_left(i, 0); |
| 589 | around.set_top(i, 80); |
| 590 | } |
| 591 | let mut out = vec![0i32; size * size]; |
| 592 | res!(predict(&around, DC, size, false, 8, &mut out)); |
| 593 | // The average of a boundary half nought and half eighty. |
| 594 | let dc = 40; |
| 595 | req!(out[size + 1], dc, "the middle of the block is not the average"); |
| 596 | req!(out[1], (80 + 3 * dc + 2) >> 2, "the first row was not pulled towards the row above"); |
| 597 | req!(out[size], (0 + 3 * dc + 2) >> 2, "the first column was not pulled"); |
| 598 | req!(out[0], (0 + 2 * dc + 80 + 2) >> 2, "the corner sample is wrong"); |
| 599 | |
| 600 | // At thirty-two it does not happen at all. |
| 601 | let mut big = Around::new(32); |
| 602 | big.set_corner(0); |
| 603 | for i in 0..64 { |
| 604 | big.set_left(i, 0); |
| 605 | big.set_top(i, 80); |
| 606 | } |
| 607 | let mut out = vec![0i32; 32 * 32]; |
| 608 | res!(predict(&big, DC, 32, false, 8, &mut out)); |
| 609 | req!(out[1], out[33], "a thirty-two block had its first row filtered"); |
| 610 | Ok(()) |
| 611 | } |
| 612 | |
| 613 | #[test] |
| 614 | fn test_planar_is_symmetric_and_lands_where_the_equation_says_06() -> Outcome<()> { |
| 615 | // Planar is a bilinear surface fitted to the four boundaries. It does **not** reproduce an |
| 616 | // arbitrary plane -- the right and bottom edges are single samples taken from past the end |
| 617 | // of the two boundaries, so a ramp comes back bent -- and a test claiming otherwise says |
| 618 | // more about the person writing it than about the decoder. |
| 619 | // |
| 620 | // What does hold, and what catches the mistake worth catching, is symmetry: a |
| 621 | // neighbourhood that is unchanged by swapping x and y must predict a block that is |
| 622 | // unchanged by swapping x and y. An implementation with the two axes crossed fails it. |
| 623 | let size = 8; |
| 624 | let mut around = Around::new(size); |
| 625 | around.set_corner(0); |
| 626 | for i in 0..size * 2 { |
| 627 | around.set_left(i, i as i32 + 1); |
| 628 | around.set_top(i, i as i32 + 1); |
| 629 | } |
| 630 | let mut out = vec![0i32; size * size]; |
| 631 | res!(predict(&around, PLANAR, size, true, 8, &mut out)); |
| 632 | for y in 0..size { |
| 633 | for x in 0..size { |
| 634 | req!(out[y * size + x], out[x * size + y], |
| 635 | "the plane is not symmetric at ({}, {})", x, y); |
| 636 | } |
| 637 | } |
| 638 | // And one sample worked through the published equation by hand, which is what says the |
| 639 | // weights are the right way round rather than merely symmetric. At (3, 1) with this |
| 640 | // boundary: (4*2 + 4*9 + 6*4 + 2*9 + 8) >> 4. |
| 641 | req!(out[1 * size + 3], (4 * 2 + 4 * 9 + 6 * 4 + 2 * 9 + 8) >> 4); |
| 642 | Ok(()) |
| 643 | } |
| 644 | } |