image_webp/
loop_filter.rs

1//! Does loop filtering on webp lossy images
2
3#[inline]
4fn c(val: i32) -> i32 {
5    val.clamp(-128, 127)
6}
7
8//unsigned to signed
9#[inline]
10fn u2s(val: u8) -> i32 {
11    i32::from(val) - 128
12}
13
14//signed to unsigned
15#[inline]
16fn s2u(val: i32) -> u8 {
17    (c(val) + 128) as u8
18}
19
20#[inline]
21const fn diff(val1: u8, val2: u8) -> u8 {
22    u8::abs_diff(val1, val2)
23}
24
25/// Used in both the simple and normal filters described in 15.2 and 15.3
26///
27/// Adjusts the 2 middle pixels in a vertical loop filter
28fn common_adjust_vertical(
29    use_outer_taps: bool,
30    pixels: &mut [u8],
31    point: usize,
32    stride: usize,
33) -> i32 {
34    let p1 = u2s(pixels[point - 2 * stride]);
35    let p0 = u2s(pixels[point - stride]);
36    let q0 = u2s(pixels[point]);
37    let q1 = u2s(pixels[point + stride]);
38
39    //value for the outer 2 pixels
40    let outer = if use_outer_taps { c(p1 - q1) } else { 0 };
41
42    let a = c(outer + 3 * (q0 - p0));
43
44    let b = (c(a + 3)) >> 3;
45
46    let a = (c(a + 4)) >> 3;
47
48    pixels[point] = s2u(q0 - a);
49    pixels[point - stride] = s2u(p0 + b);
50
51    a
52}
53
54/// Used in both the simple and normal filters described in 15.2 and 15.3
55///
56/// Adjusts the 2 middle pixels in a horizontal loop filter
57fn common_adjust_horizontal(use_outer_taps: bool, pixels: &mut [u8]) -> i32 {
58    let p1 = u2s(pixels[2]);
59    let p0 = u2s(pixels[3]);
60    let q0 = u2s(pixels[4]);
61    let q1 = u2s(pixels[5]);
62
63    //value for the outer 2 pixels
64    let outer = if use_outer_taps { c(p1 - q1) } else { 0 };
65
66    let a = c(outer + 3 * (q0 - p0));
67
68    let b = (c(a + 3)) >> 3;
69
70    let a = (c(a + 4)) >> 3;
71
72    pixels[4] = s2u(q0 - a);
73    pixels[3] = s2u(p0 + b);
74
75    a
76}
77
78#[inline]
79fn simple_threshold_vertical(
80    filter_limit: i32,
81    pixels: &[u8],
82    point: usize,
83    stride: usize,
84) -> bool {
85    i32::from(diff(pixels[point - stride], pixels[point])) * 2
86        + i32::from(diff(pixels[point - 2 * stride], pixels[point + stride])) / 2
87        <= filter_limit
88}
89
90#[inline]
91fn simple_threshold_horizontal(filter_limit: i32, pixels: &[u8]) -> bool {
92    assert!(pixels.len() >= 6); // one bounds check up front eliminates all subsequent checks in this function
93    i32::from(diff(pixels[3], pixels[4])) * 2 + i32::from(diff(pixels[2], pixels[5])) / 2
94        <= filter_limit
95}
96
97fn should_filter_vertical(
98    interior_limit: u8,
99    edge_limit: u8,
100    pixels: &[u8],
101    point: usize,
102    stride: usize,
103) -> bool {
104    simple_threshold_vertical(i32::from(edge_limit), pixels, point, stride)
105        // this looks like an erroneous way to compute differences between 8 points, but isn't:
106        // there are actually only 6 diff comparisons required as per the spec:
107        // https://www.rfc-editor.org/rfc/rfc6386#section-20.6
108        && diff(pixels[point - 4 * stride], pixels[point - 3 * stride]) <= interior_limit
109        && diff(pixels[point - 3 * stride], pixels[point - 2 * stride]) <= interior_limit
110        && diff(pixels[point - 2 * stride], pixels[point - stride]) <= interior_limit
111        && diff(pixels[point + 3 * stride], pixels[point + 2 * stride]) <= interior_limit
112        && diff(pixels[point + 2 * stride], pixels[point + stride]) <= interior_limit
113        && diff(pixels[point + stride], pixels[point]) <= interior_limit
114}
115
116fn should_filter_horizontal(interior_limit: u8, edge_limit: u8, pixels: &[u8]) -> bool {
117    assert!(pixels.len() >= 8); // one bounds check up front eliminates all subsequent checks in this function
118    simple_threshold_horizontal(i32::from(edge_limit), pixels)
119        // this looks like an erroneous way to compute differences between 8 points, but isn't:
120        // there are actually only 6 diff comparisons required as per the spec:
121        // https://www.rfc-editor.org/rfc/rfc6386#section-20.6
122        && diff(pixels[0], pixels[1]) <= interior_limit
123        && diff(pixels[1], pixels[2]) <= interior_limit
124        && diff(pixels[2], pixels[3]) <= interior_limit
125        && diff(pixels[7], pixels[6]) <= interior_limit
126        && diff(pixels[6], pixels[5]) <= interior_limit
127        && diff(pixels[5], pixels[4]) <= interior_limit
128}
129
130#[inline]
131fn high_edge_variance_vertical(threshold: u8, pixels: &[u8], point: usize, stride: usize) -> bool {
132    diff(pixels[point - 2 * stride], pixels[point - stride]) > threshold
133        || diff(pixels[point + stride], pixels[point]) > threshold
134}
135
136#[inline]
137fn high_edge_variance_horizontal(threshold: u8, pixels: &[u8]) -> bool {
138    diff(pixels[2], pixels[3]) > threshold || diff(pixels[5], pixels[4]) > threshold
139}
140
141/// Part of the simple filter described in 15.2 in the specification
142///
143/// Affects 4 pixels on an edge(2 each side)
144pub(crate) fn simple_segment_vertical(
145    edge_limit: u8,
146    pixels: &mut [u8],
147    point: usize,
148    stride: usize,
149) {
150    if simple_threshold_vertical(i32::from(edge_limit), pixels, point, stride) {
151        common_adjust_vertical(true, pixels, point, stride);
152    }
153}
154
155/// Part of the simple filter described in 15.2 in the specification
156///
157/// Affects 4 pixels on an edge(2 each side)
158pub(crate) fn simple_segment_horizontal(edge_limit: u8, pixels: &mut [u8]) {
159    if simple_threshold_horizontal(i32::from(edge_limit), pixels) {
160        common_adjust_horizontal(true, pixels);
161    }
162}
163
164/// Part of the normal filter described in 15.3 in the specification
165///
166/// Filters on the 8 pixels on the edges between subblocks inside a macroblock
167pub(crate) fn subblock_filter_vertical(
168    hev_threshold: u8,
169    interior_limit: u8,
170    edge_limit: u8,
171    pixels: &mut [u8],
172    point: usize,
173    stride: usize,
174) {
175    if should_filter_vertical(interior_limit, edge_limit, pixels, point, stride) {
176        let hv = high_edge_variance_vertical(hev_threshold, pixels, point, stride);
177
178        let a = (common_adjust_vertical(hv, pixels, point, stride) + 1) >> 1;
179
180        if !hv {
181            pixels[point + stride] = s2u(u2s(pixels[point + stride]) - a);
182            pixels[point - 2 * stride] = s2u(u2s(pixels[point - 2 * stride]) + a);
183        }
184    }
185}
186
187/// Part of the normal filter described in 15.3 in the specification
188///
189/// Filters on the 8 pixels on the edges between subblocks inside a macroblock
190pub(crate) fn subblock_filter_horizontal(
191    hev_threshold: u8,
192    interior_limit: u8,
193    edge_limit: u8,
194    pixels: &mut [u8],
195) {
196    if should_filter_horizontal(interior_limit, edge_limit, pixels) {
197        let hv = high_edge_variance_horizontal(hev_threshold, pixels);
198
199        let a = (common_adjust_horizontal(hv, pixels) + 1) >> 1;
200
201        if !hv {
202            pixels[5] = s2u(u2s(pixels[5]) - a);
203            pixels[2] = s2u(u2s(pixels[2]) + a);
204        }
205    }
206}
207
208/// Part of the normal filter described in 15.3 in the specification
209///
210/// Filters on the 8 pixels on the vertical edges between macroblocks\
211/// The point passed in must be the first vertical pixel on the bottom macroblock
212pub(crate) fn macroblock_filter_vertical(
213    hev_threshold: u8,
214    interior_limit: u8,
215    edge_limit: u8,
216    pixels: &mut [u8],
217    point: usize,
218    stride: usize,
219) {
220    if should_filter_vertical(interior_limit, edge_limit, pixels, point, stride) {
221        if !high_edge_variance_vertical(hev_threshold, pixels, point, stride) {
222            // p0-3 are the pixels on the left macroblock from right to left
223            let p2 = u2s(pixels[point - 3 * stride]);
224            let p1 = u2s(pixels[point - 2 * stride]);
225            let p0 = u2s(pixels[point - stride]);
226            // q0-3 are the pixels on the right macroblock from left to right
227            let q0 = u2s(pixels[point]);
228            let q1 = u2s(pixels[point + stride]);
229            let q2 = u2s(pixels[point + 2 * stride]);
230
231            let w = c(c(p1 - q1) + 3 * (q0 - p0));
232
233            let a = c((27 * w + 63) >> 7);
234
235            pixels[point] = s2u(q0 - a);
236            pixels[point - stride] = s2u(p0 + a);
237
238            let a = c((18 * w + 63) >> 7);
239
240            pixels[point + stride] = s2u(q1 - a);
241            pixels[point - 2 * stride] = s2u(p1 + a);
242
243            let a = c((9 * w + 63) >> 7);
244
245            pixels[point + 2 * stride] = s2u(q2 - a);
246            pixels[point - 3 * stride] = s2u(p2 + a);
247        } else {
248            common_adjust_vertical(true, pixels, point, stride);
249        }
250    }
251}
252
253/// Part of the normal filter described in 15.3 in the specification
254///
255/// Filters on the 8 pixels on the horizontal edges between macroblocks\
256/// The pixels passed in must be a slice containing the 4 pixels on each macroblock
257pub(crate) fn macroblock_filter_horizontal(
258    hev_threshold: u8,
259    interior_limit: u8,
260    edge_limit: u8,
261    pixels: &mut [u8],
262) {
263    assert!(pixels.len() >= 8);
264    if should_filter_horizontal(interior_limit, edge_limit, pixels) {
265        if !high_edge_variance_horizontal(hev_threshold, pixels) {
266            // p0-3 are the pixels on the left macroblock from right to left
267            let p2 = u2s(pixels[1]);
268            let p1 = u2s(pixels[2]);
269            let p0 = u2s(pixels[3]);
270            // q0-3 are the pixels on the right macroblock from left to right
271            let q0 = u2s(pixels[4]);
272            let q1 = u2s(pixels[5]);
273            let q2 = u2s(pixels[6]);
274
275            let w = c(c(p1 - q1) + 3 * (q0 - p0));
276
277            let a = c((27 * w + 63) >> 7);
278
279            pixels[4] = s2u(q0 - a);
280            pixels[3] = s2u(p0 + a);
281
282            let a = c((18 * w + 63) >> 7);
283
284            pixels[5] = s2u(q1 - a);
285            pixels[2] = s2u(p1 + a);
286
287            let a = c((9 * w + 63) >> 7);
288
289            pixels[6] = s2u(q2 - a);
290            pixels[1] = s2u(p2 + a);
291        } else {
292            common_adjust_horizontal(true, pixels);
293        }
294    }
295}
296
297#[cfg(all(test, feature = "_benchmarks"))]
298mod benches {
299    use super::*;
300    use test::{black_box, Bencher};
301
302    #[rustfmt::skip]
303    const TEST_DATA: [u8; 8 * 8] = [
304        177, 192, 179, 181, 185, 174, 186, 193,
305        185, 180, 175, 179, 175, 190, 189, 190,
306        185, 181, 177, 190, 190, 174, 176, 188,
307        192, 179, 186, 175, 190, 184, 190, 175,
308        175, 183, 183, 190, 187, 186, 176, 181,
309        183, 177, 182, 185, 183, 179, 178, 181,
310        191, 183, 188, 181, 180, 193, 185, 180,
311        177, 182, 177, 178, 179, 178, 191, 178,
312    ];
313
314    #[bench]
315    fn measure_horizontal_macroblock_filter(b: &mut Bencher) {
316        let hev_threshold = 5;
317        let interior_limit = 15;
318        let edge_limit = 15;
319
320        let mut data = TEST_DATA.clone();
321        let stride = 8;
322
323        b.iter(|| {
324            for y in 0..8 {
325                black_box(macroblock_filter_horizontal(
326                    hev_threshold,
327                    interior_limit,
328                    edge_limit,
329                    &mut data[y * stride..][..8],
330                ));
331            }
332        });
333    }
334
335    #[bench]
336    fn measure_vertical_macroblock_filter(b: &mut Bencher) {
337        let hev_threshold = 5;
338        let interior_limit = 15;
339        let edge_limit = 15;
340
341        let mut data = TEST_DATA.clone();
342        let stride = 8;
343
344        b.iter(|| {
345            for x in 0..8 {
346                black_box(macroblock_filter_vertical(
347                    hev_threshold,
348                    interior_limit,
349                    edge_limit,
350                    &mut data,
351                    4 * stride + x,
352                    stride,
353                ));
354            }
355        });
356    }
357
358    #[bench]
359    fn measure_horizontal_subblock_filter(b: &mut Bencher) {
360        let hev_threshold = 5;
361        let interior_limit = 15;
362        let edge_limit = 15;
363
364        let mut data = TEST_DATA.clone();
365        let stride = 8;
366
367        b.iter(|| {
368            for y in 0usize..8 {
369                black_box(subblock_filter_horizontal(
370                    hev_threshold,
371                    interior_limit,
372                    edge_limit,
373                    &mut data[y * stride..][..8],
374                ))
375            }
376        });
377    }
378
379    #[bench]
380    fn measure_vertical_subblock_filter(b: &mut Bencher) {
381        let hev_threshold = 5;
382        let interior_limit = 15;
383        let edge_limit = 15;
384
385        let mut data = TEST_DATA.clone();
386        let stride = 8;
387
388        b.iter(|| {
389            for x in 0..8 {
390                black_box(subblock_filter_vertical(
391                    hev_threshold,
392                    interior_limit,
393                    edge_limit,
394                    &mut data,
395                    4 * stride + x,
396                    stride,
397                ))
398            }
399        });
400    }
401
402    #[bench]
403    fn measure_simple_segment_horizontal_filter(b: &mut Bencher) {
404        let edge_limit = 15;
405
406        let mut data = TEST_DATA.clone();
407        let stride = 8;
408
409        b.iter(|| {
410            for y in 0usize..8 {
411                black_box(simple_segment_horizontal(
412                    edge_limit,
413                    &mut data[y * stride..][..8],
414                ))
415            }
416        });
417    }
418
419    #[bench]
420    fn measure_simple_segment_vertical_filter(b: &mut Bencher) {
421        let edge_limit = 15;
422
423        let mut data = TEST_DATA.clone();
424        let stride = 8;
425
426        b.iter(|| {
427            for x in 0usize..16 {
428                black_box(simple_segment_vertical(
429                    edge_limit,
430                    &mut data,
431                    4 * stride + x,
432                    stride,
433                ))
434            }
435        });
436    }
437}