Oregami
Repositories/oxedyne/fe2o3

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
31use oxedyne_fe2o3_core::prelude::*;
32
33pub const MAX_TB: usize = 32; // the largest block predicted at once, samples each way
34pub const DC: u8 = 1; // the flat prediction: the average of the boundary
35pub const PLANAR: u8 = 0; // the plane through the boundary
36pub const VERTICAL: u8 = 26; // straight down from the row above
37pub 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.
42const 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.
52const 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.
63fn 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)]
78pub 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
85impl 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.
252pub 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.
284fn 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).
302fn 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.
333fn 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)]
399mod 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}