mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-26 03:27:03 -06:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332432e939 | ||
|
|
aa3a1baa01 | ||
|
|
edfee40392 | ||
|
|
580b577cf2 | ||
|
|
230b6485d9 |
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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 {
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
+53
-32
@@ -74,16 +74,39 @@ impl<'a> SliceDecoder<'a> {
|
||||
|
||||
/// Merge mode motion (8.5.3.2.2).
|
||||
pub fn derive_merge(&self, g: PbGeom, part: PartMode, merge_idx: usize) -> MvField {
|
||||
let PbGeom { xc, yc, ncb, xp: xp0, yp: yp0, w: w0, h: h0, part_idx: part_idx0 } = g;
|
||||
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 g = PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx };
|
||||
let same_mer = |xn: i32, yn: i32| (xp >> pml) == (xn >> pml) && (yp >> pml) == (yn >> pml);
|
||||
let avail = |xn: i32, yn: i32| self.pb_avail(&g, 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() };
|
||||
@@ -130,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) {
|
||||
@@ -148,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 {
|
||||
@@ -179,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 {
|
||||
|
||||
@@ -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