mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-26 11:36:58 -06:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54e3e4fb18 | ||
|
|
c7b986f2db | ||
|
|
aaf6c1c38a | ||
|
|
16fe4ebc65 | ||
|
|
430d0a91c0 | ||
|
|
c93e0a0bac | ||
|
|
aa3a1baa01 | ||
|
|
edfee40392 | ||
|
|
580b577cf2 | ||
|
|
230b6485d9 | ||
|
|
3418dec9e4 | ||
|
|
a78d9a9a19 | ||
|
|
560f80abe2 | ||
|
|
2aa0310e27 | ||
|
|
b6fd616977 |
@@ -137,6 +137,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-25 | Windows installer: the Start menu shortcut takes the icon embedded in `deckcraft.exe` instead of an MSI `<Icon>`, so a taskbar pin keeps its icon after an update (#1114); a pin that already lost it has to be unpinned and pinned again once |
|
||||
| 2026-10-24 | AutoFormat as you type: a paragraph that starts with `*`, `-`, `--`, `>`, `<>`, `1.`, `1)`, `I.`, `i.`, `A.`, `a.` or `a)` and a space or tab becomes a bulleted or numbered list in that style; Backspace right after puts the typed text back, and Tools ▸ AutoCorrect and `text.autocorrect {lists}` turn it off |
|
||||
| 2026-10-24 | Tables: while the caret is in a cell, dragging into another cell selects that range of cells (shaded, merged cells taken in whole), and Shift+arrow grows or shrinks it; formatting applies to the selected cells only, and Delete clears their text and keeps the table |
|
||||
| 2026-10-24 | Preferences ▸ Measurement units: inches or centimetres. The Format pane's Size and Position fields, Shape Format ▸ Size on the ribbon, the Slide Size dialog and the ruler show and take the chosen unit; the deck still stores points (#1326) |
|
||||
@@ -293,6 +294,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-25 | trivial | Progress log: Windows shortcut icon survives updates (#1114). Scores and milestone names unchanged |
|
||||
| 2026-10-24 | trivial | Progress log: selecting a range of table cells by dragging. Scores and milestone names unchanged |
|
||||
| 2026-10-24 | trivial | Progress log: measurement units in centimetres (#1326). Scores unchanged |
|
||||
| 2026-10-22 | trivial | Progress log: Animation Pane ▸ Play From. Scores unchanged |
|
||||
|
||||
@@ -430,4 +430,27 @@ mod tests {
|
||||
assert_eq!(mac, win, "macOS and Windows offer different file types");
|
||||
assert_eq!(mac, linux, "macOS and Linux offer different file types");
|
||||
}
|
||||
|
||||
/// #1114: a shortcut that names an MSI `<Icon>` shows a blank icon once an update deletes the
|
||||
/// cached copy of it, and a taskbar pin (a copy of the shortcut) is never repaired. Shortcuts
|
||||
/// take the icon embedded in `deckcraft.exe`; only `ARPPRODUCTICON` uses the MSI `<Icon>`.
|
||||
#[test]
|
||||
fn windows_shortcuts_use_the_icon_embedded_in_the_exe() {
|
||||
let wxs = include_str!("../../../packaging/windows/deckcraft.wxs");
|
||||
let shortcuts: Vec<&str> = wxs.split("<Shortcut").skip(1).filter_map(|s| s.split("/>").next()).collect();
|
||||
assert!(!shortcuts.is_empty(), "deckcraft.wxs has no shortcut");
|
||||
for shortcut in shortcuts {
|
||||
assert!(!shortcut.contains("Icon="), "a shortcut names an MSI <Icon>:{shortcut}");
|
||||
}
|
||||
assert!(wxs.contains("<Property Id=\"ARPPRODUCTICON\""), "Apps and features keeps its icon");
|
||||
}
|
||||
|
||||
/// The shortcuts take the exe's own icon, and `package.ps1 -SkipBuild` packages an existing
|
||||
/// exe without `DECKCRAFT_REQUIRE_WINRES`, so the script has to refuse one with no icon.
|
||||
#[test]
|
||||
fn the_windows_package_script_rejects_an_exe_without_an_icon() {
|
||||
let script = include_str!("../../../packaging/windows/package.ps1");
|
||||
assert!(script.contains("ExtractIconEx"), "package.ps1 does not count the exe's icons");
|
||||
assert!(script.contains("has no embedded icon"), "package.ps1 does not reject an iconless exe");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,3 +65,6 @@ sync_github_name = true
|
||||
|
||||
[people.Katacc]
|
||||
sync_github_name = true
|
||||
|
||||
[people.SofiaAFigueredo]
|
||||
real_name = "Sofia Alves Figueredo"
|
||||
|
||||
+115
-74
@@ -45,7 +45,7 @@ fn cdef_block(fs: &FrameState, out: &mut FrameBuf, r: usize, c: usize, idx: usiz
|
||||
let var_str = if (var >> 6) != 0 { (31 - ((var >> 6) as u32).leading_zeros()).min(12) as i32 } else { 0 };
|
||||
pri = if var != 0 { (pri * (4 + var_str) + 8) >> 4 } else { 0 };
|
||||
let damping = cp.damping as i32 + coeff_shift as i32;
|
||||
filter(fs, out, 0, r, c, pri, sec, damping, dir);
|
||||
filter(fs, out, 0, (r, c), (pri, sec), damping, dir);
|
||||
if fs.num_planes == 1 {
|
||||
return true;
|
||||
}
|
||||
@@ -53,8 +53,8 @@ fn cdef_block(fs: &FrameState, out: &mut FrameBuf, r: usize, c: usize, idx: usiz
|
||||
let sec = (cp.uv_sec[idx] << coeff_shift) as i32;
|
||||
let dir = if pri == 0 { 0 } else { CDEF_UV_DIR[fs.ssx][fs.ssy][y_dir] as usize };
|
||||
let damping = cp.damping as i32 + coeff_shift as i32 - 1;
|
||||
filter(fs, out, 1, r, c, pri, sec, damping, dir);
|
||||
filter(fs, out, 2, r, c, pri, sec, damping, dir);
|
||||
filter(fs, out, 1, (r, c), (pri, sec), damping, dir);
|
||||
filter(fs, out, 2, (r, c), (pri, sec), damping, dir);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -124,33 +124,74 @@ fn constrain(diff: i32, threshold: i32, adj: u32) -> i32 {
|
||||
if diff < 0 { -mag } else { mag }
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn filter(fs: &FrameState, out: &mut FrameBuf, plane: usize, r: usize, c: usize, pri: i32, sec: i32, damping: i32, dir: usize) {
|
||||
if pri == 0 && sec == 0 {
|
||||
// Every tap contributes 0 and the clamp to [min, max] keeps x: CdefFrame = CurrFrame.
|
||||
return;
|
||||
/// Tap offsets, weights and strengths of one block.
|
||||
struct Taps {
|
||||
pri: i32,
|
||||
sec: i32,
|
||||
pri_adj: u32,
|
||||
sec_adj: u32,
|
||||
pri_off: [isize; 2],
|
||||
sec_off: [[isize; 2]; 2],
|
||||
pri_taps: [i32; 2],
|
||||
sec_taps: [i32; 2],
|
||||
}
|
||||
|
||||
impl Taps {
|
||||
fn new(coeff_shift: u32, (pri, sec): (i32, i32), damping: i32, dir: usize) -> Taps {
|
||||
let pri_tap = ((pri >> coeff_shift) & 1) as usize;
|
||||
let off = |d: usize, k: usize| CDEF_DIRECTIONS[d][k][0] as isize * PAD_STRIDE as isize + CDEF_DIRECTIONS[d][k][1] as isize;
|
||||
let pri_off = [off(dir, 0), off(dir, 1)];
|
||||
let d_lo = (dir + 6) & 7;
|
||||
let d_hi = (dir + 2) & 7;
|
||||
let sec_off = [[off(d_lo, 0), off(d_hi, 0)], [off(d_lo, 1), off(d_hi, 1)]];
|
||||
let pri_taps = [CDEF_PRI_TAPS[pri_tap][0] as i32, CDEF_PRI_TAPS[pri_tap][1] as i32];
|
||||
let sec_taps = [CDEF_SEC_TAPS[pri_tap][0] as i32, CDEF_SEC_TAPS[pri_tap][1] as i32];
|
||||
let adj = |t: i32| if t == 0 { 0 } else { (damping - (31 - (t as u32).leading_zeros()) as i32).max(0) as u32 };
|
||||
Taps { pri, sec, pri_adj: adj(pri), sec_adj: adj(sec), pri_off, sec_off, pri_taps, sec_taps }
|
||||
}
|
||||
let coeff_shift = fs.bit_depth - 8;
|
||||
|
||||
/// The 12 (offset, weight, strength, adjustment) taps in filtering order.
|
||||
fn tap_list(&self) -> [(isize, i32, i32, u32); 12] {
|
||||
let mut taps = [(0isize, 0i32, 0i32, 0u32); 12];
|
||||
let mut n = 0;
|
||||
for k in 0..2 {
|
||||
for sign in [-1isize, 1] {
|
||||
taps[n] = (sign * self.pri_off[k], self.pri_taps[k], self.pri, self.pri_adj);
|
||||
taps[n + 1] = (sign * self.sec_off[k][0], self.sec_taps[k], self.sec, self.sec_adj);
|
||||
taps[n + 2] = (sign * self.sec_off[k][1], self.sec_taps[k], self.sec, self.sec_adj);
|
||||
n += 3;
|
||||
}
|
||||
}
|
||||
taps
|
||||
}
|
||||
}
|
||||
|
||||
/// Position and size of a block within its plane.
|
||||
#[derive(Clone, Copy)]
|
||||
struct BlockRect {
|
||||
x0: usize,
|
||||
y0: usize,
|
||||
w: usize,
|
||||
h: usize,
|
||||
}
|
||||
|
||||
/// The block with two samples of padding on every side, plus whether every sample is available
|
||||
/// (CdefAvailable: its 4x4 block is inside the frame).
|
||||
fn padded_block(fs: &FrameState, plane: usize, src: &Plane, rect: BlockRect) -> ([i32; PAD_STRIDE * PAD_STRIDE], bool) {
|
||||
let (sub_x, sub_y) = if plane > 0 { (fs.ssx, fs.ssy) } else { (0, 0) };
|
||||
let x0 = (c * 4) >> sub_x;
|
||||
let y0 = (r * 4) >> sub_y;
|
||||
let w = 8 >> sub_x;
|
||||
let h = 8 >> sub_y;
|
||||
let src = &fs.cur.planes[plane];
|
||||
let (mi_rows, mi_cols) = (fs.fh.mi_rows as isize, fs.fh.mi_cols as isize);
|
||||
// CdefAvailable per sample: its 4x4 block is inside the frame.
|
||||
let mut buf = [CDEF_NA; PAD_STRIDE * PAD_STRIDE];
|
||||
let mut all_avail = true;
|
||||
for i in 0..h + 4 {
|
||||
let y = (y0 + i) as isize - 2;
|
||||
for i in 0..rect.h + 4 {
|
||||
let y = (rect.y0 + i) as isize - 2;
|
||||
let cand_r = (y << sub_y) >> 2;
|
||||
if y < 0 || cand_r >= mi_rows {
|
||||
all_avail = false;
|
||||
continue;
|
||||
}
|
||||
let row = src.row(y as usize);
|
||||
for j in 0..w + 4 {
|
||||
let x = (x0 + j) as isize - 2;
|
||||
for j in 0..rect.w + 4 {
|
||||
let x = (rect.x0 + j) as isize - 2;
|
||||
let cand_c = (x << sub_x) >> 2;
|
||||
if x >= 0 && cand_c < mi_cols {
|
||||
buf[i * PAD_STRIDE + j] = row[x as usize] as i32;
|
||||
@@ -159,70 +200,70 @@ fn filter(fs: &FrameState, out: &mut FrameBuf, plane: usize, r: usize, c: usize,
|
||||
}
|
||||
}
|
||||
}
|
||||
let pri_tap = ((pri >> coeff_shift) & 1) as usize;
|
||||
let off = |d: usize, k: usize| CDEF_DIRECTIONS[d][k][0] as isize * PAD_STRIDE as isize + CDEF_DIRECTIONS[d][k][1] as isize;
|
||||
let pri_off = [off(dir, 0), off(dir, 1)];
|
||||
let d_lo = (dir + 6) & 7;
|
||||
let d_hi = (dir + 2) & 7;
|
||||
let sec_off = [[off(d_lo, 0), off(d_hi, 0)], [off(d_lo, 1), off(d_hi, 1)]];
|
||||
let pri_taps = [CDEF_PRI_TAPS[pri_tap][0] as i32, CDEF_PRI_TAPS[pri_tap][1] as i32];
|
||||
let sec_taps = [CDEF_SEC_TAPS[pri_tap][0] as i32, CDEF_SEC_TAPS[pri_tap][1] as i32];
|
||||
let adj = |t: i32| if t == 0 { 0 } else { (damping - (31 - (t as u32).leading_zeros()) as i32).max(0) as u32 };
|
||||
let (pri_adj, sec_adj) = (adj(pri), adj(sec));
|
||||
(buf, all_avail)
|
||||
}
|
||||
|
||||
/// Filters one sample whose taps may be unavailable (CDEF_NA).
|
||||
fn filter_sample(buf: &[i32; PAD_STRIDE * PAD_STRIDE], taps: &Taps, ci: isize, x: i32) -> u16 {
|
||||
let mut sum = 0i32;
|
||||
let mut max = x;
|
||||
let mut min = x;
|
||||
for k in 0..2 {
|
||||
for sign in [-1isize, 1] {
|
||||
let p = buf[(ci + sign * taps.pri_off[k]) as usize];
|
||||
if p != CDEF_NA {
|
||||
if taps.pri != 0 {
|
||||
sum += taps.pri_taps[k] * constrain(p - x, taps.pri, taps.pri_adj);
|
||||
}
|
||||
max = max.max(p);
|
||||
min = min.min(p);
|
||||
}
|
||||
for s in 0..2 {
|
||||
let q = buf[(ci + sign * taps.sec_off[k][s]) as usize];
|
||||
if q != CDEF_NA {
|
||||
if taps.sec != 0 {
|
||||
sum += taps.sec_taps[k] * constrain(q - x, taps.sec, taps.sec_adj);
|
||||
}
|
||||
max = max.max(q);
|
||||
min = min.min(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(x + ((8 + sum - (sum < 0) as i32) >> 4)).clamp(min, max) as u16
|
||||
}
|
||||
|
||||
fn filter(fs: &FrameState, out: &mut FrameBuf, plane: usize, (r, c): (usize, usize), strengths: (i32, i32), damping: i32, dir: usize) {
|
||||
let (pri, sec) = strengths;
|
||||
if pri == 0 && sec == 0 {
|
||||
// Every tap contributes 0 and the clamp to [min, max] keeps x: CdefFrame = CurrFrame.
|
||||
return;
|
||||
}
|
||||
let coeff_shift = fs.bit_depth - 8;
|
||||
let (sub_x, sub_y) = if plane > 0 { (fs.ssx, fs.ssy) } else { (0, 0) };
|
||||
let rect = BlockRect { x0: (c * 4) >> sub_x, y0: (r * 4) >> sub_y, w: 8 >> sub_x, h: 8 >> sub_y };
|
||||
let src = &fs.cur.planes[plane];
|
||||
let (buf, all_avail) = padded_block(fs, plane, src, rect);
|
||||
let taps = Taps::new(coeff_shift, strengths, damping, dir);
|
||||
let dst = &mut out.planes[plane];
|
||||
if all_avail {
|
||||
// Every tap is available: whole rows at a time (constrain() with threshold 0 is 0).
|
||||
let mut taps = [(0isize, 0i32, 0i32, 0u32); 12];
|
||||
let mut n = 0;
|
||||
for k in 0..2 {
|
||||
for sign in [-1isize, 1] {
|
||||
taps[n] = (sign * pri_off[k], pri_taps[k], pri, pri_adj);
|
||||
taps[n + 1] = (sign * sec_off[k][0], sec_taps[k], sec, sec_adj);
|
||||
taps[n + 2] = (sign * sec_off[k][1], sec_taps[k], sec, sec_adj);
|
||||
n += 3;
|
||||
}
|
||||
}
|
||||
let rows = (y0, x0, h);
|
||||
match w {
|
||||
8 => filter_rows::<8>(&buf, &taps, dst, rows),
|
||||
_ => filter_rows::<4>(&buf, &taps, dst, rows),
|
||||
let list = taps.tap_list();
|
||||
let rows = (rect.y0, rect.x0, rect.h);
|
||||
match rect.w {
|
||||
8 => filter_rows::<8>(&buf, &list, dst, rows),
|
||||
_ => filter_rows::<4>(&buf, &list, dst, rows),
|
||||
}
|
||||
return;
|
||||
}
|
||||
for i in 0..h {
|
||||
for i in 0..rect.h {
|
||||
// The centre sample is read even where CdefAvailable is 0 (8x8 blocks straddling an odd
|
||||
// MiRows / MiCols edge).
|
||||
let srow = &src.row(y0 + i)[x0..x0 + w];
|
||||
let drow = &mut dst.row_mut(y0 + i)[x0..x0 + w];
|
||||
for j in 0..w {
|
||||
let srow = &src.row(rect.y0 + i)[rect.x0..rect.x0 + rect.w];
|
||||
let drow = &mut dst.row_mut(rect.y0 + i)[rect.x0..rect.x0 + rect.w];
|
||||
for j in 0..rect.w {
|
||||
let ci = ((i + 2) * PAD_STRIDE + j + 2) as isize;
|
||||
let x = srow[j] as i32;
|
||||
let mut sum = 0i32;
|
||||
let mut max = x;
|
||||
let mut min = x;
|
||||
for k in 0..2 {
|
||||
for sign in [-1isize, 1] {
|
||||
let p = buf[(ci + sign * pri_off[k]) as usize];
|
||||
if p != CDEF_NA {
|
||||
if pri != 0 {
|
||||
sum += pri_taps[k] * constrain(p - x, pri, pri_adj);
|
||||
}
|
||||
max = max.max(p);
|
||||
min = min.min(p);
|
||||
}
|
||||
for s in 0..2 {
|
||||
let q = buf[(ci + sign * sec_off[k][s]) as usize];
|
||||
if q != CDEF_NA {
|
||||
if sec != 0 {
|
||||
sum += sec_taps[k] * constrain(q - x, sec, sec_adj);
|
||||
}
|
||||
max = max.max(q);
|
||||
min = min.min(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drow[j] = (x + ((8 + sum - (sum < 0) as i32) >> 4)).clamp(min, max) as u16;
|
||||
drow[j] = filter_sample(&buf, &taps, ci, srow[j] as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+313
-167
@@ -55,21 +55,79 @@ impl Rng {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply(pic: &mut Picture, g: &FilmGrainParams, seq: &SequenceHeader) {
|
||||
let _ = seq;
|
||||
let bd = pic.bit_depth as u32;
|
||||
let mono = pic.mono_chrome;
|
||||
let num_planes = if mono { 1 } else { 3 };
|
||||
let (sub_x, sub_y) = (pic.subsampling_x as usize, pic.subsampling_y as usize);
|
||||
let w = pic.width as usize;
|
||||
let h = pic.height as usize;
|
||||
let grain_center = 128i32 << (bd - 8);
|
||||
let grain_min = -grain_center;
|
||||
let grain_max = (256i32 << (bd - 8)) - 1 - grain_center;
|
||||
/// Picture-level parameters shared by the grain synthesis stages.
|
||||
struct GrainCtx<'a> {
|
||||
g: &'a FilmGrainParams,
|
||||
bd: u32,
|
||||
mono: bool,
|
||||
sub_x: usize,
|
||||
sub_y: usize,
|
||||
w: usize,
|
||||
h: usize,
|
||||
grain_min: i32,
|
||||
grain_max: i32,
|
||||
}
|
||||
|
||||
// Generate grain (7.18.3.3).
|
||||
let mut rng = Rng(g.grain_seed);
|
||||
let shift = 12 - bd + g.grain_scale_shift;
|
||||
impl GrainCtx<'_> {
|
||||
fn num_planes(&self) -> usize {
|
||||
if self.mono { 1 } else { 3 }
|
||||
}
|
||||
|
||||
fn grain_shift(&self) -> u32 {
|
||||
12 - self.bd + self.g.grain_scale_shift
|
||||
}
|
||||
|
||||
fn ar_shift(&self) -> u32 {
|
||||
self.g.ar_coeff_shift_minus_6 + 6
|
||||
}
|
||||
|
||||
fn lag(&self) -> i32 {
|
||||
self.g.ar_coeff_lag as i32
|
||||
}
|
||||
|
||||
fn stripe_w(&self) -> usize {
|
||||
self.w + 64
|
||||
}
|
||||
}
|
||||
|
||||
type GrainTemplate = Vec<[i32; 82]>;
|
||||
|
||||
/// The luma, Cb and Cr grain templates (7.18.3.3).
|
||||
struct Templates {
|
||||
luma: GrainTemplate,
|
||||
cb: GrainTemplate,
|
||||
cr: GrainTemplate,
|
||||
}
|
||||
|
||||
impl Templates {
|
||||
fn plane(&self, plane: usize) -> &GrainTemplate {
|
||||
match plane {
|
||||
0 => &self.luma,
|
||||
1 => &self.cb,
|
||||
_ => &self.cr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn luma_ar_sum(g: &FilmGrainParams, luma: &[[i32; 82]], lag: i32, y: usize, x: usize) -> i32 {
|
||||
let mut s = 0;
|
||||
let mut pos = 0;
|
||||
'outer: for dr in -lag..=0 {
|
||||
for dc in -lag..=lag {
|
||||
if dr == 0 && dc == 0 {
|
||||
break 'outer;
|
||||
}
|
||||
let c = g.ar_coeffs_y_plus_128[pos] as i32 - 128;
|
||||
s += luma[(y as i32 + dr) as usize][(x as i32 + dc) as usize] * c;
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn luma_grain(c: &GrainCtx, rng: &mut Rng) -> GrainTemplate {
|
||||
let g = c.g;
|
||||
let shift = c.grain_shift();
|
||||
let mut luma = vec![[0i32; 82]; 73];
|
||||
for row in luma.iter_mut() {
|
||||
for v in row.iter_mut() {
|
||||
@@ -77,77 +135,89 @@ pub(crate) fn apply(pic: &mut Picture, g: &FilmGrainParams, seq: &SequenceHeader
|
||||
*v = round2(gv, shift);
|
||||
}
|
||||
}
|
||||
let ar_shift = g.ar_coeff_shift_minus_6 + 6;
|
||||
let lag = g.ar_coeff_lag as i32;
|
||||
let ar_shift = c.ar_shift();
|
||||
let lag = c.lag();
|
||||
for y in 3..73 {
|
||||
for x in 3..82 - 3 {
|
||||
let mut s = 0;
|
||||
let mut pos = 0;
|
||||
'outer: for dr in -lag..=0 {
|
||||
for dc in -lag..=lag {
|
||||
if dr == 0 && dc == 0 {
|
||||
break 'outer;
|
||||
}
|
||||
let c = g.ar_coeffs_y_plus_128[pos] as i32 - 128;
|
||||
s += luma[(y as i32 + dr) as usize][(x as i32 + dc) as usize] * c;
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
luma[y][x] = (luma[y][x] + round2(s, ar_shift)).clamp(grain_min, grain_max);
|
||||
}
|
||||
}
|
||||
let chroma_w = if sub_x != 0 { 44 } else { 82 };
|
||||
let chroma_h = if sub_y != 0 { 38 } else { 73 };
|
||||
let mut cb = vec![[0i32; 82]; 73];
|
||||
let mut cr = vec![[0i32; 82]; 73];
|
||||
if !mono {
|
||||
for (arr, seed, on) in [(&mut cb, 0xb524u32, g.num_cb_points > 0), (&mut cr, 0x49d8, g.num_cr_points > 0)] {
|
||||
rng.0 = g.grain_seed ^ seed;
|
||||
for row in arr.iter_mut().take(chroma_h) {
|
||||
for v in row.iter_mut().take(chroma_w) {
|
||||
let gv = if on || g.chroma_scaling_from_luma { GAUSSIAN_SEQUENCE[rng.next(11) as usize] as i32 } else { 0 };
|
||||
*v = round2(gv, shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
for y in 3..chroma_h {
|
||||
for x in 3..chroma_w - 3 {
|
||||
let mut s0 = 0;
|
||||
let mut s1 = 0;
|
||||
let mut pos = 0;
|
||||
'outer: for dr in -lag..=0 {
|
||||
for dc in -lag..=lag {
|
||||
let c0 = g.ar_coeffs_cb_plus_128[pos] as i32 - 128;
|
||||
let c1 = g.ar_coeffs_cr_plus_128[pos] as i32 - 128;
|
||||
if dr == 0 && dc == 0 {
|
||||
if g.num_y_points > 0 {
|
||||
let mut l = 0;
|
||||
let lx = ((x - 3) << sub_x) + 3;
|
||||
let ly = ((y - 3) << sub_y) + 3;
|
||||
for i in 0..=sub_y {
|
||||
for j in 0..=sub_x {
|
||||
l += luma[ly + i][lx + j];
|
||||
}
|
||||
}
|
||||
let l = round2(l, (sub_x + sub_y) as u32);
|
||||
s0 += l * c0;
|
||||
s1 += l * c1;
|
||||
}
|
||||
break 'outer;
|
||||
}
|
||||
let (yy, xx) = ((y as i32 + dr) as usize, (x as i32 + dc) as usize);
|
||||
s0 += cb[yy][xx] * c0;
|
||||
s1 += cr[yy][xx] * c1;
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
cb[y][x] = (cb[y][x] + round2(s0, ar_shift)).clamp(grain_min, grain_max);
|
||||
cr[y][x] = (cr[y][x] + round2(s1, ar_shift)).clamp(grain_min, grain_max);
|
||||
}
|
||||
let s = luma_ar_sum(g, &luma, lag, y, x);
|
||||
luma[y][x] = (luma[y][x] + round2(s, ar_shift)).clamp(c.grain_min, c.grain_max);
|
||||
}
|
||||
}
|
||||
luma
|
||||
}
|
||||
|
||||
// Scaling lookup (7.18.3.4).
|
||||
/// The luma contribution to the chroma autoregressive filter at chroma position (y, x).
|
||||
fn chroma_luma_term(c: &GrainCtx, luma: &[[i32; 82]], y: usize, x: usize) -> i32 {
|
||||
let mut l = 0;
|
||||
let lx = ((x - 3) << c.sub_x) + 3;
|
||||
let ly = ((y - 3) << c.sub_y) + 3;
|
||||
for i in 0..=c.sub_y {
|
||||
for j in 0..=c.sub_x {
|
||||
l += luma[ly + i][lx + j];
|
||||
}
|
||||
}
|
||||
round2(l, (c.sub_x + c.sub_y) as u32)
|
||||
}
|
||||
|
||||
fn chroma_ar_sums(c: &GrainCtx, t: &Templates, y: usize, x: usize) -> (i32, i32) {
|
||||
let g = c.g;
|
||||
let lag = c.lag();
|
||||
let mut s0 = 0;
|
||||
let mut s1 = 0;
|
||||
let mut pos = 0;
|
||||
'outer: for dr in -lag..=0 {
|
||||
for dc in -lag..=lag {
|
||||
let c0 = g.ar_coeffs_cb_plus_128[pos] as i32 - 128;
|
||||
let c1 = g.ar_coeffs_cr_plus_128[pos] as i32 - 128;
|
||||
if dr == 0 && dc == 0 {
|
||||
if g.num_y_points > 0 {
|
||||
let l = chroma_luma_term(c, &t.luma, y, x);
|
||||
s0 += l * c0;
|
||||
s1 += l * c1;
|
||||
}
|
||||
break 'outer;
|
||||
}
|
||||
let (yy, xx) = ((y as i32 + dr) as usize, (x as i32 + dc) as usize);
|
||||
s0 += t.cb[yy][xx] * c0;
|
||||
s1 += t.cr[yy][xx] * c1;
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
(s0, s1)
|
||||
}
|
||||
|
||||
/// Chroma grain templates; zero for monochrome pictures.
|
||||
fn chroma_grain(c: &GrainCtx, rng: &mut Rng, luma: GrainTemplate) -> Templates {
|
||||
let g = c.g;
|
||||
let mut t = Templates { luma, cb: vec![[0i32; 82]; 73], cr: vec![[0i32; 82]; 73] };
|
||||
if c.mono {
|
||||
return t;
|
||||
}
|
||||
let shift = c.grain_shift();
|
||||
let ar_shift = c.ar_shift();
|
||||
let chroma_w = if c.sub_x != 0 { 44 } else { 82 };
|
||||
let chroma_h = if c.sub_y != 0 { 38 } else { 73 };
|
||||
for (arr, seed, on) in [(&mut t.cb, 0xb524u32, g.num_cb_points > 0), (&mut t.cr, 0x49d8, g.num_cr_points > 0)] {
|
||||
rng.0 = g.grain_seed ^ seed;
|
||||
for row in arr.iter_mut().take(chroma_h) {
|
||||
for v in row.iter_mut().take(chroma_w) {
|
||||
let gv = if on || g.chroma_scaling_from_luma { GAUSSIAN_SEQUENCE[rng.next(11) as usize] as i32 } else { 0 };
|
||||
*v = round2(gv, shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
for y in 3..chroma_h {
|
||||
for x in 3..chroma_w - 3 {
|
||||
let (s0, s1) = chroma_ar_sums(c, &t, y, x);
|
||||
t.cb[y][x] = (t.cb[y][x] + round2(s0, ar_shift)).clamp(c.grain_min, c.grain_max);
|
||||
t.cr[y][x] = (t.cr[y][x] + round2(s1, ar_shift)).clamp(c.grain_min, c.grain_max);
|
||||
}
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
/// Scaling lookup tables (7.18.3.4), one per plane.
|
||||
fn scaling_luts(g: &FilmGrainParams, num_planes: usize) -> [[i32; 256]; 3] {
|
||||
let mut lut = [[0i32; 256]; 3];
|
||||
for (plane, l) in lut.iter_mut().enumerate().take(num_planes) {
|
||||
let (n, xs, ys) = if plane == 0 || g.chroma_scaling_from_luma {
|
||||
@@ -159,101 +229,141 @@ pub(crate) fn apply(pic: &mut Picture, g: &FilmGrainParams, seq: &SequenceHeader
|
||||
};
|
||||
*l = scaling_lut(n, xs, ys);
|
||||
}
|
||||
let scale_lut = |plane: usize, index: i32| -> i32 {
|
||||
let sh = bd - 8;
|
||||
let x = index >> sh;
|
||||
let rem = index - (x << sh);
|
||||
if bd == 8 || x == 255 {
|
||||
lut[plane][x as usize]
|
||||
} else {
|
||||
let start = lut[plane][x as usize];
|
||||
let end = lut[plane][x as usize + 1];
|
||||
start + round2((end - start) * rem, sh)
|
||||
}
|
||||
};
|
||||
lut
|
||||
}
|
||||
|
||||
// Noise stripes (7.18.3.5).
|
||||
let stripe_w = w + 64;
|
||||
let n_stripes = (h.div_ceil(2)).div_ceil(16).max(1);
|
||||
let mut stripes: Vec<[Vec<i32>; 3]> = (0..n_stripes).map(|_| [vec![0; 34 * stripe_w], vec![0; 34 * stripe_w], vec![0; 34 * stripe_w]]).collect();
|
||||
let overlap = g.overlap_flag;
|
||||
for (luma_num, stripe) in stripes.iter_mut().enumerate() {
|
||||
let mut r = g.grain_seed;
|
||||
r ^= (((luma_num * 37 + 178) & 255) << 8) as u32;
|
||||
r ^= ((luma_num * 173 + 105) & 255) as u32;
|
||||
rng.0 = r;
|
||||
let mut x = 0;
|
||||
while x < w.div_ceil(2) {
|
||||
let rand = rng.next(8) as usize;
|
||||
let off_x = rand >> 4;
|
||||
let off_y = rand & 15;
|
||||
for (plane, s) in stripe.iter_mut().enumerate().take(num_planes) {
|
||||
let psx = if plane > 0 { sub_x } else { 0 };
|
||||
let psy = if plane > 0 { sub_y } else { 0 };
|
||||
let pox = if psx != 0 { 6 + off_x } else { 9 + off_x * 2 };
|
||||
let poy = if psy != 0 { 6 + off_y } else { 9 + off_y * 2 };
|
||||
let src = match plane {
|
||||
0 => &luma,
|
||||
1 => &cb,
|
||||
_ => &cr,
|
||||
fn scale_lookup(lut: &[[i32; 256]; 3], bd: u32, plane: usize, index: i32) -> i32 {
|
||||
let sh = bd - 8;
|
||||
let x = index >> sh;
|
||||
let rem = index - (x << sh);
|
||||
if bd == 8 || x == 255 {
|
||||
lut[plane][x as usize]
|
||||
} else {
|
||||
let start = lut[plane][x as usize];
|
||||
let end = lut[plane][x as usize + 1];
|
||||
start + round2((end - start) * rem, sh)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills one plane of a noise stripe from the grain template at stripe column `x`.
|
||||
fn fill_stripe_plane(c: &GrainCtx, src: &GrainTemplate, s: &mut [i32], plane: usize, offsets: (usize, usize), x: usize) {
|
||||
let (off_x, off_y) = offsets;
|
||||
let stripe_w = c.stripe_w();
|
||||
let overlap = c.g.overlap_flag;
|
||||
let psx = if plane > 0 { c.sub_x } else { 0 };
|
||||
let psy = if plane > 0 { c.sub_y } else { 0 };
|
||||
let pox = if psx != 0 { 6 + off_x } else { 9 + off_x * 2 };
|
||||
let poy = if psy != 0 { 6 + off_y } else { 9 + off_y * 2 };
|
||||
for i in 0..(34 >> psy) {
|
||||
for j in 0..(34 >> psx) {
|
||||
let mut gv = src[poy + i][pox + j];
|
||||
let (idx, blend) = if psx == 0 { (i * stripe_w + x * 2 + j, j < 2) } else { (i * stripe_w + x + j, j == 0) };
|
||||
if blend && overlap && x > 0 {
|
||||
let old = s[idx];
|
||||
gv = if psx != 0 {
|
||||
old * 23 + gv * 22
|
||||
} else if j == 0 {
|
||||
old * 27 + gv * 17
|
||||
} else {
|
||||
old * 17 + gv * 27
|
||||
};
|
||||
for i in 0..(34 >> psy) {
|
||||
for j in 0..(34 >> psx) {
|
||||
let mut gv = src[poy + i][pox + j];
|
||||
if psx == 0 {
|
||||
let idx = i * stripe_w + x * 2 + j;
|
||||
if j < 2 && overlap && x > 0 {
|
||||
let old = s[idx];
|
||||
gv = if j == 0 { old * 27 + gv * 17 } else { old * 17 + gv * 27 };
|
||||
gv = round2(gv, 5).clamp(grain_min, grain_max);
|
||||
}
|
||||
s[idx] = gv;
|
||||
} else {
|
||||
let idx = i * stripe_w + x + j;
|
||||
if j == 0 && overlap && x > 0 {
|
||||
let old = s[idx];
|
||||
gv = round2(old * 23 + gv * 22, 5).clamp(grain_min, grain_max);
|
||||
}
|
||||
s[idx] = gv;
|
||||
}
|
||||
}
|
||||
}
|
||||
gv = round2(gv, 5).clamp(c.grain_min, c.grain_max);
|
||||
}
|
||||
x += 16;
|
||||
s[idx] = gv;
|
||||
}
|
||||
}
|
||||
let noise_at = |plane: usize, y: usize, x: usize| -> i32 {
|
||||
let psy = if plane > 0 { sub_y } else { 0 };
|
||||
}
|
||||
|
||||
/// Noise stripes (7.18.3.5).
|
||||
struct Stripes {
|
||||
data: Vec<[Vec<i32>; 3]>,
|
||||
stripe_w: usize,
|
||||
overlap: bool,
|
||||
sub_y: usize,
|
||||
grain_min: i32,
|
||||
grain_max: i32,
|
||||
}
|
||||
|
||||
impl Stripes {
|
||||
fn build(c: &GrainCtx, t: &Templates, rng: &mut Rng) -> Stripes {
|
||||
let g = c.g;
|
||||
let stripe_w = c.stripe_w();
|
||||
let n_stripes = (c.h.div_ceil(2)).div_ceil(16).max(1);
|
||||
let mut data: Vec<[Vec<i32>; 3]> = (0..n_stripes).map(|_| [vec![0; 34 * stripe_w], vec![0; 34 * stripe_w], vec![0; 34 * stripe_w]]).collect();
|
||||
for (luma_num, stripe) in data.iter_mut().enumerate() {
|
||||
let mut r = g.grain_seed;
|
||||
r ^= (((luma_num * 37 + 178) & 255) << 8) as u32;
|
||||
r ^= ((luma_num * 173 + 105) & 255) as u32;
|
||||
rng.0 = r;
|
||||
let mut x = 0;
|
||||
while x < c.w.div_ceil(2) {
|
||||
let rand = rng.next(8) as usize;
|
||||
let offsets = (rand >> 4, rand & 15);
|
||||
for (plane, s) in stripe.iter_mut().enumerate().take(c.num_planes()) {
|
||||
fill_stripe_plane(c, t.plane(plane), s, plane, offsets, x);
|
||||
}
|
||||
x += 16;
|
||||
}
|
||||
}
|
||||
Stripes { data, stripe_w, overlap: g.overlap_flag, sub_y: c.sub_y, grain_min: c.grain_min, grain_max: c.grain_max }
|
||||
}
|
||||
|
||||
fn noise_at(&self, plane: usize, y: usize, x: usize) -> i32 {
|
||||
let psy = if plane > 0 { self.sub_y } else { 0 };
|
||||
let luma_num = y >> (5 - psy);
|
||||
let i = y - (luma_num << (5 - psy));
|
||||
let mut gv = stripes[luma_num][plane][i * stripe_w + x];
|
||||
let mut gv = self.data[luma_num][plane][i * self.stripe_w + x];
|
||||
if psy == 0 {
|
||||
if i < 2 && luma_num > 0 && overlap {
|
||||
let old = stripes[luma_num - 1][plane][(i + 32) * stripe_w + x];
|
||||
if i < 2 && luma_num > 0 && self.overlap {
|
||||
let old = self.data[luma_num - 1][plane][(i + 32) * self.stripe_w + x];
|
||||
gv = if i == 0 { old * 27 + gv * 17 } else { old * 17 + gv * 27 };
|
||||
gv = round2(gv, 5).clamp(grain_min, grain_max);
|
||||
gv = round2(gv, 5).clamp(self.grain_min, self.grain_max);
|
||||
}
|
||||
} else if i < 1 && luma_num > 0 && overlap {
|
||||
let old = stripes[luma_num - 1][plane][(i + 16) * stripe_w + x];
|
||||
gv = round2(old * 23 + gv * 22, 5).clamp(grain_min, grain_max);
|
||||
} else if i < 1 && luma_num > 0 && self.overlap {
|
||||
let old = self.data[luma_num - 1][plane][(i + 16) * self.stripe_w + x];
|
||||
gv = round2(old * 23 + gv * 22, 5).clamp(self.grain_min, self.grain_max);
|
||||
}
|
||||
gv
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Blend (7.18.3.5).
|
||||
let (min_value, max_luma, max_chroma) = if g.clip_to_restricted_range {
|
||||
let ml = 235 << (bd - 8);
|
||||
(16 << (bd - 8), ml, if pic.matrix_coefficients as usize == MC_IDENTITY { ml } else { 240 << (bd - 8) })
|
||||
} else {
|
||||
let m = (256 << (bd - 8)) - 1;
|
||||
(0, m, m)
|
||||
};
|
||||
let pix_max = (1i32 << bd) - 1;
|
||||
let scaling_shift = g.grain_scaling_minus_8 + 8;
|
||||
if !mono {
|
||||
/// Output clipping range and scaling shift for the blend step (7.18.3.5).
|
||||
struct BlendRange {
|
||||
min_value: i32,
|
||||
max_luma: i32,
|
||||
max_chroma: i32,
|
||||
pix_max: i32,
|
||||
scaling_shift: u32,
|
||||
}
|
||||
|
||||
impl BlendRange {
|
||||
fn new(c: &GrainCtx, matrix_coefficients: usize) -> BlendRange {
|
||||
let bd = c.bd;
|
||||
let (min_value, max_luma, max_chroma) = if c.g.clip_to_restricted_range {
|
||||
let ml = 235 << (bd - 8);
|
||||
(16 << (bd - 8), ml, if matrix_coefficients == MC_IDENTITY { ml } else { 240 << (bd - 8) })
|
||||
} else {
|
||||
let m = (256 << (bd - 8)) - 1;
|
||||
(0, m, m)
|
||||
};
|
||||
BlendRange { min_value, max_luma, max_chroma, pix_max: (1i32 << bd) - 1, scaling_shift: c.g.grain_scaling_minus_8 + 8 }
|
||||
}
|
||||
}
|
||||
|
||||
struct BlendEnv<'a> {
|
||||
c: &'a GrainCtx<'a>,
|
||||
lut: &'a [[i32; 256]; 3],
|
||||
stripes: &'a Stripes,
|
||||
range: &'a BlendRange,
|
||||
}
|
||||
|
||||
impl BlendEnv<'_> {
|
||||
fn blend_chroma(&self, py: &[u16], pu: &mut [u16], pv: &mut [u16]) {
|
||||
let c = self.c;
|
||||
let g = c.g;
|
||||
let (w, h, sub_x, sub_y, bd) = (c.w, c.h, c.sub_x, c.sub_y, c.bd);
|
||||
let cw = (w + sub_x) >> sub_x;
|
||||
let ch = (h + sub_y) >> sub_y;
|
||||
let [py, pu, pv] = &mut pic.planes;
|
||||
for y in 0..ch {
|
||||
for x in 0..cw {
|
||||
let lx = x << sub_x;
|
||||
@@ -272,26 +382,62 @@ pub(crate) fn apply(pic: &mut Picture, g: &FilmGrainParams, seq: &SequenceHeader
|
||||
avg
|
||||
} else {
|
||||
let combined = avg * (luma_mult as i32 - 128) + orig * (mult as i32 - 128);
|
||||
((combined >> 6) + ((offset as i32 - 256) << (bd - 8))).clamp(0, pix_max)
|
||||
((combined >> 6) + ((offset as i32 - 256) << (bd - 8))).clamp(0, self.range.pix_max)
|
||||
};
|
||||
let noise = round2(scale_lut(plane, merged) * noise_at(plane, y, x), scaling_shift);
|
||||
out[y * cw + x] = (orig + noise).clamp(min_value, max_chroma) as u16;
|
||||
let noise = round2(scale_lookup(self.lut, bd, plane, merged) * self.stripes.noise_at(plane, y, x), self.range.scaling_shift);
|
||||
out[y * cw + x] = (orig + noise).clamp(self.range.min_value, self.range.max_chroma) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if g.num_y_points > 0 {
|
||||
let py = &mut pic.planes[0];
|
||||
|
||||
fn blend_luma(&self, py: &mut [u16]) {
|
||||
let (w, h) = (self.c.w, self.c.h);
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let orig = py[y * w + x] as i32;
|
||||
let noise = round2(scale_lut(0, orig) * noise_at(0, y, x), scaling_shift);
|
||||
py[y * w + x] = (orig + noise).clamp(min_value, max_luma) as u16;
|
||||
let noise = round2(scale_lookup(self.lut, self.c.bd, 0, orig) * self.stripes.noise_at(0, y, x), self.range.scaling_shift);
|
||||
py[y * w + x] = (orig + noise).clamp(self.range.min_value, self.range.max_luma) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply(pic: &mut Picture, g: &FilmGrainParams, seq: &SequenceHeader) {
|
||||
let _ = seq;
|
||||
let bd = pic.bit_depth as u32;
|
||||
let grain_center = 128i32 << (bd - 8);
|
||||
let c = GrainCtx {
|
||||
g,
|
||||
bd,
|
||||
mono: pic.mono_chrome,
|
||||
sub_x: pic.subsampling_x as usize,
|
||||
sub_y: pic.subsampling_y as usize,
|
||||
w: pic.width as usize,
|
||||
h: pic.height as usize,
|
||||
grain_min: -grain_center,
|
||||
grain_max: (256i32 << (bd - 8)) - 1 - grain_center,
|
||||
};
|
||||
|
||||
// Generate grain (7.18.3.3).
|
||||
let mut rng = Rng(g.grain_seed);
|
||||
let luma = luma_grain(&c, &mut rng);
|
||||
let templates = chroma_grain(&c, &mut rng, luma);
|
||||
let lut = scaling_luts(g, c.num_planes());
|
||||
let stripes = Stripes::build(&c, &templates, &mut rng);
|
||||
|
||||
// Blend (7.18.3.5).
|
||||
let range = BlendRange::new(&c, pic.matrix_coefficients as usize);
|
||||
let env = BlendEnv { c: &c, lut: &lut, stripes: &stripes, range: &range };
|
||||
if !c.mono {
|
||||
let [py, pu, pv] = &mut pic.planes;
|
||||
env.blend_chroma(py, pu, pv);
|
||||
}
|
||||
if g.num_y_points > 0 {
|
||||
env.blend_luma(&mut pic.planes[0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
+113
-78
@@ -73,57 +73,70 @@ impl LrCtx<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Position and size of a (at most 4x4) restoration block within its plane.
|
||||
#[derive(Clone, Copy)]
|
||||
struct LrBlock {
|
||||
x: i32,
|
||||
y: i32,
|
||||
w: i32,
|
||||
h: i32,
|
||||
}
|
||||
|
||||
/// Loop restoration (7.17): returns LrFrame given UpscaledCurrFrame and UpscaledCdefFrame.
|
||||
pub(crate) fn loop_restoration(fs: &FrameState, cur: &FrameBuf, cdef: &FrameBuf) -> FrameBuf {
|
||||
let fh = &fs.fh;
|
||||
let mut lr = cdef.clone();
|
||||
let up_w = fh.upscaled_width as i32;
|
||||
let fr_h = fh.frame_height as i32;
|
||||
let bd = fs.bit_depth;
|
||||
for plane in 0..fs.num_planes {
|
||||
let rtype_frame = fh.lr.frame_restoration_type[plane];
|
||||
if rtype_frame == RESTORE_NONE as u8 {
|
||||
if fs.fh.lr.frame_restoration_type[plane] == RESTORE_NONE as u8 {
|
||||
continue;
|
||||
}
|
||||
let (sub_x, sub_y) = if plane > 0 { (fs.ssx as i32, fs.ssy as i32) } else { (0, 0) };
|
||||
let unit_size = fh.lr.loop_restoration_size[plane] as i32;
|
||||
let unit_rows = fs.lr_unit_rows[plane] as i32;
|
||||
let unit_cols = fs.lr_unit_cols[plane] as i32;
|
||||
let plane_end_x = ((up_w + sub_x) >> sub_x) - 1;
|
||||
let plane_end_y = ((fr_h + sub_y) >> sub_y) - 1;
|
||||
let mut row = 0;
|
||||
while row * 4 < fr_h {
|
||||
let luma_y = row * 4;
|
||||
let stripe_num = (luma_y + 8) / 64;
|
||||
let stripe_start_y = (-8 + stripe_num * 64) >> sub_y;
|
||||
let stripe_end_y = stripe_start_y + (64 >> sub_y) - 1;
|
||||
let ctx = LrCtx { cur: &cur.planes[plane], cdef: &cdef.planes[plane], plane_end_x, plane_end_y, stripe_start_y, stripe_end_y };
|
||||
let unit_row = (unit_rows - 1).min(((row * 4 + 8) >> sub_y) / unit_size);
|
||||
let mut col = 0;
|
||||
while col * 4 < up_w {
|
||||
let unit_col = (unit_cols - 1).min(((col * 4) >> sub_x) / unit_size);
|
||||
let x = (col * 4) >> sub_x;
|
||||
let y = (row * 4) >> sub_y;
|
||||
let w = (4 >> sub_x).min(plane_end_x - x + 1);
|
||||
let h = (4 >> sub_y).min(plane_end_y - y + 1);
|
||||
if w > 0 && h > 0 {
|
||||
let ui = (unit_row * unit_cols + unit_col) as usize;
|
||||
match fs.lr.lr_type[plane][ui] {
|
||||
t if t == RESTORE_WIENER as u8 => wiener(fs, &ctx, &mut lr.planes[plane], plane, ui, x, y, w, h, bd),
|
||||
t if t == RESTORE_SGRPROJ as u8 => self_guided(fs, &ctx, &mut lr.planes[plane], plane, ui, x, y, w, h, bd),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
col += 1;
|
||||
}
|
||||
row += 1;
|
||||
}
|
||||
restore_plane(fs, cur, cdef, &mut lr.planes[plane], plane);
|
||||
}
|
||||
lr
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn wiener(fs: &FrameState, ctx: &LrCtx, out: &mut Plane, plane: usize, ui: usize, x: i32, y: i32, w: i32, h: i32, bd: u32) {
|
||||
fn restore_plane(fs: &FrameState, cur: &FrameBuf, cdef: &FrameBuf, out: &mut Plane, plane: usize) {
|
||||
let fh = &fs.fh;
|
||||
let up_w = fh.upscaled_width as i32;
|
||||
let fr_h = fh.frame_height as i32;
|
||||
let bd = fs.bit_depth;
|
||||
let (sub_x, sub_y) = if plane > 0 { (fs.ssx as i32, fs.ssy as i32) } else { (0, 0) };
|
||||
let unit_size = fh.lr.loop_restoration_size[plane] as i32;
|
||||
let unit_rows = fs.lr_unit_rows[plane] as i32;
|
||||
let unit_cols = fs.lr_unit_cols[plane] as i32;
|
||||
let plane_end_x = ((up_w + sub_x) >> sub_x) - 1;
|
||||
let plane_end_y = ((fr_h + sub_y) >> sub_y) - 1;
|
||||
let mut row = 0;
|
||||
while row * 4 < fr_h {
|
||||
let luma_y = row * 4;
|
||||
let stripe_num = (luma_y + 8) / 64;
|
||||
let stripe_start_y = (-8 + stripe_num * 64) >> sub_y;
|
||||
let stripe_end_y = stripe_start_y + (64 >> sub_y) - 1;
|
||||
let ctx = LrCtx { cur: &cur.planes[plane], cdef: &cdef.planes[plane], plane_end_x, plane_end_y, stripe_start_y, stripe_end_y };
|
||||
let unit_row = (unit_rows - 1).min(((row * 4 + 8) >> sub_y) / unit_size);
|
||||
let mut col = 0;
|
||||
while col * 4 < up_w {
|
||||
let unit_col = (unit_cols - 1).min(((col * 4) >> sub_x) / unit_size);
|
||||
let x = (col * 4) >> sub_x;
|
||||
let y = (row * 4) >> sub_y;
|
||||
let w = (4 >> sub_x).min(plane_end_x - x + 1);
|
||||
let h = (4 >> sub_y).min(plane_end_y - y + 1);
|
||||
if w > 0 && h > 0 {
|
||||
let ui = (unit_row * unit_cols + unit_col) as usize;
|
||||
let blk = LrBlock { x, y, w, h };
|
||||
match fs.lr.lr_type[plane][ui] {
|
||||
t if t == RESTORE_WIENER as u8 => wiener(fs, &ctx, out, (plane, ui), blk, bd),
|
||||
t if t == RESTORE_SGRPROJ as u8 => self_guided(fs, &ctx, out, (plane, ui), blk, bd),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
col += 1;
|
||||
}
|
||||
row += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn wiener(fs: &FrameState, ctx: &LrCtx, out: &mut Plane, (plane, ui): (usize, usize), blk: LrBlock, bd: u32) {
|
||||
let LrBlock { x, y, w, h } = blk;
|
||||
let round0: u32 = if bd == 12 { 5 } else { 3 };
|
||||
let round1: u32 = if bd == 12 { 9 } else { 11 };
|
||||
let coef = &fs.lr.lr_wiener[plane][ui];
|
||||
@@ -166,13 +179,13 @@ fn wiener(fs: &FrameState, ctx: &LrCtx, out: &mut Plane, plane: usize, ui: usize
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn self_guided(fs: &FrameState, ctx: &LrCtx, out: &mut Plane, plane: usize, ui: usize, x: i32, y: i32, w: i32, h: i32, bd: u32) {
|
||||
fn self_guided(fs: &FrameState, ctx: &LrCtx, out: &mut Plane, (plane, ui): (usize, usize), blk: LrBlock, bd: u32) {
|
||||
let LrBlock { x, y, w, h } = blk;
|
||||
let set = fs.lr.lr_sgr_set[plane][ui] as usize;
|
||||
let mut flt0 = [[0i32; 4]; 4];
|
||||
let mut flt1 = [[0i32; 4]; 4];
|
||||
box_filter(ctx, x, y, w, h, set, 0, bd, &mut flt0);
|
||||
box_filter(ctx, x, y, w, h, set, 1, bd, &mut flt1);
|
||||
box_filter(ctx, blk, (set, 0), bd, &mut flt0);
|
||||
box_filter(ctx, blk, (set, 1), bd, &mut flt1);
|
||||
let xqd = fs.lr.lr_sgr_xqd[plane][ui];
|
||||
let w0 = xqd[0] as i32;
|
||||
let w1 = xqd[1] as i32;
|
||||
@@ -198,8 +211,56 @@ fn round2_64(x: i64, n: u32) -> i64 {
|
||||
if n == 0 { x } else { (x + (1i64 << (n - 1))) >> n }
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn box_filter(ctx: &LrCtx, x: i32, y: i32, w: i32, h: i32, set: usize, pass: usize, bd: u32, f: &mut [[i32; 4]; 4]) {
|
||||
/// Box sums of the samples within `r` of (x, y): (sum of squares, sum).
|
||||
fn box_sums(ctx: &LrCtx, x: i32, y: i32, r: i32) -> (i64, i64) {
|
||||
let mut a = 0i64;
|
||||
let mut b = 0i64;
|
||||
for dy in -r..=r {
|
||||
for dx in -r..=r {
|
||||
let c = ctx.sample(x + dx, y + dy) as i64;
|
||||
a += c * c;
|
||||
b += c;
|
||||
}
|
||||
}
|
||||
(a, b)
|
||||
}
|
||||
|
||||
/// Self-guided filter constants of one pass.
|
||||
struct SgrConsts {
|
||||
n: i64,
|
||||
s: i64,
|
||||
one_over_n: i64,
|
||||
}
|
||||
|
||||
/// (A, B) coefficients of one sample from its box sums.
|
||||
fn sgr_coefficients(a: i64, b: i64, k: &SgrConsts, bd: u32) -> (i64, i64) {
|
||||
let a = round2_64(a, 2 * (bd - 8));
|
||||
let d = round2_64(b, bd - 8);
|
||||
let p = (a * k.n - d * d).max(0);
|
||||
let z = round2_64(p * k.s, SGRPROJ_MTABLE_BITS as u32);
|
||||
let a2 = if z >= 255 {
|
||||
256
|
||||
} else if z == 0 {
|
||||
1
|
||||
} else {
|
||||
((z << SGRPROJ_SGR_BITS) + z / 2) / (z + 1)
|
||||
};
|
||||
let b2 = ((1i64 << SGRPROJ_SGR_BITS) - a2) * b * k.one_over_n;
|
||||
(a2, round2_64(b2, SGRPROJ_RECIP_BITS as u32))
|
||||
}
|
||||
|
||||
fn box_weight(pass: usize, i: i32, dx: i32, dy: i32) -> i64 {
|
||||
if pass == 0 {
|
||||
if ((i + dy) & 1) != 0 { if dx == 0 { 6 } else { 5 } } else { 0 }
|
||||
} else if dx == 0 || dy == 0 {
|
||||
4
|
||||
} else {
|
||||
3
|
||||
}
|
||||
}
|
||||
|
||||
fn box_filter(ctx: &LrCtx, blk: LrBlock, (set, pass): (usize, usize), bd: u32, f: &mut [[i32; 4]; 4]) {
|
||||
let LrBlock { x, y, w, h } = blk;
|
||||
let r = SGR_PARAMS[set][pass * 2] as i32;
|
||||
if r == 0 {
|
||||
return;
|
||||
@@ -207,35 +268,15 @@ fn box_filter(ctx: &LrCtx, x: i32, y: i32, w: i32, h: i32, set: usize, pass: usi
|
||||
let eps = SGR_PARAMS[set][pass * 2 + 1] as i64;
|
||||
let n = ((2 * r + 1) * (2 * r + 1)) as i64;
|
||||
let n2e = n * n * eps;
|
||||
let s = ((1i64 << SGRPROJ_MTABLE_BITS) + n2e / 2) / n2e;
|
||||
let one_over_n = ((1i64 << SGRPROJ_RECIP_BITS) + n / 2) / n;
|
||||
let k = SgrConsts { n, s: ((1i64 << SGRPROJ_MTABLE_BITS) + n2e / 2) / n2e, one_over_n: ((1i64 << SGRPROJ_RECIP_BITS) + n / 2) / n };
|
||||
let mut a_arr = [[0i64; 6]; 6];
|
||||
let mut b_arr = [[0i64; 6]; 6];
|
||||
for i in -1..h + 1 {
|
||||
for j in -1..w + 1 {
|
||||
let mut a = 0i64;
|
||||
let mut b = 0i64;
|
||||
for dy in -r..=r {
|
||||
for dx in -r..=r {
|
||||
let c = ctx.sample(x + j + dx, y + i + dy) as i64;
|
||||
a += c * c;
|
||||
b += c;
|
||||
}
|
||||
}
|
||||
let a = round2_64(a, 2 * (bd - 8));
|
||||
let d = round2_64(b, bd - 8);
|
||||
let p = (a * n - d * d).max(0);
|
||||
let z = round2_64(p * s, SGRPROJ_MTABLE_BITS as u32);
|
||||
let a2 = if z >= 255 {
|
||||
256
|
||||
} else if z == 0 {
|
||||
1
|
||||
} else {
|
||||
((z << SGRPROJ_SGR_BITS) + z / 2) / (z + 1)
|
||||
};
|
||||
let b2 = ((1i64 << SGRPROJ_SGR_BITS) - a2) * b * one_over_n;
|
||||
let (a, b) = box_sums(ctx, x + j, y + i, r);
|
||||
let (a2, b2) = sgr_coefficients(a, b, &k, bd);
|
||||
a_arr[(i + 1) as usize][(j + 1) as usize] = a2;
|
||||
b_arr[(i + 1) as usize][(j + 1) as usize] = round2_64(b2, SGRPROJ_RECIP_BITS as u32);
|
||||
b_arr[(i + 1) as usize][(j + 1) as usize] = b2;
|
||||
}
|
||||
}
|
||||
for i in 0..h {
|
||||
@@ -245,13 +286,7 @@ fn box_filter(ctx: &LrCtx, x: i32, y: i32, w: i32, h: i32, set: usize, pass: usi
|
||||
let mut b = 0i64;
|
||||
for dy in -1..=1i32 {
|
||||
for dx in -1..=1i32 {
|
||||
let weight = if pass == 0 {
|
||||
if ((i + dy) & 1) != 0 { if dx == 0 { 6 } else { 5 } } else { 0 }
|
||||
} else if dx == 0 || dy == 0 {
|
||||
4
|
||||
} else {
|
||||
3
|
||||
};
|
||||
let weight = box_weight(pass, i, dx, dy);
|
||||
a += weight * a_arr[(i + dy + 1) as usize][(j + dx + 1) as usize];
|
||||
b += weight * b_arr[(i + dy + 1) as usize][(j + dx + 1) as usize];
|
||||
}
|
||||
|
||||
@@ -165,6 +165,15 @@ pub(crate) fn inverse_dct<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
for i in 0..len {
|
||||
t[i] = copy[brev(n, i)];
|
||||
}
|
||||
inverse_dct_stage1(t, n, r);
|
||||
inverse_dct_stage2(t, n, r);
|
||||
inverse_dct_stage3(t, n, r);
|
||||
inverse_dct_stage4(t, n, r);
|
||||
inverse_dct_stage5(t, n, r);
|
||||
inverse_dct_stage6(t, n, r);
|
||||
}
|
||||
|
||||
fn inverse_dct_stage1<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
if n == 6 {
|
||||
for i in 0..16 {
|
||||
bf(t, 32 + i, 63 - i, 63 - 4 * brev(4, i) as i32, false);
|
||||
@@ -190,6 +199,9 @@ pub(crate) fn inverse_dct<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
hd(t, 16 + 2 * i, 17 + 2 * i, i & 1 == 1, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inverse_dct_stage2<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
if n == 6 {
|
||||
for i in 0..4 {
|
||||
for j in 0..2 {
|
||||
@@ -221,6 +233,9 @@ pub(crate) fn inverse_dct<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inverse_dct_stage3<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
for i in 0..2 {
|
||||
bf(t, 2 * i, 2 * i + 1, 32 + 16 * i as i32, i == 0);
|
||||
}
|
||||
@@ -248,6 +263,9 @@ pub(crate) fn inverse_dct<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inverse_dct_stage4<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
for i in 0..2 {
|
||||
hd(t, i, 3 - i, false, r);
|
||||
}
|
||||
@@ -273,6 +291,9 @@ pub(crate) fn inverse_dct<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inverse_dct_stage5<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
if n >= 3 {
|
||||
for i in 0..4 {
|
||||
hd(t, i, 7 - i, false, r);
|
||||
@@ -300,6 +321,9 @@ pub(crate) fn inverse_dct<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
hd(t, i, 15 - i, false, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inverse_dct_stage6<T: Lane>(t: &mut [T], n: u32, r: u32) {
|
||||
if n >= 5 {
|
||||
for i in 0..4 {
|
||||
bf(t, 27 - i, 20 + i, 32, true);
|
||||
@@ -531,10 +555,9 @@ pub(crate) fn inverse_transform_2d(dequant: &[i32], residual: &mut [i32], tx_sz:
|
||||
let col_clamp = (bit_depth + 6).max(16);
|
||||
let rk = row_kind(tx_type);
|
||||
let ck = col_kind(tx_type);
|
||||
let mut t = [0i32; 64];
|
||||
let rect2 = log2w.abs_diff(log2h) == 1;
|
||||
let p = Tx2d { w, h, log2w, log2h, row_shift, col_shift, row_clamp, col_clamp, rk, ck, rect2 };
|
||||
if !lossless && bit_depth <= 10 {
|
||||
let p = Tx2d { w, h, log2w, log2h, row_shift, col_shift, row_clamp, col_clamp, rk, ck, rect2 };
|
||||
if w >= 8 && h >= 8 {
|
||||
transform_lanes::<8>(dequant, residual, &p);
|
||||
} else {
|
||||
@@ -542,6 +565,13 @@ pub(crate) fn inverse_transform_2d(dequant: &[i32], residual: &mut [i32], tx_sz:
|
||||
}
|
||||
return;
|
||||
}
|
||||
scalar_row_pass(dequant, residual, &p, lossless);
|
||||
scalar_col_pass(residual, &p, lossless);
|
||||
}
|
||||
|
||||
fn scalar_row_pass(dequant: &[i32], residual: &mut [i32], p: &Tx2d, lossless: bool) {
|
||||
let (w, h, log2w, row_shift, row_clamp, col_clamp, rk, rect2) = (p.w, p.h, p.log2w, p.row_shift, p.row_clamp, p.col_clamp, p.rk, p.rect2);
|
||||
let mut t = [0i32; 64];
|
||||
let in_w = w.min(32);
|
||||
for i in 0..h {
|
||||
// Every 1D transform maps an all-zero row to zeros (each step is linear with
|
||||
@@ -572,6 +602,11 @@ pub(crate) fn inverse_transform_2d(dequant: &[i32], residual: &mut [i32], tx_sz:
|
||||
residual[i * w + j] = round2(t[j], row_shift).clamp(lo, hi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scalar_col_pass(residual: &mut [i32], p: &Tx2d, lossless: bool) {
|
||||
let (w, h, log2h, col_shift, col_clamp, ck) = (p.w, p.h, p.log2h, p.col_shift, p.col_clamp, p.ck);
|
||||
let mut t = [0i32; 64];
|
||||
for j in 0..w {
|
||||
for i in 0..h {
|
||||
t[i] = residual[i * w + j];
|
||||
@@ -617,11 +652,16 @@ fn transform_1d<T: Lane>(t: &mut [T], kind: Kind1d, n: u32, r: u32) {
|
||||
/// The 2D inverse transform with `L` rows (row pass) or columns (column pass) at a time;
|
||||
/// bit-identical to the one-at-a-time process for bit depths up to 10.
|
||||
fn transform_lanes<const L: usize>(dequant: &[i32], residual: &mut [i32], p: &Tx2d) {
|
||||
let mut t = [[0i32; L]; 64];
|
||||
lanes_row_pass(dequant, residual, p, &mut t);
|
||||
lanes_col_pass(residual, p, &mut t);
|
||||
}
|
||||
|
||||
fn lanes_row_pass<const L: usize>(dequant: &[i32], residual: &mut [i32], p: &Tx2d, t: &mut [[i32; L]; 64]) {
|
||||
let (w, h) = (p.w, p.h);
|
||||
let in_w = w.min(32);
|
||||
let lo = -(1i32 << (p.col_clamp - 1));
|
||||
let hi = (1i32 << (p.col_clamp - 1)) - 1;
|
||||
let mut t = [[0i32; L]; 64];
|
||||
let mut i0 = 0;
|
||||
while i0 < h {
|
||||
// Rows without coefficients (and rows 32..63) transform to zeros.
|
||||
@@ -649,6 +689,10 @@ fn transform_lanes<const L: usize>(dequant: &[i32], residual: &mut [i32], p: &Tx
|
||||
}
|
||||
i0 += L;
|
||||
}
|
||||
}
|
||||
|
||||
fn lanes_col_pass<const L: usize>(residual: &mut [i32], p: &Tx2d, t: &mut [[i32; L]; 64]) {
|
||||
let (w, h) = (p.w, p.h);
|
||||
let mut j0 = 0;
|
||||
while j0 < w {
|
||||
for i in 0..h {
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::collections::HashMap;
|
||||
|
||||
use deckcraft_color::{ColorTransform, Rgba, SchemeSlot};
|
||||
use deckcraft_model::style::{Dash, Glow, Gradient, GradientShape, GradientStop, LineEnd, PatternFill, PictureFill, PictureMode, Reflection, Shadow};
|
||||
use deckcraft_model::{ColorRef, Effects, Fill, Geom, Line, ShapeStyle};
|
||||
use deckcraft_model::{ColorRef, Effects, Fill, Geom, Line, ShapeId, ShapeStyle};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::{
|
||||
@@ -556,81 +556,102 @@ fn edit_group_appearance(s: &mut Session, p: &Value, cmd: &str, f: impl Fn(&mut
|
||||
edit_shapes(s, p, cmd, |sh| apply(sh, 0, &f))
|
||||
}
|
||||
|
||||
fn fill(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let new_fill: Option<Option<Fill>> = if bool_or(p, "reset", false) {
|
||||
Some(None)
|
||||
} else if bool_or(p, "none", false) {
|
||||
Some(Some(Fill::None))
|
||||
} else if let Some(c) = color_param(p, "color") {
|
||||
Some(Some(Fill::solid(c)))
|
||||
} else if let Some(g) = p.get("gradient") {
|
||||
Some(Some(Fill::Gradient(
|
||||
gradient_param(g).ok_or_else(|| bad("shape.fill", "gradient needs at least two `stops`: [[0, \"#fff\"], [1, \"#000\"]]"))?,
|
||||
)))
|
||||
} else if let Some(pt) = p.get("pattern") {
|
||||
Some(Some(Fill::Pattern(PatternFill {
|
||||
/// The fill a `shape.fill` request sets: `Some(None)` clears it, `Some(Some(_))` sets it, and `None`
|
||||
/// leaves it as it is.
|
||||
fn requested_fill(s: &mut Session, p: &Value) -> Result<Option<Option<Fill>>> {
|
||||
if bool_or(p, "reset", false) {
|
||||
return Ok(Some(None));
|
||||
}
|
||||
if bool_or(p, "none", false) {
|
||||
return Ok(Some(Some(Fill::None)));
|
||||
}
|
||||
if let Some(c) = color_param(p, "color") {
|
||||
return Ok(Some(Some(Fill::solid(c))));
|
||||
}
|
||||
if let Some(g) = p.get("gradient") {
|
||||
let gradient = gradient_param(g).ok_or_else(|| bad("shape.fill", "gradient needs at least two `stops`: [[0, \"#fff\"], [1, \"#000\"]]"))?;
|
||||
return Ok(Some(Some(Fill::Gradient(gradient))));
|
||||
}
|
||||
if let Some(pt) = p.get("pattern") {
|
||||
return Ok(Some(Some(Fill::Pattern(PatternFill {
|
||||
preset: pt.get("preset").and_then(Value::as_str).unwrap_or("pct50").to_string(),
|
||||
fg: pt.get("fg").and_then(color_value).unwrap_or(ColorRef::scheme(SchemeSlot::Accent1)),
|
||||
bg: pt.get("bg").and_then(color_value).unwrap_or(ColorRef::scheme(SchemeSlot::Bg1)),
|
||||
})))
|
||||
} else if p.get("picture").is_some() {
|
||||
}))));
|
||||
}
|
||||
if p.get("picture").is_some() {
|
||||
let (name, bytes) = super::insert::media_bytes(&with_param(p, "data", p.get("picture").cloned().unwrap_or_default()), "shape.fill")?;
|
||||
let ct = super::insert::content_type(&name, &bytes);
|
||||
let media = s.edit(|doc, _| doc.add_media(&name, ct, bytes).ok_or_else(|| bad("shape.fill", NO_ID_SPACE)))?;
|
||||
Some(Some(Fill::Picture(PictureFill { media, ..Default::default() })))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return Ok(Some(Some(Fill::Picture(PictureFill { media, ..Default::default() }))));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// The effective fill of each targeted shape, for a transparency change on an inherited (style) fill.
|
||||
fn inherited_fills(s: &Session, p: &Value) -> Result<HashMap<ShapeId, Option<Fill>>> {
|
||||
let st = s.doc()?;
|
||||
let ctx = super::text::ctx_for(&st.doc, &st.selection);
|
||||
let mut fills = HashMap::new();
|
||||
for id in targets(s, p)? {
|
||||
if let Some(sh) = st.shape(id) {
|
||||
deckcraft_model::walk(std::slice::from_ref(sh), &mut |sh, _| {
|
||||
let fill = ctx.as_ref().and_then(|c| {
|
||||
let (f, ph) = deckcraft_model::resolve::fill(c, sh);
|
||||
f.map(|f| match f {
|
||||
Fill::Solid { color } => Fill::solid(ColorRef::rgb(c.color(&color, ph))),
|
||||
other => other,
|
||||
})
|
||||
});
|
||||
fills.insert(sh.id, fill);
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(fills)
|
||||
}
|
||||
|
||||
/// Applies a `shape.fill` request to one shape: the fill, then its picture options, then transparency.
|
||||
fn apply_fill(
|
||||
sh: &mut deckcraft_model::Shape,
|
||||
new_fill: Option<&Option<Fill>>,
|
||||
pic_props: Option<&Value>,
|
||||
transparency: Option<f64>,
|
||||
inherited: &HashMap<ShapeId, Option<Fill>>,
|
||||
) -> Result<()> {
|
||||
if let Some(f) = new_fill {
|
||||
sh.fill.clone_from(f);
|
||||
}
|
||||
if let (Some(pp), Some(Fill::Picture(pf))) = (pic_props, sh.fill.as_mut()) {
|
||||
picture_props(pf, pp);
|
||||
}
|
||||
if let (Some(t), Some(Fill::Picture(pf))) = (transparency, sh.fill.as_mut()) {
|
||||
let a = 1.0 - t.clamp(0.0, 1.0);
|
||||
pf.alpha = (a < 1.0).then_some(a);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(t) = transparency {
|
||||
let base = sh.fill.clone().or_else(|| inherited.get(&sh.id).cloned().flatten());
|
||||
if let Some(Fill::Solid { mut color }) = base {
|
||||
color.mods.retain(|m| !matches!(m, ColorTransform::Alpha(_)));
|
||||
if t > 0.0 {
|
||||
color.mods.push(ColorTransform::Alpha(((1.0 - t.clamp(0.0, 1.0)) * 100_000.0).round() as i32));
|
||||
}
|
||||
sh.fill = Some(Fill::Solid { color });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fill(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let new_fill = requested_fill(s, p)?;
|
||||
let transparency = f64_param(p, "transparency");
|
||||
let pic_props = p.get("pictureProps").filter(|v| v.is_object());
|
||||
if new_fill.is_none() && transparency.is_none() && pic_props.is_none() {
|
||||
return Err(bad("shape.fill", "give `color`, `none`, `gradient`, `pattern`, `picture`, `pictureProps`, `transparency` or `reset`"));
|
||||
}
|
||||
// Transparency on an inherited (style) fill needs the effective colour.
|
||||
let st = s.doc()?;
|
||||
let ctx_fill = {
|
||||
let ctx = super::text::ctx_for(&st.doc, &st.selection);
|
||||
let mut fills = HashMap::new();
|
||||
for id in targets(s, p)? {
|
||||
if let Some(sh) = st.shape(id) {
|
||||
deckcraft_model::walk(std::slice::from_ref(sh), &mut |sh, _| {
|
||||
let fill = ctx.as_ref().and_then(|c| {
|
||||
let (f, ph) = deckcraft_model::resolve::fill(c, sh);
|
||||
f.map(|f| match f {
|
||||
Fill::Solid { color } => Fill::solid(ColorRef::rgb(c.color(&color, ph))),
|
||||
other => other,
|
||||
})
|
||||
});
|
||||
fills.insert(sh.id, fill);
|
||||
});
|
||||
}
|
||||
}
|
||||
fills
|
||||
};
|
||||
edit_group_appearance(s, p, "shape.fill", |sh| {
|
||||
if let Some(f) = &new_fill {
|
||||
sh.fill.clone_from(f);
|
||||
}
|
||||
if let (Some(pp), Some(Fill::Picture(pf))) = (pic_props, sh.fill.as_mut()) {
|
||||
picture_props(pf, pp);
|
||||
}
|
||||
if let (Some(t), Some(Fill::Picture(pf))) = (transparency, sh.fill.as_mut()) {
|
||||
let a = 1.0 - t.clamp(0.0, 1.0);
|
||||
pf.alpha = (a < 1.0).then_some(a);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(t) = transparency {
|
||||
let base = sh.fill.clone().or_else(|| ctx_fill.get(&sh.id).cloned().flatten());
|
||||
if let Some(Fill::Solid { mut color }) = base {
|
||||
color.mods.retain(|m| !matches!(m, ColorTransform::Alpha(_)));
|
||||
if t > 0.0 {
|
||||
color.mods.push(ColorTransform::Alpha(((1.0 - t.clamp(0.0, 1.0)) * 100_000.0).round() as i32));
|
||||
}
|
||||
sh.fill = Some(Fill::Solid { color });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
let inherited = inherited_fills(s, p)?;
|
||||
edit_group_appearance(s, p, "shape.fill", |sh| apply_fill(sh, new_fill.as_ref(), pic_props, transparency, &inherited))
|
||||
}
|
||||
|
||||
/// Applies Format Shape ▸ Picture or texture fill options to a picture fill.
|
||||
|
||||
+201
-140
@@ -126,155 +126,216 @@ fn advance(rtf: &str, i: usize, n: usize) -> usize {
|
||||
if rtf.is_char_boundary(i + n) { i + n } else { i + rtf.get(i..).and_then(|r| r.chars().next()).map_or(1, char::len_utf8) }
|
||||
}
|
||||
|
||||
/// Group state: whether the text is being skipped, and the `\uc` fallback count.
|
||||
#[derive(Clone, Copy)]
|
||||
struct St {
|
||||
skip: bool,
|
||||
uc: usize,
|
||||
}
|
||||
|
||||
/// Parser state for [`rtf_to_outline`]: the cursor, the group stack and the outline so far.
|
||||
struct Outline<'a> {
|
||||
rtf: &'a str,
|
||||
b: &'a [u8],
|
||||
i: usize,
|
||||
stack: Vec<St>,
|
||||
st: St,
|
||||
level: Option<u32>,
|
||||
indent: i64,
|
||||
pending_skip: usize,
|
||||
line: String,
|
||||
out: String,
|
||||
}
|
||||
|
||||
/// Read an RTF outline into tab-indented outline text: a paragraph's outline level (or its left
|
||||
/// indent in 0.25" steps) becomes its number of tabs.
|
||||
pub fn rtf_to_outline(rtf: &str) -> String {
|
||||
#[derive(Clone, Copy)]
|
||||
struct St {
|
||||
skip: bool,
|
||||
uc: usize,
|
||||
}
|
||||
let b = rtf.as_bytes();
|
||||
let mut stack: Vec<St> = vec![];
|
||||
let mut st = St { skip: false, uc: 1 };
|
||||
let (mut level, mut indent): (Option<u32>, i64) = (None, 0);
|
||||
let mut line = String::new();
|
||||
let mut out = String::new();
|
||||
let mut pending_skip = 0usize;
|
||||
let mut i = 0;
|
||||
let flush = |line: &mut String, level: Option<u32>, indent: i64, out: &mut String| {
|
||||
let text = line.trim();
|
||||
if !text.is_empty() {
|
||||
let n = level.map(|l| l as i64).unwrap_or(indent.saturating_add(180) / 360).clamp(0, 9) as usize;
|
||||
out.push_str(&"\t".repeat(n));
|
||||
out.push_str(text);
|
||||
out.push('\n');
|
||||
}
|
||||
line.clear();
|
||||
let mut p = Outline {
|
||||
rtf,
|
||||
b: rtf.as_bytes(),
|
||||
i: 0,
|
||||
stack: vec![],
|
||||
st: St { skip: false, uc: 1 },
|
||||
level: None,
|
||||
indent: 0,
|
||||
pending_skip: 0,
|
||||
line: String::new(),
|
||||
out: String::new(),
|
||||
};
|
||||
while i < b.len() {
|
||||
let c = b[i];
|
||||
match c {
|
||||
b'{' => {
|
||||
stack.push(st);
|
||||
i += 1;
|
||||
// `{\*\dest …}`: an ignorable destination.
|
||||
if b[i..].starts_with(b"\\*") {
|
||||
st.skip = true;
|
||||
}
|
||||
}
|
||||
b'}' => {
|
||||
st = stack.pop().unwrap_or(St { skip: false, uc: 1 });
|
||||
i += 1;
|
||||
}
|
||||
b'\\' => {
|
||||
i += 1;
|
||||
let Some(&n) = b.get(i) else { break };
|
||||
if n.is_ascii_alphabetic() {
|
||||
let s = i;
|
||||
while i < b.len() && b[i].is_ascii_alphabetic() {
|
||||
i += 1;
|
||||
}
|
||||
let word = &rtf[s..i];
|
||||
let ns = i;
|
||||
if i < b.len() && (b[i] == b'-' || b[i].is_ascii_digit()) {
|
||||
i += 1;
|
||||
while i < b.len() && b[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
let arg: Option<i64> = rtf[ns..i].parse().ok();
|
||||
if i < b.len() && b[i] == b' ' {
|
||||
i += 1;
|
||||
}
|
||||
if pending_skip > 0 {
|
||||
pending_skip -= 1;
|
||||
continue;
|
||||
}
|
||||
if SKIP.contains(&word) {
|
||||
st.skip = true;
|
||||
}
|
||||
if st.skip {
|
||||
continue;
|
||||
}
|
||||
match word {
|
||||
"par" => {
|
||||
flush(&mut line, level, indent, &mut out);
|
||||
}
|
||||
"pard" => {
|
||||
level = None;
|
||||
indent = 0;
|
||||
}
|
||||
"outlinelevel" => level = arg.map(|a| a.clamp(0, 9) as u32),
|
||||
"li" => indent = arg.unwrap_or(0),
|
||||
"tab" | "line" => line.push(' '),
|
||||
"uc" => st.uc = arg.unwrap_or(1).clamp(0, 8) as usize,
|
||||
"u" => {
|
||||
let v = arg.unwrap_or(0);
|
||||
// RTF writes UTF-16 units as signed 16-bit values.
|
||||
push_utf16(&mut line, (v & 0xffff) as u32);
|
||||
pending_skip = 0;
|
||||
// Skip the fallback characters.
|
||||
let mut k = st.uc;
|
||||
while k > 0 && i < b.len() {
|
||||
if b[i] == b'\\' && b.get(i + 1) == Some(&b'\'') {
|
||||
i = advance(rtf, i, 4);
|
||||
} else if b[i] == b'{' || b[i] == b'}' || b[i] == b'\\' {
|
||||
break;
|
||||
} else {
|
||||
i = advance(rtf, i, 1);
|
||||
}
|
||||
k -= 1;
|
||||
}
|
||||
}
|
||||
"emdash" => line.push('—'),
|
||||
"endash" => line.push('–'),
|
||||
"bullet" => line.push('•'),
|
||||
"lquote" => line.push('‘'),
|
||||
"rquote" => line.push('’'),
|
||||
"ldblquote" => line.push('“'),
|
||||
"rdblquote" => line.push('”'),
|
||||
_ => {}
|
||||
}
|
||||
} else if n == b'\'' {
|
||||
let hex = rtf.get(i + 1..i + 3).filter(|h| h.bytes().all(|b| b.is_ascii_hexdigit())).and_then(|h| u8::from_str_radix(h, 16).ok());
|
||||
i = advance(rtf, i, 3);
|
||||
if !st.skip
|
||||
&& let Some(h) = hex
|
||||
&& h != 0
|
||||
{
|
||||
line.push(cp1252(h));
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
if st.skip {
|
||||
continue;
|
||||
}
|
||||
match n {
|
||||
b'\\' | b'{' | b'}' => line.push(n as char),
|
||||
b'~' => line.push('\u{a0}'),
|
||||
b'_' => line.push('\u{2011}'),
|
||||
b'\n' | b'\r' => flush(&mut line, level, indent, &mut out),
|
||||
_ => {}
|
||||
p.run();
|
||||
p.flush();
|
||||
finish_surrogates(p.out)
|
||||
}
|
||||
|
||||
impl Outline<'_> {
|
||||
fn run(&mut self) {
|
||||
while self.i < self.b.len() {
|
||||
match self.b[self.i] {
|
||||
b'{' => self.open_group(),
|
||||
b'}' => self.close_group(),
|
||||
b'\\' => {
|
||||
self.i += 1;
|
||||
if !self.escape() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
b'\r' | b'\n' => i += 1,
|
||||
_ => {
|
||||
let s = i;
|
||||
while i < b.len() && !matches!(b[i], b'{' | b'}' | b'\\' | b'\r' | b'\n') {
|
||||
i += 1;
|
||||
}
|
||||
if !st.skip
|
||||
&& let Some(t) = rtf.get(s..i)
|
||||
{
|
||||
line.push_str(&t.replace('\u{0}', ""));
|
||||
}
|
||||
b'\r' | b'\n' => self.i += 1,
|
||||
_ => self.text(),
|
||||
}
|
||||
}
|
||||
}
|
||||
flush(&mut line, level, indent, &mut out);
|
||||
finish_surrogates(out)
|
||||
|
||||
/// `{`: push the group; `{\*\dest …}` is an ignorable destination.
|
||||
fn open_group(&mut self) {
|
||||
self.stack.push(self.st);
|
||||
self.i += 1;
|
||||
if self.b[self.i..].starts_with(b"\\*") {
|
||||
self.st.skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn close_group(&mut self) {
|
||||
self.st = self.stack.pop().unwrap_or(St { skip: false, uc: 1 });
|
||||
self.i += 1;
|
||||
}
|
||||
|
||||
/// The character after a backslash: a control word, a hex escape or a symbol. False at the
|
||||
/// end of the input.
|
||||
fn escape(&mut self) -> bool {
|
||||
let Some(&n) = self.b.get(self.i) else { return false };
|
||||
if n.is_ascii_alphabetic() {
|
||||
self.control_word();
|
||||
} else if n == b'\'' {
|
||||
self.hex_escape();
|
||||
} else {
|
||||
self.symbol(n);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn control_word(&mut self) {
|
||||
let rtf = self.rtf;
|
||||
let s = self.i;
|
||||
while self.i < self.b.len() && self.b[self.i].is_ascii_alphabetic() {
|
||||
self.i += 1;
|
||||
}
|
||||
let word = &rtf[s..self.i];
|
||||
let ns = self.i;
|
||||
if self.i < self.b.len() && (self.b[self.i] == b'-' || self.b[self.i].is_ascii_digit()) {
|
||||
self.i += 1;
|
||||
while self.i < self.b.len() && self.b[self.i].is_ascii_digit() {
|
||||
self.i += 1;
|
||||
}
|
||||
}
|
||||
let arg: Option<i64> = rtf[ns..self.i].parse().ok();
|
||||
if self.i < self.b.len() && self.b[self.i] == b' ' {
|
||||
self.i += 1;
|
||||
}
|
||||
if self.pending_skip > 0 {
|
||||
self.pending_skip -= 1;
|
||||
return;
|
||||
}
|
||||
if SKIP.contains(&word) {
|
||||
self.st.skip = true;
|
||||
}
|
||||
if !self.st.skip {
|
||||
self.apply(word, arg);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply(&mut self, word: &str, arg: Option<i64>) {
|
||||
match word {
|
||||
"par" => self.flush(),
|
||||
"pard" => {
|
||||
self.level = None;
|
||||
self.indent = 0;
|
||||
}
|
||||
"outlinelevel" => self.level = arg.map(|a| a.clamp(0, 9) as u32),
|
||||
"li" => self.indent = arg.unwrap_or(0),
|
||||
"tab" | "line" => self.line.push(' '),
|
||||
"uc" => self.st.uc = arg.unwrap_or(1).clamp(0, 8) as usize,
|
||||
"u" => self.unicode(arg.unwrap_or(0)),
|
||||
"emdash" => self.line.push('—'),
|
||||
"endash" => self.line.push('–'),
|
||||
"bullet" => self.line.push('•'),
|
||||
"lquote" => self.line.push('‘'),
|
||||
"rquote" => self.line.push('’'),
|
||||
"ldblquote" => self.line.push('“'),
|
||||
"rdblquote" => self.line.push('”'),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// `\uN`: one UTF-16 unit, then the `\uc` fallback characters, which are skipped.
|
||||
fn unicode(&mut self, v: i64) {
|
||||
// RTF writes UTF-16 units as signed 16-bit values.
|
||||
push_utf16(&mut self.line, (v & 0xffff) as u32);
|
||||
self.pending_skip = 0;
|
||||
let mut k = self.st.uc;
|
||||
while k > 0 && self.i < self.b.len() {
|
||||
if self.b[self.i] == b'\\' && self.b.get(self.i + 1) == Some(&b'\'') {
|
||||
self.i = advance(self.rtf, self.i, 4);
|
||||
} else if matches!(self.b[self.i], b'{' | b'}' | b'\\') {
|
||||
break;
|
||||
} else {
|
||||
self.i = advance(self.rtf, self.i, 1);
|
||||
}
|
||||
k -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// `\'hh`: one cp1252 byte.
|
||||
fn hex_escape(&mut self) {
|
||||
let hex =
|
||||
self.rtf.get(self.i + 1..self.i + 3).filter(|h| h.bytes().all(|b| b.is_ascii_hexdigit())).and_then(|h| u8::from_str_radix(h, 16).ok());
|
||||
self.i = advance(self.rtf, self.i, 3);
|
||||
if !self.st.skip
|
||||
&& let Some(h) = hex
|
||||
&& h != 0
|
||||
{
|
||||
self.line.push(cp1252(h));
|
||||
}
|
||||
}
|
||||
|
||||
/// A one-character control symbol (`\\`, `\{`, `\~`, ...).
|
||||
fn symbol(&mut self, n: u8) {
|
||||
self.i += 1;
|
||||
if self.st.skip {
|
||||
return;
|
||||
}
|
||||
match n {
|
||||
b'\\' | b'{' | b'}' => self.line.push(n as char),
|
||||
b'~' => self.line.push('\u{a0}'),
|
||||
b'_' => self.line.push('\u{2011}'),
|
||||
b'\n' | b'\r' => self.flush(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plain text up to the next special character.
|
||||
fn text(&mut self) {
|
||||
let s = self.i;
|
||||
while self.i < self.b.len() && !matches!(self.b[self.i], b'{' | b'}' | b'\\' | b'\r' | b'\n') {
|
||||
self.i += 1;
|
||||
}
|
||||
if !self.st.skip
|
||||
&& let Some(t) = self.rtf.get(s..self.i)
|
||||
{
|
||||
self.line.push_str(&t.replace('\u{0}', ""));
|
||||
}
|
||||
}
|
||||
|
||||
/// Ends the current line: a non-empty line becomes one outline entry, indented by its level.
|
||||
fn flush(&mut self) {
|
||||
let text = self.line.trim();
|
||||
if !text.is_empty() {
|
||||
let n = self.level.map(|l| l as i64).unwrap_or(self.indent.saturating_add(180) / 360).clamp(0, 9) as usize;
|
||||
self.out.push_str(&"\t".repeat(n));
|
||||
self.out.push_str(text);
|
||||
self.out.push('\n');
|
||||
}
|
||||
self.line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a UTF-16 unit; surrogate halves are parked as private markers (NUL plus four hex digits)
|
||||
|
||||
+210
-149
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::error::{Result, ensure};
|
||||
use crate::picture::{FrameRef, RefPic};
|
||||
use crate::slice::{Mmco, SliceHeader, SliceType};
|
||||
use crate::slice::{Mmco, RefPicListMod, SliceHeader, SliceType};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -174,6 +174,116 @@ impl Dpb {
|
||||
}
|
||||
}
|
||||
|
||||
/// Initial reference picture lists (8.2.4.2) as DPB entries.
|
||||
fn initial_entry_lists<'a>(
|
||||
sh: &SliceHeader,
|
||||
cur_poc: i32,
|
||||
short: &[&'a DpbEntry],
|
||||
long: &[&'a DpbEntry],
|
||||
pic_num: impl Fn(&DpbEntry) -> i32,
|
||||
) -> [Vec<&'a DpbEntry>; 2] {
|
||||
let mut entry_lists: [Vec<&'a DpbEntry>; 2] = [Vec::new(), Vec::new()];
|
||||
if sh.slice_type != SliceType::B {
|
||||
let mut s = short.to_vec();
|
||||
s.sort_by_key(|e| std::cmp::Reverse(pic_num(e)));
|
||||
entry_lists[0] = s.into_iter().chain(long.iter().copied()).collect();
|
||||
} else {
|
||||
let mut before: Vec<&'a DpbEntry> = short.iter().copied().filter(|e| e.poc < cur_poc).collect();
|
||||
let mut after: Vec<&'a DpbEntry> = short.iter().copied().filter(|e| e.poc > cur_poc).collect();
|
||||
before.sort_by_key(|e| std::cmp::Reverse(e.poc));
|
||||
after.sort_by_key(|e| e.poc);
|
||||
entry_lists[0] = before.iter().chain(after.iter()).chain(long.iter()).copied().collect();
|
||||
entry_lists[1] = after.iter().chain(before.iter()).chain(long.iter()).copied().collect();
|
||||
if entry_lists[1].len() > 1 && entry_lists[0].len() == entry_lists[1].len() {
|
||||
let same = entry_lists[0].iter().zip(entry_lists[1].iter()).all(|(a, b)| std::ptr::eq(*a, *b));
|
||||
if same {
|
||||
entry_lists[1].swap(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
entry_lists
|
||||
}
|
||||
|
||||
/// picNumLXNoWrap (8-34, 8-35) of a short-term modification from the predictor `pred`, and the
|
||||
/// picNum it selects.
|
||||
fn short_term_pic_nums(pred: i32, idc: u32, value: u32, curr_pic_num: i32, max_pic_num: i32) -> (i32, i32) {
|
||||
let d = value as i32 + 1;
|
||||
let no_wrap = if idc == 0 {
|
||||
if pred - d < 0 { pred - d + max_pic_num } else { pred - d }
|
||||
} else if pred + d >= max_pic_num {
|
||||
pred + d - max_pic_num
|
||||
} else {
|
||||
pred + d
|
||||
};
|
||||
let pn = if no_wrap > curr_pic_num { no_wrap - max_pic_num } else { no_wrap };
|
||||
(no_wrap, pn)
|
||||
}
|
||||
|
||||
/// Puts `target` at `ref_idx` and shifts the rest of the list up, then drops the later entries
|
||||
/// that repeat it (8.2.4.3.1, 8.2.4.3.2) and keeps the list `n` long.
|
||||
fn insert_ref<'a>(list: &mut Vec<Option<&'a DpbEntry>>, ref_idx: usize, n: usize, target: &'a DpbEntry, is_long: bool) {
|
||||
for c in (ref_idx + 1..=n).rev() {
|
||||
list[c] = list[c - 1];
|
||||
}
|
||||
list[ref_idx] = Some(target);
|
||||
let mut nidx = ref_idx + 1;
|
||||
for c in ref_idx + 1..=n {
|
||||
let keep = match list[c] {
|
||||
None => true,
|
||||
Some(e) => {
|
||||
if is_long {
|
||||
!(e.mark == RefMark::Long && e.long_term_frame_idx == target.long_term_frame_idx)
|
||||
} else {
|
||||
!(e.mark == RefMark::Short && std::ptr::eq(e, target))
|
||||
}
|
||||
}
|
||||
};
|
||||
if keep {
|
||||
list[nidx] = list[c];
|
||||
nidx += 1;
|
||||
}
|
||||
}
|
||||
list.truncate(n);
|
||||
}
|
||||
|
||||
/// Reference picture list modification (8.2.4.3) of one list; `n` is num_ref_idx_active.
|
||||
fn modify_ref_list<'a>(
|
||||
list: &mut Vec<Option<&'a DpbEntry>>,
|
||||
mods: &[RefPicListMod],
|
||||
n: usize,
|
||||
cur_fn: u32,
|
||||
max_frame_num: u32,
|
||||
short: &[&'a DpbEntry],
|
||||
long: &[&'a DpbEntry],
|
||||
) -> Result<()> {
|
||||
let max_pic_num = max_frame_num as i32;
|
||||
let curr_pic_num = cur_fn as i32;
|
||||
let mut pred = curr_pic_num;
|
||||
let mut ref_idx = 0usize;
|
||||
for m in mods {
|
||||
ensure!(ref_idx < n, "too many ref_pic_list_modification entries");
|
||||
list.push(None); // temporarily one longer
|
||||
let (target, is_long): (Option<&DpbEntry>, bool) = match m.idc {
|
||||
0 | 1 => {
|
||||
let (no_wrap, pn) = Self::short_term_pic_nums(pred, m.idc, m.value, curr_pic_num, max_pic_num);
|
||||
pred = no_wrap;
|
||||
(short.iter().copied().find(|e| Self::frame_num_wrap(e, cur_fn, max_frame_num) == pn), false)
|
||||
}
|
||||
2 => (long.iter().copied().find(|e| e.long_term_frame_idx == m.value), true),
|
||||
_ => continue,
|
||||
};
|
||||
let Some(target) = target else {
|
||||
// Missing picture: keep the list unchanged (error concealment).
|
||||
list.pop();
|
||||
ref_idx += 1;
|
||||
continue;
|
||||
};
|
||||
Self::insert_ref(list, ref_idx, n, target, is_long);
|
||||
ref_idx += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Initial reference picture lists (8.2.4.2) + modification (8.2.4.3) for a frame slice.
|
||||
pub fn build_ref_lists(&self, sh: &SliceHeader, cur_poc: i32, max_frame_num: u32) -> Result<[Vec<RefPic>; 2]> {
|
||||
let mut lists: [Vec<RefPic>; 2] = [Vec::new(), Vec::new()];
|
||||
@@ -186,25 +296,7 @@ impl Dpb {
|
||||
let mut long: Vec<&DpbEntry> = self.entries.iter().filter(|e| e.mark == RefMark::Long).collect();
|
||||
long.sort_by_key(|e| e.long_term_frame_idx);
|
||||
let to_ref = |e: &&DpbEntry| RefPic { frame: e.frame.clone(), long_term: e.mark == RefMark::Long };
|
||||
let mut entry_lists: [Vec<&DpbEntry>; 2] = [Vec::new(), Vec::new()];
|
||||
if sh.slice_type != SliceType::B {
|
||||
let mut s = short.clone();
|
||||
s.sort_by_key(|e| std::cmp::Reverse(pic_num(e)));
|
||||
entry_lists[0] = s.into_iter().chain(long.iter().copied()).collect();
|
||||
} else {
|
||||
let mut before: Vec<&DpbEntry> = short.iter().copied().filter(|e| e.poc < cur_poc).collect();
|
||||
let mut after: Vec<&DpbEntry> = short.iter().copied().filter(|e| e.poc > cur_poc).collect();
|
||||
before.sort_by_key(|e| std::cmp::Reverse(e.poc));
|
||||
after.sort_by_key(|e| e.poc);
|
||||
entry_lists[0] = before.iter().chain(after.iter()).chain(long.iter()).copied().collect();
|
||||
entry_lists[1] = after.iter().chain(before.iter()).chain(long.iter()).copied().collect();
|
||||
if entry_lists[1].len() > 1 && entry_lists[0].len() == entry_lists[1].len() {
|
||||
let same = entry_lists[0].iter().zip(entry_lists[1].iter()).all(|(a, b)| std::ptr::eq(*a, *b));
|
||||
if same {
|
||||
entry_lists[1].swap(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
let entry_lists = Self::initial_entry_lists(sh, cur_poc, &short, &long, pic_num);
|
||||
let nlists = if sh.slice_type == SliceType::B { 2 } else { 1 };
|
||||
for l in 0..nlists {
|
||||
let n = sh.num_ref_idx_active[l] as usize;
|
||||
@@ -212,60 +304,7 @@ impl Dpb {
|
||||
list.truncate(n);
|
||||
list.resize(n, None);
|
||||
if !sh.ref_pic_list_mod[l].is_empty() {
|
||||
let max_pic_num = max_frame_num as i32;
|
||||
let curr_pic_num = cur_fn as i32;
|
||||
let mut pred = curr_pic_num;
|
||||
let mut ref_idx = 0usize;
|
||||
for m in &sh.ref_pic_list_mod[l] {
|
||||
ensure!(ref_idx < n, "too many ref_pic_list_modification entries");
|
||||
list.push(None); // temporarily one longer
|
||||
let (target, is_long): (Option<&DpbEntry>, bool) = match m.idc {
|
||||
0 | 1 => {
|
||||
let d = m.value as i32 + 1;
|
||||
let no_wrap = if m.idc == 0 {
|
||||
if pred - d < 0 { pred - d + max_pic_num } else { pred - d }
|
||||
} else if pred + d >= max_pic_num {
|
||||
pred + d - max_pic_num
|
||||
} else {
|
||||
pred + d
|
||||
};
|
||||
pred = no_wrap;
|
||||
let pn = if no_wrap > curr_pic_num { no_wrap - max_pic_num } else { no_wrap };
|
||||
(short.iter().copied().find(|e| pic_num(e) == pn), false)
|
||||
}
|
||||
2 => (long.iter().copied().find(|e| e.long_term_frame_idx == m.value), true),
|
||||
_ => continue,
|
||||
};
|
||||
let Some(target) = target else {
|
||||
// Missing picture: keep the list unchanged (error concealment).
|
||||
list.pop();
|
||||
ref_idx += 1;
|
||||
continue;
|
||||
};
|
||||
for c in (ref_idx + 1..=n).rev() {
|
||||
list[c] = list[c - 1];
|
||||
}
|
||||
list[ref_idx] = Some(target);
|
||||
ref_idx += 1;
|
||||
let mut nidx = ref_idx;
|
||||
for c in ref_idx..=n {
|
||||
let keep = match list[c] {
|
||||
None => true,
|
||||
Some(e) => {
|
||||
if is_long {
|
||||
!(e.mark == RefMark::Long && e.long_term_frame_idx == target.long_term_frame_idx)
|
||||
} else {
|
||||
!(e.mark == RefMark::Short && std::ptr::eq(e, target))
|
||||
}
|
||||
}
|
||||
};
|
||||
if keep {
|
||||
list[nidx] = list[c];
|
||||
nidx += 1;
|
||||
}
|
||||
}
|
||||
list.truncate(n);
|
||||
}
|
||||
Self::modify_ref_list(&mut list, &sh.ref_pic_list_mod[l], n, cur_fn, max_frame_num, &short, &long)?;
|
||||
}
|
||||
// Fill missing entries (should not happen in conforming streams) with the first valid entry.
|
||||
let fallback = list.iter().flatten().next().copied().or_else(|| self.entries.last());
|
||||
@@ -275,91 +314,88 @@ impl Dpb {
|
||||
Ok(lists)
|
||||
}
|
||||
|
||||
/// Adaptive memory control (8.2.5.4). Returns true if MMCO 5 was executed.
|
||||
fn apply_mmcos(&mut self, mmcos: &[Mmco], cur_frame_num: u32, max_frame_num: u32, cur_long: &mut Option<u32>) -> bool {
|
||||
/// One memory management control operation; returns true for MMCO 5.
|
||||
fn apply_mmco(&mut self, m: &Mmco, cur_frame_num: u32, max_frame_num: u32, cur_long: &mut Option<u32>) -> bool {
|
||||
let mut had5 = false;
|
||||
let curr_pic_num = cur_frame_num as i32;
|
||||
for m in mmcos {
|
||||
match m.op {
|
||||
1 => {
|
||||
let pn = curr_pic_num - (m.difference_of_pic_nums_minus1 as i32 + 1);
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Short && Self::frame_num_wrap(e, cur_frame_num, max_frame_num) == pn {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
match m.op {
|
||||
1 => {
|
||||
let pn = curr_pic_num - (m.difference_of_pic_nums_minus1 as i32 + 1);
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Short && Self::frame_num_wrap(e, cur_frame_num, max_frame_num) == pn {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && e.long_term_frame_idx == m.long_term_pic_num {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && e.long_term_frame_idx == m.long_term_pic_num {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
3 => {
|
||||
let pn = curr_pic_num - (m.difference_of_pic_nums_minus1 as i32 + 1);
|
||||
}
|
||||
3 => {
|
||||
let pn = curr_pic_num - (m.difference_of_pic_nums_minus1 as i32 + 1);
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && e.long_term_frame_idx == m.long_term_frame_idx {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Short && Self::frame_num_wrap(e, cur_frame_num, max_frame_num) == pn {
|
||||
e.mark = RefMark::Long;
|
||||
e.long_term_frame_idx = m.long_term_frame_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
let max = m.max_long_term_frame_idx_plus1;
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && (max == 0 || e.long_term_frame_idx > max - 1) {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
self.max_long_term_frame_idx = if max == 0 { None } else { Some(max - 1) };
|
||||
}
|
||||
5 => {
|
||||
for e in &mut self.entries {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
self.max_long_term_frame_idx = None;
|
||||
had5 = true;
|
||||
}
|
||||
6 => {
|
||||
// Only an index within the allowed range may be assigned (8.2.5.4.6).
|
||||
let allowed = match self.max_long_term_frame_idx {
|
||||
Some(max) => m.long_term_frame_idx <= max,
|
||||
None => false,
|
||||
};
|
||||
if allowed {
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && e.long_term_frame_idx == m.long_term_frame_idx {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Short && Self::frame_num_wrap(e, cur_frame_num, max_frame_num) == pn {
|
||||
e.mark = RefMark::Long;
|
||||
e.long_term_frame_idx = m.long_term_frame_idx;
|
||||
}
|
||||
}
|
||||
*cur_long = Some(m.long_term_frame_idx);
|
||||
}
|
||||
4 => {
|
||||
let max = m.max_long_term_frame_idx_plus1;
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && (max == 0 || e.long_term_frame_idx > max - 1) {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
self.max_long_term_frame_idx = if max == 0 { None } else { Some(max - 1) };
|
||||
}
|
||||
5 => {
|
||||
for e in &mut self.entries {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
self.max_long_term_frame_idx = None;
|
||||
had5 = true;
|
||||
}
|
||||
6 => {
|
||||
let allowed = match self.max_long_term_frame_idx {
|
||||
Some(max) => m.long_term_frame_idx <= max,
|
||||
None => false,
|
||||
};
|
||||
if allowed {
|
||||
for e in &mut self.entries {
|
||||
if e.mark == RefMark::Long && e.long_term_frame_idx == m.long_term_frame_idx {
|
||||
e.mark = RefMark::Unused;
|
||||
}
|
||||
}
|
||||
*cur_long = Some(m.long_term_frame_idx);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
had5
|
||||
}
|
||||
|
||||
/// Adaptive memory control (8.2.5.4). Returns true if MMCO 5 was executed.
|
||||
fn apply_mmcos(&mut self, mmcos: &[Mmco], cur_frame_num: u32, max_frame_num: u32, cur_long: &mut Option<u32>) -> bool {
|
||||
let mut had5 = false;
|
||||
for m in mmcos {
|
||||
had5 |= self.apply_mmco(m, cur_frame_num, max_frame_num, cur_long);
|
||||
}
|
||||
self.prune();
|
||||
had5
|
||||
}
|
||||
|
||||
/// Reference marking for the just-decoded picture and insertion into the DPB with output bumping.
|
||||
/// Returns pictures ready for output.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn store_picture(
|
||||
&mut self,
|
||||
sh: &SliceHeader,
|
||||
frame: FrameRef,
|
||||
poc: i32,
|
||||
max_frame_num: u32,
|
||||
max_num_ref_frames: usize,
|
||||
meta: Arc<OutputMeta>,
|
||||
out: &mut Vec<Output>,
|
||||
) {
|
||||
/// Reference marking of the current picture (8.2.5); returns (mark, long-term idx, MMCO 5 executed).
|
||||
fn mark_current(&mut self, sh: &SliceHeader, max_frame_num: u32, max_num_ref_frames: usize) -> (RefMark, u32, bool) {
|
||||
let mut mark = RefMark::Unused;
|
||||
let mut long_idx = 0;
|
||||
let mut had5 = false;
|
||||
@@ -391,12 +427,12 @@ impl Dpb {
|
||||
}
|
||||
}
|
||||
}
|
||||
if had5 {
|
||||
// All earlier pictures are output before the current one (C.4.4).
|
||||
self.flush(out);
|
||||
}
|
||||
let frame_num = if had5 { 0 } else { sh.frame_num };
|
||||
// Bumping until there is space (C.4.5.1 / C.4.5.2).
|
||||
(mark, long_idx, had5)
|
||||
}
|
||||
|
||||
/// Bumping until there is space (C.4.5.1 / C.4.5.2). Returns true when the (non-reference) current
|
||||
/// picture has the smallest POC and is to be output directly instead of being stored.
|
||||
fn bump_for_space(&mut self, mark: RefMark, poc: i32, out: &mut Vec<Output>) -> bool {
|
||||
if mark == RefMark::Unused {
|
||||
loop {
|
||||
let waiting = self.entries.len();
|
||||
@@ -406,8 +442,7 @@ impl Dpb {
|
||||
// non-reference picture with the smallest POC is output directly
|
||||
let min_poc = self.entries.iter().filter(|e| e.needed_for_output).map(|e| e.poc).min();
|
||||
if min_poc.is_none_or(|m| poc < m) {
|
||||
out.push(Output { frame, poc, meta });
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
if !self.bump(out) {
|
||||
break;
|
||||
@@ -425,6 +460,32 @@ impl Dpb {
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Reference marking for the just-decoded picture and insertion into the DPB with output bumping.
|
||||
/// Returns pictures ready for output.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn store_picture(
|
||||
&mut self,
|
||||
sh: &SliceHeader,
|
||||
frame: FrameRef,
|
||||
poc: i32,
|
||||
max_frame_num: u32,
|
||||
max_num_ref_frames: usize,
|
||||
meta: Arc<OutputMeta>,
|
||||
out: &mut Vec<Output>,
|
||||
) {
|
||||
let (mark, long_idx, had5) = self.mark_current(sh, max_frame_num, max_num_ref_frames);
|
||||
if had5 {
|
||||
// All earlier pictures are output before the current one (C.4.4).
|
||||
self.flush(out);
|
||||
}
|
||||
let frame_num = if had5 { 0 } else { sh.frame_num };
|
||||
if self.bump_for_space(mark, poc, out) {
|
||||
out.push(Output { frame, poc, meta });
|
||||
return;
|
||||
}
|
||||
self.entries.push(DpbEntry {
|
||||
frame,
|
||||
poc,
|
||||
|
||||
@@ -575,7 +575,7 @@ impl Pps {
|
||||
ensure!(sps_id < 32, "seq_parameter_set_id out of range");
|
||||
let sps = spss
|
||||
.get(sps_id as usize)
|
||||
.and_then(|s| s.as_ref())
|
||||
.and_then(Option::as_ref)
|
||||
.ok_or_else(|| crate::Error::MissingParameterSet(format!("SPS {sps_id} for PPS {id}")))?;
|
||||
let entropy_coding_mode = r.read_flag()?;
|
||||
let bottom_field_pic_order_in_frame_present = r.read_flag()?;
|
||||
|
||||
@@ -250,7 +250,7 @@ impl Frame {
|
||||
|
||||
/// Copy the w x h window of chroma component `c` at (x0, y0) (clamped) into `out`.
|
||||
#[inline]
|
||||
pub fn chroma_window(&self, c: usize, x0: i32, y0: i32, w: usize, h: usize, out: &mut [u8], os: usize) {
|
||||
pub fn chroma_window(&self, c: usize, (x0, y0): (i32, i32), (w, h): (usize, usize), out: &mut [u8], os: usize) {
|
||||
let (cw, ch) = (self.cwidth, self.cheight);
|
||||
for r in 0..h {
|
||||
let yy = (y0 + r as i32).clamp(0, ch as i32 - 1) as usize;
|
||||
|
||||
+39
-25
@@ -232,7 +232,7 @@ fn predict_block(f: &Frame, mv: [i16; 2], px: usize, py: usize, bw: usize, bh: u
|
||||
let (fx, fy) = ((mv[0] & 7) as u32, (mv[1] & 7) as u32);
|
||||
for (comp, out) in c.into_iter().enumerate() {
|
||||
let mut w = [0u8; 16 * 9];
|
||||
f.chroma_window(comp, cx, cy, cw + 1, ch + 1, &mut w, 16);
|
||||
f.chroma_window(comp, (cx, cy), (cw + 1, ch + 1), &mut w, 16);
|
||||
inter::mc_chroma_win(&w, 16, fx, fy, cw, ch, out, cs);
|
||||
}
|
||||
}
|
||||
@@ -665,33 +665,38 @@ impl<'a> SliceDecoder<'a> {
|
||||
return Ok(());
|
||||
}
|
||||
for p in 0..4 {
|
||||
let sub = self.cur.sub[p];
|
||||
let (x8, y8) = ((p & 1) * 2, (p >> 1) * 2);
|
||||
if sub.direct {
|
||||
self.derive_direct(1 << p)?;
|
||||
continue;
|
||||
self.derive_sub_mb_motion(p, n)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Motion of one 8x8 sub-macroblock `p` of a P8x8/B8x8 MB.
|
||||
fn derive_sub_mb_motion(&mut self, p: usize, n: [usize; 2]) -> Result<()> {
|
||||
let sub = self.cur.sub[p];
|
||||
let (x8, y8) = ((p & 1) * 2, (p >> 1) * 2);
|
||||
if sub.direct {
|
||||
return self.derive_direct(1 << p);
|
||||
}
|
||||
let mut refs = [-1i8; 2];
|
||||
for (l, r) in refs.iter_mut().enumerate() {
|
||||
if sub.pred & (1 << l) != 0 {
|
||||
let v = self.cur.ref_idx[l][p];
|
||||
ensure!((v as usize) < n[l], "ref_idx {v} out of range (list {l}, size {})", n[l]);
|
||||
*r = v;
|
||||
}
|
||||
let mut refs = [-1i8; 2];
|
||||
for (l, r) in refs.iter_mut().enumerate() {
|
||||
if sub.pred & (1 << l) != 0 {
|
||||
let v = self.cur.ref_idx[l][p];
|
||||
ensure!((v as usize) < n[l], "ref_idx {v} out of range (list {l}, size {})", n[l]);
|
||||
*r = v;
|
||||
}
|
||||
for j in 0..sub.shape.num_parts() {
|
||||
let (sx, sy, w, h) = sub.shape.rect(j);
|
||||
let (x, y) = (x8 + sx, y8 + sy);
|
||||
let mut mvs = [[0i16; 2]; 2];
|
||||
for l in 0..2 {
|
||||
if refs[l] >= 0 {
|
||||
let mvp = self.mvp(l, x, y, w, refs[l], 0, 0);
|
||||
let d = self.cur.mvd[l][p * 4 + j];
|
||||
mvs[l] = [mvp[0].wrapping_add(d[0]), mvp[1].wrapping_add(d[1])];
|
||||
}
|
||||
}
|
||||
for j in 0..sub.shape.num_parts() {
|
||||
let (sx, sy, w, h) = sub.shape.rect(j);
|
||||
let (x, y) = (x8 + sx, y8 + sy);
|
||||
let mut mvs = [[0i16; 2]; 2];
|
||||
for l in 0..2 {
|
||||
if refs[l] >= 0 {
|
||||
let mvp = self.mvp(l, x, y, w, refs[l], 0, 0);
|
||||
let d = self.cur.mvd[l][p * 4 + j];
|
||||
mvs[l] = [mvp[0].wrapping_add(d[0]), mvp[1].wrapping_add(d[1])];
|
||||
}
|
||||
}
|
||||
self.set_motion(x, y, w, h, refs, mvs);
|
||||
}
|
||||
self.set_motion(x, y, w, h, refs, mvs);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1380,6 +1385,11 @@ impl<'a> SliceDecoder<'a> {
|
||||
cavlc::residual_block(r, &mut lv, 0, 15, 16, nc)?;
|
||||
self.put_luma_dc(&lv);
|
||||
}
|
||||
self.residual_cavlc_luma(r, cbp, kind, t8)?;
|
||||
self.residual_cavlc_chroma(r, cbp)
|
||||
}
|
||||
|
||||
fn residual_cavlc_luma(&mut self, r: &mut BitReader, cbp: u8, kind: MbKind, t8: bool) -> Result<()> {
|
||||
for b8 in 0..4 {
|
||||
let (bx8, by8) = ((b8 & 1) * 2, (b8 >> 1) * 2);
|
||||
if cbp & (1 << b8) == 0 {
|
||||
@@ -1413,6 +1423,10 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn residual_cavlc_chroma(&mut self, r: &mut BitReader, cbp: u8) -> Result<()> {
|
||||
let cbp_c = cbp >> 4;
|
||||
if cbp_c & 3 != 0 {
|
||||
for c in 0..2 {
|
||||
|
||||
@@ -128,7 +128,7 @@ pub fn mc_chroma(f: &Frame, c: usize, x: i32, y: i32, mv: [i16; 2], w: usize, h:
|
||||
let yi = y + (mv[1] as i32 >> 3);
|
||||
let shift1 = bd.min(12) - 8;
|
||||
let shift3 = 14 - bd;
|
||||
f.chroma_window(c, xi - 1, yi - 1, w + 3, h + 3, &mut sc.win, WIN);
|
||||
f.chroma_window(c, (xi - 1, yi - 1), (w + 3, h + 3), &mut sc.win, WIN);
|
||||
interpolate::<4>(&sc.win, &mut sc.tmp, fx, fy, &CHROMA_FILTER[fx], &CHROMA_FILTER[fy], w, h, shift1, shift3, out);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ pub fn put_bi(p0: &[i16], p1: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u1
|
||||
}
|
||||
|
||||
/// Explicit weighted prediction, one list (8-265). `o` is the offset already scaled to the bit depth.
|
||||
pub fn put_weighted_uni(p: &[i16], w: usize, h: usize, bd: u32, log2wd: u32, wt: i32, o: i32, dst: &mut [u16], ds: usize) {
|
||||
pub fn put_weighted_uni(p: &[i16], (w, h): (usize, usize), bd: u32, (log2wd, wt, o): (u32, i32, i32), dst: &mut [u16], ds: usize) {
|
||||
let max = (1i32 << bd) - 1;
|
||||
for r in 0..h {
|
||||
for c in 0..w {
|
||||
@@ -173,14 +173,9 @@ pub fn put_weighted_uni(p: &[i16], w: usize, h: usize, bd: u32, log2wd: u32, wt:
|
||||
pub fn put_weighted_bi(
|
||||
p0: &[i16],
|
||||
p1: &[i16],
|
||||
w: usize,
|
||||
h: usize,
|
||||
(w, h): (usize, usize),
|
||||
bd: u32,
|
||||
log2wd: u32,
|
||||
w0: i32,
|
||||
w1: i32,
|
||||
o0: i32,
|
||||
o1: i32,
|
||||
(log2wd, w0, w1, o0, o1): (u32, i32, i32, i32, i32),
|
||||
dst: &mut [u16],
|
||||
ds: usize,
|
||||
) {
|
||||
|
||||
+73
-63
@@ -36,9 +36,23 @@ fn same_motion(a: &MvField, b: &MvField) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Coding block and prediction block geometry (6.4.2): coding block (xc, yc, ncb), prediction block (xp, yp, w, h, part_idx).
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PbGeom {
|
||||
pub xc: i32,
|
||||
pub yc: i32,
|
||||
pub ncb: i32,
|
||||
pub xp: i32,
|
||||
pub yp: i32,
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
pub part_idx: usize,
|
||||
}
|
||||
|
||||
impl<'a> SliceDecoder<'a> {
|
||||
/// Prediction block availability (6.4.2).
|
||||
fn pb_avail(&self, xc: i32, yc: i32, ncb: i32, xp: i32, yp: i32, w: i32, h: i32, part_idx: usize, xn: i32, yn: i32) -> bool {
|
||||
fn pb_avail(&self, g: &PbGeom, xn: i32, yn: i32) -> bool {
|
||||
let PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx } = *g;
|
||||
let same_cb = xc <= xn && yc <= yn && xc + ncb > xn && yc + ncb > yn;
|
||||
let avail = if !same_cb {
|
||||
self.pic.zavail(xp, yp, xn, yn, self.slice_addr)
|
||||
@@ -59,27 +73,40 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
|
||||
/// Merge mode motion (8.5.3.2.2).
|
||||
pub fn derive_merge(
|
||||
&self,
|
||||
xc: i32,
|
||||
yc: i32,
|
||||
ncb: i32,
|
||||
xp0: i32,
|
||||
yp0: i32,
|
||||
w0: i32,
|
||||
h0: i32,
|
||||
part_idx0: usize,
|
||||
part: PartMode,
|
||||
merge_idx: usize,
|
||||
) -> MvField {
|
||||
pub fn derive_merge(&self, g: PbGeom, part: PartMode, merge_idx: usize) -> MvField {
|
||||
let (w0, h0) = (g.w, g.h);
|
||||
// With a parallel merge level above 4x4, an 8x8 coding block shares one candidate set.
|
||||
let g = if self.pps.log2_parallel_merge_level > 2 && g.ncb == 8 {
|
||||
PbGeom { xp: g.xc, yp: g.yc, w: g.ncb, h: g.ncb, part_idx: 0, ..g }
|
||||
} else {
|
||||
g
|
||||
};
|
||||
let max = self.sh.max_num_merge_cand as usize;
|
||||
let (list, n) = self.spatial_merge_candidates(&g, part);
|
||||
if merge_idx < n {
|
||||
return self.finish_merge(list[merge_idx], w0, h0);
|
||||
}
|
||||
let mut cands: Vec<MvField> = list[..n].to_vec();
|
||||
cands.extend(self.temporal_merge_candidate(g.xp, g.yp, g.w, g.h));
|
||||
if merge_idx < cands.len() {
|
||||
return self.finish_merge(cands[merge_idx], w0, h0);
|
||||
}
|
||||
self.combined_merge_candidates(&mut cands, max);
|
||||
if merge_idx < cands.len() {
|
||||
return self.finish_merge(cands[merge_idx], w0, h0);
|
||||
}
|
||||
self.zero_merge_candidates(&mut cands, merge_idx);
|
||||
self.finish_merge(cands[merge_idx], w0, h0)
|
||||
}
|
||||
|
||||
/// The spatial candidates A1, B1, B0, A0 and B2 (8.5.3.2.3): the candidates, and how many.
|
||||
fn spatial_merge_candidates(&self, g: &PbGeom, part: PartMode) -> ([MvField; 5], usize) {
|
||||
let PbGeom { xp, yp, w, h, part_idx, .. } = *g;
|
||||
let pml = self.pps.log2_parallel_merge_level;
|
||||
let (xp, yp, w, h, part_idx) = if pml > 2 && ncb == 8 { (xc, yc, ncb, ncb, 0) } else { (xp0, yp0, w0, h0, part_idx0) };
|
||||
let same_mer = |xn: i32, yn: i32| (xp >> pml) == (xn >> pml) && (yp >> pml) == (yn >> pml);
|
||||
let avail = |xn: i32, yn: i32| self.pb_avail(xc, yc, ncb, xp, yp, w, h, part_idx, xn, yn) && !same_mer(xn, yn);
|
||||
let avail = |xn: i32, yn: i32| self.pb_avail(g, xn, yn) && !same_mer(xn, yn);
|
||||
let mut list: [MvField; 5] = [MvField::default(); 5];
|
||||
let mut n = 0;
|
||||
let max = self.sh.max_num_merge_cand as usize;
|
||||
// spatial candidates (8.5.3.2.3)
|
||||
let pa1 = (xp - 1, yp + h - 1);
|
||||
let av_a1 = avail(pa1.0, pa1.1) && !(matches!(part, PartMode::PNx2N | PartMode::PnLx2N | PartMode::PnRx2N) && part_idx == 1);
|
||||
let m_a1 = if av_a1 { self.mv_at(pa1.0, pa1.1) } else { MvField::default() };
|
||||
@@ -126,10 +153,11 @@ impl<'a> SliceDecoder<'a> {
|
||||
list[n] = self.mv_at(pb2.0, pb2.1);
|
||||
n += 1;
|
||||
}
|
||||
if merge_idx < n {
|
||||
return self.finish_merge(list[merge_idx], w0, h0);
|
||||
}
|
||||
// temporal candidate
|
||||
(list, n)
|
||||
}
|
||||
|
||||
/// The temporal candidate (8.5.3.2.8): list 0 from the collocated picture, and list 1 for B slices.
|
||||
fn temporal_merge_candidate(&self, xp: i32, yp: i32, w: i32, h: i32) -> Option<MvField> {
|
||||
let mut col = MvField::default();
|
||||
let mut col_avail = false;
|
||||
if let Some(mv) = self.temporal(xp, yp, w, h, 0, 0) {
|
||||
@@ -144,30 +172,28 @@ impl<'a> SliceDecoder<'a> {
|
||||
col.ref_idx[1] = 0;
|
||||
col_avail = true;
|
||||
}
|
||||
let mut cands: Vec<MvField> = list[..n].to_vec();
|
||||
if col_avail {
|
||||
cands.push(col);
|
||||
}
|
||||
if merge_idx < cands.len() {
|
||||
return self.finish_merge(cands[merge_idx], w0, h0);
|
||||
}
|
||||
// combined bi-predictive candidates (8.5.3.2.4)
|
||||
col_avail.then_some(col)
|
||||
}
|
||||
|
||||
/// Combined bi-predictive candidates (8.5.3.2.4) from pairs of the original candidates.
|
||||
fn combined_merge_candidates(&self, cands: &mut Vec<MvField>, max: usize) {
|
||||
let num_orig = cands.len();
|
||||
if self.sh.is_b() && num_orig > 1 && num_orig < max {
|
||||
let mut comb = 0;
|
||||
while comb < num_orig * (num_orig - 1) && cands.len() < max {
|
||||
let (i0, i1) = COMB[comb];
|
||||
let (c0, c1) = (cands[i0], cands[i1]);
|
||||
if c0.pred(0) && c1.pred(1) && (self.ref_pic(0, c0.ref_idx[0]).poc != self.ref_pic(1, c1.ref_idx[1]).poc || c0.mv[0] != c1.mv[1]) {
|
||||
cands.push(MvField { mv: [c0.mv[0], c1.mv[1]], ref_idx: [c0.ref_idx[0], c1.ref_idx[1]] });
|
||||
}
|
||||
comb += 1;
|
||||
if !(self.sh.is_b() && num_orig > 1 && num_orig < max) {
|
||||
return;
|
||||
}
|
||||
let mut comb = 0;
|
||||
while comb < num_orig * (num_orig - 1) && cands.len() < max {
|
||||
let (i0, i1) = COMB[comb];
|
||||
let (c0, c1) = (cands[i0], cands[i1]);
|
||||
if c0.pred(0) && c1.pred(1) && (self.ref_pic(0, c0.ref_idx[0]).poc != self.ref_pic(1, c1.ref_idx[1]).poc || c0.mv[0] != c1.mv[1]) {
|
||||
cands.push(MvField { mv: [c0.mv[0], c1.mv[1]], ref_idx: [c0.ref_idx[0], c1.ref_idx[1]] });
|
||||
}
|
||||
comb += 1;
|
||||
}
|
||||
if merge_idx < cands.len() {
|
||||
return self.finish_merge(cands[merge_idx], w0, h0);
|
||||
}
|
||||
// zero candidates (8.5.3.2.5)
|
||||
}
|
||||
|
||||
/// Zero candidates (8.5.3.2.5) up to and including `merge_idx`.
|
||||
fn zero_merge_candidates(&self, cands: &mut Vec<MvField>, merge_idx: usize) {
|
||||
let num_ref = if self.sh.is_b() { self.sh.num_ref_idx[0].min(self.sh.num_ref_idx[1]) } else { self.sh.num_ref_idx[0] } as usize;
|
||||
let mut zero_idx = 0;
|
||||
while cands.len() <= merge_idx {
|
||||
@@ -175,7 +201,6 @@ impl<'a> SliceDecoder<'a> {
|
||||
cands.push(MvField { mv: [[0; 2]; 2], ref_idx: [r, if self.sh.is_b() { r } else { -1 }] });
|
||||
zero_idx += 1;
|
||||
}
|
||||
self.finish_merge(cands[merge_idx], w0, h0)
|
||||
}
|
||||
|
||||
fn finish_merge(&self, mut f: MvField, w: i32, h: i32) -> MvField {
|
||||
@@ -237,20 +262,8 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
|
||||
/// AMVP predictor (8.5.3.2.6, 8.5.3.2.7).
|
||||
pub fn derive_amvp(
|
||||
&self,
|
||||
xc: i32,
|
||||
yc: i32,
|
||||
ncb: i32,
|
||||
xp: i32,
|
||||
yp: i32,
|
||||
w: i32,
|
||||
h: i32,
|
||||
part_idx: usize,
|
||||
l: usize,
|
||||
ref_idx: usize,
|
||||
mvp_flag: usize,
|
||||
) -> [i16; 2] {
|
||||
pub fn derive_amvp(&self, g: PbGeom, l: usize, ref_idx: usize, mvp_flag: usize) -> [i16; 2] {
|
||||
let PbGeom { xp, yp, w, h, .. } = g;
|
||||
let target = &self.refs[l][ref_idx];
|
||||
let (tpoc, tlt) = (target.poc, target.long_term);
|
||||
let cur_poc = self.pic.poc;
|
||||
@@ -277,10 +290,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
pick.map(|(mv, r)| if r.poc != tpoc && !r.long_term && !tlt { scale_mv(mv, cur_poc - r.poc, cur_poc - tpoc) } else { mv })
|
||||
};
|
||||
let pa = [(xp - 1, yp + h), (xp - 1, yp + h - 1)];
|
||||
let av_a = [
|
||||
self.pb_avail(xc, yc, ncb, xp, yp, w, h, part_idx, pa[0].0, pa[0].1),
|
||||
self.pb_avail(xc, yc, ncb, xp, yp, w, h, part_idx, pa[1].0, pa[1].1),
|
||||
];
|
||||
let av_a = [self.pb_avail(&g, pa[0].0, pa[0].1), self.pb_avail(&g, pa[1].0, pa[1].1)];
|
||||
let is_scaled = av_a[0] || av_a[1];
|
||||
let mut a = None;
|
||||
for k in 0..2 {
|
||||
@@ -296,7 +306,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
}
|
||||
let pb = [(xp + w, yp - 1), (xp + w - 1, yp - 1), (xp - 1, yp - 1)];
|
||||
let av_b = [0, 1, 2].map(|k| self.pb_avail(xc, yc, ncb, xp, yp, w, h, part_idx, pb[k].0, pb[k].1));
|
||||
let av_b = [0, 1, 2].map(|k| self.pb_avail(&g, pb[k].0, pb[k].1));
|
||||
let mut b = None;
|
||||
for k in 0..3 {
|
||||
if av_b[k] && b.is_none() {
|
||||
|
||||
@@ -125,7 +125,7 @@ impl Frame {
|
||||
}
|
||||
|
||||
/// Copy a w x h window of chroma component `c` (0 = Cb, 1 = Cr) with edge replication.
|
||||
pub fn chroma_window(&self, c: usize, x0: i32, y0: i32, w: usize, h: usize, out: &mut [i16], os: usize) {
|
||||
pub fn chroma_window(&self, c: usize, (x0, y0): (i32, i32), (w, h): (usize, usize), out: &mut [i16], os: usize) {
|
||||
let (cw, ch) = (self.cwidth as i32, self.cheight as i32);
|
||||
let sh = self.log2_ctb - 1;
|
||||
let mask = (1i32 << sh) - 1;
|
||||
|
||||
+138
-106
@@ -7,6 +7,7 @@ use crate::cabac::{Cabac, Contexts, init_contexts};
|
||||
use crate::error::{Result, ensure, invalid};
|
||||
use crate::inter;
|
||||
use crate::intra::{self, Refs};
|
||||
use crate::mvpred::PbGeom;
|
||||
use crate::params::{Layout, Pps, Sps};
|
||||
use crate::picture::{FrameRef, RefPic};
|
||||
use crate::slice::{SliceHeader, SliceType};
|
||||
@@ -276,6 +277,30 @@ pub struct SliceDecoder<'a> {
|
||||
pub ctb_y: i32,
|
||||
}
|
||||
|
||||
/// Scaling process (8.6.3): dequantises the top-left `max_x` × `max_y` coefficients of the `n` × `n`
|
||||
/// block `coeffs` with scaling matrix `m` (flat when `None`).
|
||||
fn dequantize(coeffs: &mut [i32], n: usize, (max_x, max_y): (usize, usize), m: Option<&[u8]>, scale: i64, round: i64, bd_shift: u32) {
|
||||
for y in 0..=max_y {
|
||||
for x in 0..=max_x {
|
||||
let v = coeffs[y * n + x];
|
||||
if v != 0 {
|
||||
let f = m.map_or(16, |m| m[y * n + x] as i64);
|
||||
coeffs[y * n + x] = ((v as i64 * f * scale + round) >> bd_shift).clamp(-32768, 32767) as i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the `n` × `n` residual `coeffs` to the samples of `plane` at (x0, y0), clipped to 0..=`max`.
|
||||
fn add_residual(plane: &mut [u16], stride: usize, (x0, y0): (usize, usize), n: usize, coeffs: &[i32], max: i32) {
|
||||
for y in 0..n {
|
||||
let row = &mut plane[(y0 + y) * stride + x0..][..n];
|
||||
for (x, s) in row.iter_mut().enumerate() {
|
||||
*s = (*s as i32 + coeffs[y * n + x]).clamp(0, max) as u16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SliceDecoder<'a> {
|
||||
#[inline(always)]
|
||||
fn dd(&mut self, ctx: usize) -> u32 {
|
||||
@@ -678,7 +703,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
self.mark_edges(x0, y0, n, n, E_TU_V | E_PU_V, E_TU_H | E_PU_H);
|
||||
if skip {
|
||||
self.cu_intra = false;
|
||||
self.prediction_unit(x0, y0, n, x0, y0, n, n, 0, PartMode::P2Nx2N, true)?;
|
||||
self.prediction_unit(PbGeom { xc: x0, yc: y0, ncb: n, xp: x0, yp: y0, w: n, h: n, part_idx: 0 }, PartMode::P2Nx2N, true)?;
|
||||
self.finish_cu(x0, y0, n);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -707,7 +732,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
self.parse_intra_modes(x0, y0, n, part)?;
|
||||
self.max_trafo_depth = self.sps.max_th_depth_intra + self.intra_split as u32;
|
||||
self.transform_tree(x0, y0, x0, y0, log2, 0, 0, [true, true])?;
|
||||
self.transform_tree((x0, y0), (x0, y0), log2, 0, 0, [true, true])?;
|
||||
} else {
|
||||
self.intra_split = false;
|
||||
let mut merge2nx2n = false;
|
||||
@@ -724,7 +749,8 @@ impl<'a> SliceDecoder<'a> {
|
||||
PartMode::PNxN => &[(0, 0, h, h), (h, 0, h, h), (0, h, h, h), (h, h, h, h)],
|
||||
};
|
||||
for (i, &(px, py, pw, ph)) in parts.iter().enumerate() {
|
||||
let merge = self.prediction_unit(x0, y0, n, x0 + px, y0 + py, pw, ph, i, part, false)?;
|
||||
let merge =
|
||||
self.prediction_unit(PbGeom { xc: x0, yc: y0, ncb: n, xp: x0 + px, yp: y0 + py, w: pw, h: ph, part_idx: i }, part, false)?;
|
||||
if part == PartMode::P2Nx2N {
|
||||
merge2nx2n = merge;
|
||||
}
|
||||
@@ -738,7 +764,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
let root_cbf = if !merge2nx2n { self.dd(RQT_ROOT_CBF) == 1 } else { true };
|
||||
if root_cbf {
|
||||
self.max_trafo_depth = self.sps.max_th_depth_inter;
|
||||
self.transform_tree(x0, y0, x0, y0, log2, 0, 0, [true, true])?;
|
||||
self.transform_tree((x0, y0), (x0, y0), log2, 0, 0, [true, true])?;
|
||||
}
|
||||
}
|
||||
self.finish_cu(x0, y0, n);
|
||||
@@ -912,7 +938,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_tree(&mut self, x0: i32, y0: i32, xb: i32, yb: i32, log2: u32, depth: u32, blk_idx: u32, parent_cbf_c: [bool; 2]) -> Result<()> {
|
||||
fn transform_tree(&mut self, (x0, y0): (i32, i32), base: (i32, i32), log2: u32, depth: u32, blk_idx: u32, parent_cbf_c: [bool; 2]) -> Result<()> {
|
||||
let split =
|
||||
if log2 <= self.sps.log2_max_tb && log2 > self.sps.log2_min_tb && depth < self.max_trafo_depth && !(self.intra_split && depth == 0) {
|
||||
self.dd(SPLIT_TRANSFORM + (5 - log2 as usize)) == 1
|
||||
@@ -937,10 +963,10 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
if split {
|
||||
let h = 1 << (log2 - 1);
|
||||
self.transform_tree(x0, y0, x0, y0, log2 - 1, depth + 1, 0, cbf_c)?;
|
||||
self.transform_tree(x0 + h, y0, x0, y0, log2 - 1, depth + 1, 1, cbf_c)?;
|
||||
self.transform_tree(x0, y0 + h, x0, y0, log2 - 1, depth + 1, 2, cbf_c)?;
|
||||
self.transform_tree(x0 + h, y0 + h, x0, y0, log2 - 1, depth + 1, 3, cbf_c)?;
|
||||
self.transform_tree((x0, y0), (x0, y0), log2 - 1, depth + 1, 0, cbf_c)?;
|
||||
self.transform_tree((x0 + h, y0), (x0, y0), log2 - 1, depth + 1, 1, cbf_c)?;
|
||||
self.transform_tree((x0, y0 + h), (x0, y0), log2 - 1, depth + 1, 2, cbf_c)?;
|
||||
self.transform_tree((x0 + h, y0 + h), (x0, y0), log2 - 1, depth + 1, 3, cbf_c)?;
|
||||
return Ok(());
|
||||
}
|
||||
let cbf_luma =
|
||||
@@ -949,7 +975,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
if depth > 0 {
|
||||
self.mark_edges(x0, y0, n, n, E_TU_V, E_TU_H);
|
||||
}
|
||||
self.transform_unit(x0, y0, xb, yb, log2, blk_idx, cbf_luma, cbf_c)
|
||||
self.transform_unit((x0, y0), base, log2, blk_idx, cbf_luma, cbf_c)
|
||||
}
|
||||
|
||||
/// cu_qp_delta_abs / sign and QP update (7.3.8.14).
|
||||
@@ -979,7 +1005,15 @@ impl<'a> SliceDecoder<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn transform_unit(&mut self, x0: i32, y0: i32, xb: i32, yb: i32, log2: u32, blk_idx: u32, cbf_luma: bool, cbf_c: [bool; 2]) -> Result<()> {
|
||||
fn transform_unit(
|
||||
&mut self,
|
||||
(x0, y0): (i32, i32),
|
||||
(xb, yb): (i32, i32),
|
||||
log2: u32,
|
||||
blk_idx: u32,
|
||||
cbf_luma: bool,
|
||||
cbf_c: [bool; 2],
|
||||
) -> Result<()> {
|
||||
let n = 1i32 << log2;
|
||||
let chroma_here = log2 > 2 || blk_idx == 3;
|
||||
let cbf_chroma = cbf_c[0] || cbf_c[1];
|
||||
@@ -1284,7 +1318,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
max_y = max_y.max(yc);
|
||||
}
|
||||
}
|
||||
self.reconstruct(x0, y0, log2, c, qp, ts, max_x, max_y)
|
||||
self.reconstruct((x0, y0), log2, c, qp, ts, (max_x, max_y))
|
||||
}
|
||||
|
||||
fn coeff_abs_level_remaining(&mut self, rice: u32) -> Result<i32> {
|
||||
@@ -1303,7 +1337,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
|
||||
/// Scaling, transform and addition to the prediction.
|
||||
fn reconstruct(&mut self, x0: i32, y0: i32, log2: u32, c: usize, qp: i32, ts: bool, max_x: usize, max_y: usize) -> Result<()> {
|
||||
fn reconstruct(&mut self, (x0, y0): (i32, i32), log2: u32, c: usize, qp: i32, ts: bool, (max_x, max_y): (usize, usize)) -> Result<()> {
|
||||
let n = 1usize << log2;
|
||||
let bd = if c == 0 { self.pic.bd_y } else { self.pic.bd_c };
|
||||
let coeffs = &mut self.coeffs[..n * n];
|
||||
@@ -1321,15 +1355,7 @@ impl<'a> SliceDecoder<'a> {
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
for y in 0..=max_y {
|
||||
for x in 0..=max_x {
|
||||
let v = coeffs[y * n + x];
|
||||
if v != 0 {
|
||||
let f = m.map_or(16, |m| m[y * n + x] as i64);
|
||||
coeffs[y * n + x] = ((v as i64 * f * scale + round) >> bd_shift).clamp(-32768, 32767) as i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
dequantize(coeffs, n, (max_x, max_y), m, scale, round, bd_shift);
|
||||
let bd_shift = 20 - bd;
|
||||
if ts {
|
||||
transform::transform_skip(coeffs, n, bd_shift);
|
||||
@@ -1345,106 +1371,112 @@ impl<'a> SliceDecoder<'a> {
|
||||
let _ = dc_only;
|
||||
let (stride, max) = (if c == 0 { self.pic.width } else { self.pic.cwidth }, (1i32 << bd) - 1);
|
||||
let plane = &mut self.pic.planes[c];
|
||||
for y in 0..n {
|
||||
let row = &mut plane[(y0 as usize + y) * stride + x0 as usize..][..n];
|
||||
for (x, s) in row.iter_mut().enumerate() {
|
||||
*s = (*s as i32 + coeffs[y * n + x]).clamp(0, max) as u16;
|
||||
}
|
||||
}
|
||||
add_residual(plane, stride, (x0 as usize, y0 as usize), n, coeffs, max);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// prediction_unit(): parse, derive motion, store it and predict. Returns merge_flag.
|
||||
fn prediction_unit(
|
||||
&mut self,
|
||||
xc: i32,
|
||||
yc: i32,
|
||||
ncb: i32,
|
||||
xp: i32,
|
||||
yp: i32,
|
||||
w: i32,
|
||||
h: i32,
|
||||
part_idx: usize,
|
||||
part: PartMode,
|
||||
skip: bool,
|
||||
) -> Result<bool> {
|
||||
fn prediction_unit(&mut self, g: PbGeom, part: PartMode, skip: bool) -> Result<bool> {
|
||||
let merge = if skip { true } else { self.dd(MERGE_FLAG) == 1 };
|
||||
let mvf = if merge {
|
||||
let idx = if self.sh.max_num_merge_cand > 1 {
|
||||
let mut i = 0;
|
||||
if self.dd(MERGE_IDX) == 1 {
|
||||
i = 1;
|
||||
while i < self.sh.max_num_merge_cand - 1 && self.bypass() == 1 {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
i
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.derive_merge(xc, yc, ncb, xp, yp, w, h, part_idx, part, idx as usize)
|
||||
let idx = self.merge_idx();
|
||||
self.derive_merge(g, part, idx as usize)
|
||||
} else {
|
||||
let pred_idc = if self.sh.is_b() {
|
||||
if w + h != 12 {
|
||||
let depth = self.pic.blk_at(xc, yc).depth as usize;
|
||||
if self.dd(INTER_PRED_IDC + depth) == 1 { 2 } else { self.dd(INTER_PRED_IDC + 4) }
|
||||
} else {
|
||||
self.dd(INTER_PRED_IDC + 4)
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let mut ref_idx = [-1i8; 2];
|
||||
let mut mvd = [[0i32; 2]; 2];
|
||||
let mut mvp_flag = [0u32; 2];
|
||||
for l in 0..2 {
|
||||
let used = if l == 0 { pred_idc != 1 } else { pred_idc != 0 };
|
||||
if !used {
|
||||
continue;
|
||||
}
|
||||
let nref = self.sh.num_ref_idx[l];
|
||||
let mut ri = 0;
|
||||
if nref > 1 {
|
||||
while ri < nref - 1 {
|
||||
let bin = if ri < 2 { self.dd(REF_IDX + ri as usize) } else { self.bypass() };
|
||||
if bin == 0 {
|
||||
break;
|
||||
}
|
||||
ri += 1;
|
||||
}
|
||||
}
|
||||
ref_idx[l] = ri as i8;
|
||||
if l == 1 && self.sh.mvd_l1_zero && pred_idc == 2 {
|
||||
mvd[1] = [0, 0];
|
||||
} else {
|
||||
mvd[l] = self.mvd_coding()?;
|
||||
}
|
||||
mvp_flag[l] = self.dd(MVP_FLAG);
|
||||
}
|
||||
let mut f = MvField { mv: [[0; 2]; 2], ref_idx };
|
||||
for l in 0..2 {
|
||||
if ref_idx[l] < 0 {
|
||||
continue;
|
||||
}
|
||||
let mvp = self.derive_amvp(xc, yc, ncb, xp, yp, w, h, part_idx, l, ref_idx[l] as usize, mvp_flag[l] as usize);
|
||||
// mvLX = (mvp + mvd) wrapped to 16 bits (8-94)
|
||||
f.mv[l] = [(mvp[0] as i32 + mvd[l][0]) as i16, (mvp[1] as i32 + mvd[l][1]) as i16];
|
||||
}
|
||||
f
|
||||
self.amvp_motion(g)?
|
||||
};
|
||||
for l in 0..2 {
|
||||
if mvf.ref_idx[l] >= 0 {
|
||||
ensure!((mvf.ref_idx[l] as usize) < self.refs[l].len(), "reference index out of range");
|
||||
}
|
||||
}
|
||||
// store motion
|
||||
self.store_motion(g.xp, g.yp, g.w, g.h, mvf);
|
||||
self.motion_compensate(g.xp, g.yp, g.w as usize, g.h as usize, &mvf);
|
||||
Ok(merge)
|
||||
}
|
||||
|
||||
/// merge_idx(): the merge candidate index, truncated unary (0 when there is one candidate).
|
||||
fn merge_idx(&mut self) -> u32 {
|
||||
if self.sh.max_num_merge_cand <= 1 {
|
||||
return 0;
|
||||
}
|
||||
let mut i = 0;
|
||||
if self.dd(MERGE_IDX) == 1 {
|
||||
i = 1;
|
||||
while i < self.sh.max_num_merge_cand - 1 && self.bypass() == 1 {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
i
|
||||
}
|
||||
|
||||
/// inter_pred_idc: 0 = list 0, 1 = list 1, 2 = both.
|
||||
fn inter_pred_idc(&mut self, xc: i32, yc: i32, w: i32, h: i32) -> u32 {
|
||||
if !self.sh.is_b() {
|
||||
return 0;
|
||||
}
|
||||
if w + h != 12 {
|
||||
let depth = self.pic.blk_at(xc, yc).depth as usize;
|
||||
if self.dd(INTER_PRED_IDC + depth) == 1 { 2 } else { self.dd(INTER_PRED_IDC + 4) }
|
||||
} else {
|
||||
self.dd(INTER_PRED_IDC + 4)
|
||||
}
|
||||
}
|
||||
|
||||
/// ref_idx_lX: truncated unary, the first two bins context-coded.
|
||||
fn ref_idx(&mut self, l: usize) -> u32 {
|
||||
let nref = self.sh.num_ref_idx[l];
|
||||
let mut ri = 0;
|
||||
if nref > 1 {
|
||||
while ri < nref - 1 {
|
||||
let bin = if ri < 2 { self.dd(REF_IDX + ri as usize) } else { self.bypass() };
|
||||
if bin == 0 {
|
||||
break;
|
||||
}
|
||||
ri += 1;
|
||||
}
|
||||
}
|
||||
ri
|
||||
}
|
||||
|
||||
/// The AMVP branch: parse each used list's reference index, mvd and predictor flag, then add
|
||||
/// every mvd to its predictor.
|
||||
fn amvp_motion(&mut self, g: PbGeom) -> Result<MvField> {
|
||||
let pred_idc = self.inter_pred_idc(g.xc, g.yc, g.w, g.h);
|
||||
let mut ref_idx = [-1i8; 2];
|
||||
let mut mvd = [[0i32; 2]; 2];
|
||||
let mut mvp_flag = [0u32; 2];
|
||||
for l in 0..2 {
|
||||
let used = if l == 0 { pred_idc != 1 } else { pred_idc != 0 };
|
||||
if !used {
|
||||
continue;
|
||||
}
|
||||
ref_idx[l] = self.ref_idx(l) as i8;
|
||||
if l == 1 && self.sh.mvd_l1_zero && pred_idc == 2 {
|
||||
mvd[1] = [0, 0];
|
||||
} else {
|
||||
mvd[l] = self.mvd_coding()?;
|
||||
}
|
||||
mvp_flag[l] = self.dd(MVP_FLAG);
|
||||
}
|
||||
let mut f = MvField { mv: [[0; 2]; 2], ref_idx };
|
||||
for l in 0..2 {
|
||||
if ref_idx[l] < 0 {
|
||||
continue;
|
||||
}
|
||||
let mvp = self.derive_amvp(g, l, ref_idx[l] as usize, mvp_flag[l] as usize);
|
||||
// mvLX = (mvp + mvd) wrapped to 16 bits (8-94)
|
||||
f.mv[l] = [(mvp[0] as i32 + mvd[l][0]) as i16, (mvp[1] as i32 + mvd[l][1]) as i16];
|
||||
}
|
||||
Ok(f)
|
||||
}
|
||||
|
||||
/// Writes a prediction block's motion into the picture's 4x4 motion field.
|
||||
fn store_motion(&mut self, xp: i32, yp: i32, w: i32, h: i32, mvf: MvField) {
|
||||
let w4 = self.pic.w4;
|
||||
for by in (yp >> 2)..((yp + h) >> 2) {
|
||||
let o = by as usize * w4;
|
||||
self.pic.mvf[o + (xp >> 2) as usize..o + ((xp + w) >> 2) as usize].fill(mvf);
|
||||
}
|
||||
self.motion_compensate(xp, yp, w as usize, h as usize, &mvf);
|
||||
Ok(merge)
|
||||
}
|
||||
|
||||
fn mvd_coding(&mut self) -> Result<[i32; 2]> {
|
||||
@@ -1524,11 +1556,11 @@ impl<'a> SliceDecoder<'a> {
|
||||
if bi {
|
||||
let (w0, o0) = get(0);
|
||||
let (w1, o1) = get(1);
|
||||
inter::put_weighted_bi(&self.pred0, &self.pred1, bw, bh, bd, log2wd, w0, w1, o0, o1, dst, stride);
|
||||
inter::put_weighted_bi(&self.pred0, &self.pred1, (bw, bh), bd, (log2wd, w0, w1, o0, o1), dst, stride);
|
||||
} else {
|
||||
let l = if lists[0] { 0 } else { 1 };
|
||||
let (wt, o) = get(l);
|
||||
inter::put_weighted_uni(&self.pred0, bw, bh, bd, log2wd, wt, o, dst, stride);
|
||||
inter::put_weighted_uni(&self.pred0, (bw, bh), bd, (log2wd, wt, o), dst, stride);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
-31
@@ -802,6 +802,7 @@ impl Chart {
|
||||
/// The (min, max, major unit) of an axis over the data range [lo, hi]: `fmin`/`fmax`/`major`
|
||||
/// where the chart fixes them; otherwise Office's automatic limits, rounded out to the smallest
|
||||
/// 1-2-5 unit giving at most `max_intervals` intervals:
|
||||
/// The data range [lo, hi] an axis covers, clamped to ±1e100; (0, 1) without finite values.
|
||||
///
|
||||
/// - **Maximum:** `hi + (hi − lo)/20` (with `headroom`), rounded up to the unit.
|
||||
/// - **Minimum:** zero when every value is ≥ 0 and `lo` is below 5/6 of `hi`; otherwise
|
||||
@@ -809,22 +810,14 @@ impl Chart {
|
||||
/// near them.
|
||||
/// - All-negative values mirror this: the axis ends at zero when `hi` is above 5/6 of `lo`.
|
||||
/// Mixed signs get headroom at both ends.
|
||||
pub fn axis_scale(
|
||||
lo: f64,
|
||||
hi: f64,
|
||||
fmin: Option<f64>,
|
||||
fmax: Option<f64>,
|
||||
major: Option<f64>,
|
||||
max_intervals: usize,
|
||||
headroom: bool,
|
||||
) -> (f64, f64, f64) {
|
||||
// Values past ±1e100 aren't axis values; clamping keeps the arithmetic finite.
|
||||
let fin = |v: Option<f64>| v.filter(|v| v.is_finite()).map(|v| v.clamp(-1e100, 1e100));
|
||||
let (fmin, fmax) = (fin(fmin), fin(fmax));
|
||||
let (lo, hi) = if lo.is_finite() && hi.is_finite() { (lo.min(hi).clamp(-1e100, 1e100), lo.max(hi).clamp(-1e100, 1e100)) } else { (0.0, 1.0) };
|
||||
// Office's automatic limits ("How chart axis limits are determined", as Jon Peltier
|
||||
// summarises it): 5 % of the data range past the data, and zero when the values don't
|
||||
// sit within the top sixth of their largest magnitude. A single value is measured from zero.
|
||||
fn axis_data_range(lo: f64, hi: f64) -> (f64, f64) {
|
||||
if lo.is_finite() && hi.is_finite() { (lo.min(hi).clamp(-1e100, 1e100), lo.max(hi).clamp(-1e100, 1e100)) } else { (0.0, 1.0) }
|
||||
}
|
||||
|
||||
/// Office's automatic (min, max) of the data range [lo, hi]. "How chart axis limits are determined",
|
||||
/// as Jon Peltier summarises it: 5 % of the data range past the data, and zero when the values don't
|
||||
/// sit within the top sixth of their largest magnitude. A single value is measured from zero.
|
||||
fn auto_limits(lo: f64, hi: f64, headroom: bool) -> (f64, f64) {
|
||||
let flat = hi - lo <= 1e-12 * lo.abs().max(hi.abs());
|
||||
let pad = if !headroom {
|
||||
0.0
|
||||
@@ -835,21 +828,23 @@ pub fn axis_scale(
|
||||
};
|
||||
let auto_lo = if lo >= 0.0 && (flat || lo < hi * 5.0 / 6.0) { 0.0 } else { lo - pad };
|
||||
let auto_hi = if hi <= 0.0 && (flat || hi > lo * 5.0 / 6.0) { 0.0 } else { hi + pad };
|
||||
let mut lo = fmin.unwrap_or(auto_lo);
|
||||
let mut hi = fmax.unwrap_or(auto_hi);
|
||||
let mag = lo.abs().max(hi.abs());
|
||||
// Relative to the data's magnitude, so tiny values don't count as flat; all-zero falls back to 1.
|
||||
let unit = if mag == 0.0 { 1.0 } else { mag };
|
||||
if hi - lo <= 1e-12 * mag {
|
||||
// A fixed limit on the far side of the data: the automatic end gives way.
|
||||
match (fmin, fmax) {
|
||||
(None, Some(_)) => lo = if hi > 0.0 { 0.0 } else { hi - hi.abs().max(unit) },
|
||||
(Some(_), None) if lo < 0.0 => hi = 0.0,
|
||||
_ => hi = lo + unit,
|
||||
}
|
||||
(auto_lo, auto_hi)
|
||||
}
|
||||
|
||||
/// The ends of a span too small to show: with one fixed limit on the far side of the data, the
|
||||
/// automatic end gives way; otherwise the span is one `unit` wide.
|
||||
fn flat_span_ends(fmin: Option<f64>, fmax: Option<f64>, lo: f64, hi: f64, unit: f64) -> (f64, f64) {
|
||||
match (fmin, fmax) {
|
||||
(None, Some(_)) => (if hi > 0.0 { 0.0 } else { hi - hi.abs().max(unit) }, hi),
|
||||
(Some(_), None) if lo < 0.0 => (lo, 0.0),
|
||||
_ => (lo, lo + unit),
|
||||
}
|
||||
let n = max_intervals.clamp(1, 50) as f64;
|
||||
let step = match fin(major).filter(|m| *m > 0.0 && (hi - lo) / m <= 1000.0) {
|
||||
}
|
||||
|
||||
/// The major step: `major` when it gives at most 1000 intervals, else the smallest 1-2-5 step
|
||||
/// that fits `n` intervals over [lo, hi].
|
||||
fn axis_step(major: Option<f64>, lo: f64, hi: f64, n: f64, unit: f64) -> f64 {
|
||||
match major.filter(|m| *m > 0.0 && (hi - lo) / m <= 1000.0) {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
let mut step = 10f64.powf(((hi - lo) / n).log10().floor());
|
||||
@@ -865,7 +860,32 @@ pub fn axis_scale(
|
||||
}
|
||||
step
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis_scale(
|
||||
lo: f64,
|
||||
hi: f64,
|
||||
fmin: Option<f64>,
|
||||
fmax: Option<f64>,
|
||||
major: Option<f64>,
|
||||
max_intervals: usize,
|
||||
headroom: bool,
|
||||
) -> (f64, f64, f64) {
|
||||
// Values past ±1e100 aren't axis values; clamping keeps the arithmetic finite.
|
||||
let fin = |v: Option<f64>| v.filter(|v| v.is_finite()).map(|v| v.clamp(-1e100, 1e100));
|
||||
let (fmin, fmax) = (fin(fmin), fin(fmax));
|
||||
let (lo, hi) = axis_data_range(lo, hi);
|
||||
let (auto_lo, auto_hi) = auto_limits(lo, hi, headroom);
|
||||
let (mut lo, mut hi) = (fmin.unwrap_or(auto_lo), fmax.unwrap_or(auto_hi));
|
||||
let mag = lo.abs().max(hi.abs());
|
||||
// Relative to the data's magnitude, so tiny values don't count as flat; all-zero falls back to 1.
|
||||
let unit = if mag == 0.0 { 1.0 } else { mag };
|
||||
if hi - lo <= 1e-12 * mag {
|
||||
(lo, hi) = flat_span_ends(fmin, fmax, lo, hi, unit);
|
||||
}
|
||||
let n = max_intervals.clamp(1, 50) as f64;
|
||||
let step = axis_step(fin(major), lo, hi, n, unit);
|
||||
let min = fmin.unwrap_or((lo / step + 1e-9).floor() * step);
|
||||
let max = fmax.unwrap_or((hi / step - 1e-9).ceil() * step).max(min + step);
|
||||
(min, max, step)
|
||||
|
||||
+43
-31
@@ -576,6 +576,44 @@ pub fn text_body(imp: &mut Imp, part: &Part, tx: &El) -> TextBody {
|
||||
|
||||
const MAX_PARAGRAPH_RUNS: usize = 10_000;
|
||||
|
||||
/// The run properties of `e`'s `rPr` child, or the defaults.
|
||||
fn run_props(imp: &mut Imp, part: &Part, e: &El) -> RunProps {
|
||||
e.child("rPr").map(|r| rpr(imp, part, r)).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Appends the runs of an `a:r` text, where a vertical tab or line feed is a line break, as it
|
||||
/// lays out and as export writes it.
|
||||
fn push_text_runs(para: &mut Paragraph, props: RunProps, text: &str) {
|
||||
for (i, piece) in text.split(['\u{b}', '\n']).enumerate() {
|
||||
if para.runs.len() >= MAX_PARAGRAPH_RUNS {
|
||||
break;
|
||||
}
|
||||
if i > 0 {
|
||||
para.runs.push(Run { text: String::new(), props: props.clone(), kind: RunKind::Break });
|
||||
}
|
||||
if !piece.is_empty() {
|
||||
if para.runs.len() >= MAX_PARAGRAPH_RUNS {
|
||||
break;
|
||||
}
|
||||
para.runs.push(Run { text: piece.to_string(), props: props.clone(), kind: RunKind::Text });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends an Office Math (a14:m) run: its plain text and its OMML.
|
||||
fn push_math_run(para: &mut Paragraph, part: &Part, e: &El) {
|
||||
if para.runs.len() < MAX_PARAGRAPH_RUNS {
|
||||
let mut text = String::new();
|
||||
let mut ts = vec![];
|
||||
e.find_all("t", &mut ts);
|
||||
for t in ts {
|
||||
text.push_str(&t.text());
|
||||
}
|
||||
let omml = e.elements().map(|c| part.keep_inner(c)).collect::<String>();
|
||||
para.runs.push(Run { text, props: RunProps::default(), kind: RunKind::Math { omml } });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paragraph(imp: &mut Imp, part: &Part, p: &El) -> Paragraph {
|
||||
let mut para = Paragraph::default();
|
||||
for e in p.elements() {
|
||||
@@ -589,50 +627,24 @@ pub fn paragraph(imp: &mut Imp, part: &Part, p: &El) -> Paragraph {
|
||||
}
|
||||
}
|
||||
"r" => {
|
||||
let props = e.child("rPr").map(|r| rpr(imp, part, r)).unwrap_or_default();
|
||||
let props = run_props(imp, part, e);
|
||||
let text: String = e.child("t").map(|t| t.text()).unwrap_or_default();
|
||||
// A vertical tab or line feed inside text is a line break, as it lays out and as export writes it.
|
||||
for (i, piece) in text.split(['\u{b}', '\n']).enumerate() {
|
||||
if para.runs.len() >= MAX_PARAGRAPH_RUNS {
|
||||
break;
|
||||
}
|
||||
if i > 0 {
|
||||
para.runs.push(Run { text: String::new(), props: props.clone(), kind: RunKind::Break });
|
||||
}
|
||||
if !piece.is_empty() {
|
||||
if para.runs.len() >= MAX_PARAGRAPH_RUNS {
|
||||
break;
|
||||
}
|
||||
para.runs.push(Run { text: piece.to_string(), props: props.clone(), kind: RunKind::Text });
|
||||
}
|
||||
}
|
||||
push_text_runs(&mut para, props, &text);
|
||||
}
|
||||
"br" => {
|
||||
if para.runs.len() < MAX_PARAGRAPH_RUNS {
|
||||
let props = e.child("rPr").map(|r| rpr(imp, part, r)).unwrap_or_default();
|
||||
let props = run_props(imp, part, e);
|
||||
para.runs.push(Run { text: String::new(), props, kind: RunKind::Break });
|
||||
}
|
||||
}
|
||||
"fld" => {
|
||||
if para.runs.len() < MAX_PARAGRAPH_RUNS {
|
||||
let props = e.child("rPr").map(|r| rpr(imp, part, r)).unwrap_or_default();
|
||||
let props = run_props(imp, part, e);
|
||||
let text = e.child("t").map(|t| t.text()).unwrap_or_default();
|
||||
para.runs.push(Run { text, props, kind: RunKind::Field { field: e.attr("type").unwrap_or("").to_string() } });
|
||||
}
|
||||
}
|
||||
"m" => {
|
||||
if para.runs.len() < MAX_PARAGRAPH_RUNS {
|
||||
// Office Math (a14:m) — keep the OMML and its plain text.
|
||||
let mut text = String::new();
|
||||
let mut ts = vec![];
|
||||
e.find_all("t", &mut ts);
|
||||
for t in ts {
|
||||
text.push_str(&t.text());
|
||||
}
|
||||
let omml = e.elements().map(|c| part.keep_inner(c)).collect::<String>();
|
||||
para.runs.push(Run { text, props: RunProps::default(), kind: RunKind::Math { omml } });
|
||||
}
|
||||
}
|
||||
"m" => push_math_run(&mut para, part, e),
|
||||
"endParaRPr" => para.end_props = rpr(imp, part, e),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
+114
-74
@@ -739,84 +739,19 @@ fn keyboard(ctx: &egui::Context, show: &mut Show, doc: &Presentation, now: f64,
|
||||
let mut keep = true;
|
||||
let mut actions = vec![];
|
||||
for (k, m) in keys {
|
||||
use egui::Key::*;
|
||||
let presenter_grid = presenter && show.presenter_grid;
|
||||
let grid_open = if presenter { show.presenter_grid } else { show.grid };
|
||||
if grid_open && !matches!(k, Escape | Minus) {
|
||||
let handled = if grid_open(show, presenter) && !matches!(k, egui::Key::Escape | egui::Key::Minus) {
|
||||
// See All Slides takes no other keys until a slide is chosen.
|
||||
if k == G && plain(m) {
|
||||
if presenter_grid {
|
||||
show.presenter_grid = false;
|
||||
} else {
|
||||
show.grid = false;
|
||||
}
|
||||
if k == egui::Key::G && plain(m) {
|
||||
close_grid(show, presenter);
|
||||
true
|
||||
} else {
|
||||
continue;
|
||||
false
|
||||
}
|
||||
} else {
|
||||
match k {
|
||||
Escape | Minus => {
|
||||
let cancelled = k == Escape && show.clear_goto();
|
||||
if !cancelled && !show.back_out(presenter) {
|
||||
keep = false;
|
||||
}
|
||||
}
|
||||
ArrowRight | ArrowDown | Space | PageDown | N => {
|
||||
if show.ended {
|
||||
keep = false;
|
||||
} else {
|
||||
actions.push(show.state.next(doc));
|
||||
}
|
||||
}
|
||||
Enter => {
|
||||
if !show.goto.is_empty() {
|
||||
if let Some(i) = show.take_goto()
|
||||
&& i < doc.slides.len()
|
||||
{
|
||||
actions.push(show.state.goto(doc, i));
|
||||
}
|
||||
} else if show.ended {
|
||||
keep = false;
|
||||
} else {
|
||||
actions.push(show.state.next(doc));
|
||||
}
|
||||
}
|
||||
Backspace if !show.goto.is_empty() => {
|
||||
show.goto.pop();
|
||||
}
|
||||
ArrowLeft | ArrowUp | Backspace | PageUp | P if !(k == P && m.command) => {
|
||||
show.ended = false;
|
||||
actions.push(show.state.prev(doc));
|
||||
}
|
||||
P if m.command => show.set_pen(!show.pen),
|
||||
L if m.command => show.set_laser(!show.laser),
|
||||
A if m.command => show.arrow(),
|
||||
Home => {
|
||||
if let Some(i) = show.state.first_index(doc) {
|
||||
actions.push(show.state.goto(doc, i));
|
||||
}
|
||||
}
|
||||
End => {
|
||||
if let Some(i) = show.state.last_index(doc) {
|
||||
actions.push(show.state.goto(doc, i));
|
||||
}
|
||||
}
|
||||
B | Period => toggle_blank(show, Color32::BLACK),
|
||||
W | Comma => toggle_blank(show, Color32::WHITE),
|
||||
E => show.strokes.clear(),
|
||||
G if plain(m) => show.open_grid(presenter),
|
||||
Z if plain(m) => show.zoom_key(),
|
||||
T if plain(m) => show.timer.toggle(now),
|
||||
R if plain(m) => show.timer.reset(now),
|
||||
OpenBracket if plain(m) => show.nudge_notes(-NOTES_STEP),
|
||||
CloseBracket if plain(m) => show.nudge_notes(NOTES_STEP),
|
||||
Num0 | Num1 | Num2 | Num3 | Num4 | Num5 | Num6 | Num7 | Num8 | Num9 => {
|
||||
if let Some(d) = k.name().chars().next() {
|
||||
show.push_slide_number(d, now);
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
navigation_key(show, doc, k, m, presenter, &mut keep, &mut actions) || tool_key(show, k, m, now, presenter)
|
||||
};
|
||||
if !handled {
|
||||
continue;
|
||||
}
|
||||
// The same key must not also activate a focused presenter button or be handled again
|
||||
// by an embedded presenter viewport.
|
||||
@@ -831,6 +766,111 @@ fn keyboard(ctx: &egui::Context, show: &mut Show, doc: &Presentation, now: f64,
|
||||
keep
|
||||
}
|
||||
|
||||
/// Whether the grid that the presenter key G opens is showing in this viewport.
|
||||
fn grid_open(show: &Show, presenter: bool) -> bool {
|
||||
if presenter { show.presenter_grid } else { show.grid }
|
||||
}
|
||||
|
||||
/// Closes the grid that G opens in this viewport.
|
||||
fn close_grid(show: &mut Show, presenter: bool) {
|
||||
if presenter && show.presenter_grid {
|
||||
show.presenter_grid = false;
|
||||
} else {
|
||||
show.grid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Keys that move through the show or end it. False when `k` isn't one of them.
|
||||
fn navigation_key(
|
||||
show: &mut Show,
|
||||
doc: &Presentation,
|
||||
k: egui::Key,
|
||||
m: egui::Modifiers,
|
||||
presenter: bool,
|
||||
keep: &mut bool,
|
||||
actions: &mut Vec<ShowAction>,
|
||||
) -> bool {
|
||||
use egui::Key::*;
|
||||
match k {
|
||||
Escape | Minus => {
|
||||
let cancelled = k == Escape && show.clear_goto();
|
||||
if !cancelled && !show.back_out(presenter) {
|
||||
*keep = false;
|
||||
}
|
||||
}
|
||||
ArrowRight | ArrowDown | Space | PageDown | N => {
|
||||
if show.ended {
|
||||
*keep = false;
|
||||
} else {
|
||||
actions.push(show.state.next(doc));
|
||||
}
|
||||
}
|
||||
Enter => {
|
||||
if !show.goto.is_empty() {
|
||||
if let Some(i) = show.take_goto()
|
||||
&& i < doc.slides.len()
|
||||
{
|
||||
actions.push(show.state.goto(doc, i));
|
||||
}
|
||||
} else if show.ended {
|
||||
*keep = false;
|
||||
} else {
|
||||
actions.push(show.state.next(doc));
|
||||
}
|
||||
}
|
||||
Backspace if !show.goto.is_empty() => {
|
||||
show.goto.pop();
|
||||
}
|
||||
ArrowLeft | ArrowUp | Backspace | PageUp => {
|
||||
show.ended = false;
|
||||
actions.push(show.state.prev(doc));
|
||||
}
|
||||
P if m.command => show.set_pen(!show.pen),
|
||||
P => {
|
||||
show.ended = false;
|
||||
actions.push(show.state.prev(doc));
|
||||
}
|
||||
Home => {
|
||||
if let Some(i) = show.state.first_index(doc) {
|
||||
actions.push(show.state.goto(doc, i));
|
||||
}
|
||||
}
|
||||
End => {
|
||||
if let Some(i) = show.state.last_index(doc) {
|
||||
actions.push(show.state.goto(doc, i));
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Pen, laser, blanking, zoom, the timer, note size and typed slide numbers. False when `k`
|
||||
/// isn't one of them.
|
||||
fn tool_key(show: &mut Show, k: egui::Key, m: egui::Modifiers, now: f64, presenter: bool) -> bool {
|
||||
use egui::Key::*;
|
||||
match k {
|
||||
L if m.command => show.set_laser(!show.laser),
|
||||
A if m.command => show.arrow(),
|
||||
B | Period => toggle_blank(show, Color32::BLACK),
|
||||
W | Comma => toggle_blank(show, Color32::WHITE),
|
||||
E => show.strokes.clear(),
|
||||
G if plain(m) => show.open_grid(presenter),
|
||||
Z if plain(m) => show.zoom_key(),
|
||||
T if plain(m) => show.timer.toggle(now),
|
||||
R if plain(m) => show.timer.reset(now),
|
||||
OpenBracket if plain(m) => show.nudge_notes(-NOTES_STEP),
|
||||
CloseBracket if plain(m) => show.nudge_notes(NOTES_STEP),
|
||||
Num0 | Num1 | Num2 | Num3 | Num4 | Num5 | Num6 | Num7 | Num8 | Num9 => {
|
||||
if let Some(d) = k.name().chars().next() {
|
||||
show.push_slide_number(d, now);
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Draw and advance the active show, handling hidden frames, timing, input, transitions, and
|
||||
/// presenter/reading presentation.
|
||||
pub fn ui(app: &mut SlideApp, ui: &mut Ui) {
|
||||
|
||||
@@ -58,9 +58,15 @@
|
||||
<Component Id="DeckcraftApp" Guid="D87EA444-41A4-4C19-922E-66D1A29461FF">
|
||||
<File Id="DeckcraftNotice" Source="$(var.BinDir)\NOTICE" KeyPath="no" />
|
||||
<File Id="DeckcraftExe" Source="$(var.BinDir)\deckcraft.exe" KeyPath="yes">
|
||||
<!-- No Icon here, so the shortcut shows the icon embedded in deckcraft.exe (build.rs). An
|
||||
MSI Icon is cached under %APPDATA%\Microsoft\Installer\{ProductCode}\, and every
|
||||
update is a major upgrade with a new ProductCode that deletes the old folder. A
|
||||
taskbar pin is a copy of this shortcut that the update never touches, so it kept
|
||||
pointing at the deleted file and lost its icon (#1114). The exe's path survives
|
||||
updates; the shortcut is not advertised, so it targets that path and its icon is the
|
||||
exe's own. The Icon above stays for ARPPRODUCTICON (Apps and features). -->
|
||||
<Shortcut Id="StartMenuShortcut" Directory="ProgramMenuFolder" Name="DeckCraft"
|
||||
Description="DeckCraft presentations and slide shows" WorkingDirectory="INSTALLFOLDER" Advertise="yes"
|
||||
Icon="DeckcraftIcon.ico" IconIndex="0" />
|
||||
Description="DeckCraft presentations and slide shows" WorkingDirectory="INSTALLFOLDER" Advertise="no" />
|
||||
</File>
|
||||
|
||||
<!-- .deckcraft: DeckCraft's own format. -->
|
||||
|
||||
@@ -83,6 +83,15 @@ foreach ($check in @(@('deckcraft.exe', 2), @('deckcraft-cli.exe', 3))) {
|
||||
if ($h.Subsystem -ne $check[1]) { throw "$($check[0]) has PE subsystem $($h.Subsystem), expected $($check[1])" }
|
||||
Write-Output "ok $($check[0]): $Arch, PE subsystem $($h.Subsystem)"
|
||||
}
|
||||
|
||||
# The Start menu shortcut is not advertised and names no MSI icon, so it shows the icon embedded in
|
||||
# deckcraft.exe (#1114). -SkipBuild takes whatever binary is already there, which may come from a
|
||||
# build without DECKCRAFT_REQUIRE_WINRES=1 whose resource compilation failed: refuse an exe with no
|
||||
# icon instead of shipping shortcuts with a generic one. ExtractIconEx with index -1 counts them.
|
||||
Add-Type -Namespace DeckCraft -Name Shell -MemberDefinition '[DllImport("shell32.dll", CharSet = CharSet.Unicode)] public static extern uint ExtractIconEx(string file, int index, IntPtr[] large, IntPtr[] small, uint icons);'
|
||||
$Icons = [DeckCraft.Shell]::ExtractIconEx((Join-Path $Bin 'deckcraft.exe'), -1, $null, $null, 0)
|
||||
if ($Icons -eq 0) { throw 'deckcraft.exe has no embedded icon: rebuild it with DECKCRAFT_REQUIRE_WINRES=1 (omit -SkipBuild)' }
|
||||
Write-Output "ok deckcraft.exe: $Icons embedded icon group(s)"
|
||||
$Stage = Join-Path $TargetDir "windows-package\$Arch"
|
||||
Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $Stage | Out-Null
|
||||
|
||||
Reference in New Issue
Block a user