Compare commits

..
14 changed files with 458 additions and 728 deletions
-2
View File
@@ -137,7 +137,6 @@ 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) |
@@ -294,7 +293,6 @@ 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 |
-23
View File
@@ -430,27 +430,4 @@ 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");
}
}
-3
View File
@@ -65,6 +65,3 @@ sync_github_name = true
[people.Katacc]
sync_github_name = true
[people.SofiaAFigueredo]
real_name = "Sofia Alves Figueredo"
+55 -78
View File
@@ -405,35 +405,49 @@ impl Decoder {
r
}
/// Whether an OBU belongs to the selected operating point (spec 7.5: OBUs of other layers are dropped).
fn in_operating_point(&self, obu: &ObuUnit) -> bool {
if obu.obu_type != OBU_SEQUENCE_HEADER_T
&& obu.obu_type != OBU_TEMPORAL_DELIMITER_T
&& obu.ext
&& let Some(seq) = &self.seq
&& seq.op_idc != 0
{
let idc = seq.op_idc;
let in_t = (idc >> obu.temporal_id) & 1;
let in_s = (idc >> (obu.spatial_id + 8)) & 1;
if in_t == 0 || in_s == 0 {
return false;
}
}
true
}
fn parse_obus(&mut self, data: &[u8], out: &mut Vec<(Output, usize, u32)>) -> Result<()> {
let mut tu = 0usize;
let mut pos = 0;
while pos < data.len() {
let obu = parse_obu_unit(data, pos)?;
pos = obu.end;
if !self.in_operating_point(&obu) {
continue;
let h = data[pos];
if h & 0x80 != 0 {
return Err(Error::Invalid("obu_forbidden_bit"));
}
let (payload, spatial_id) = (obu.payload, obu.spatial_id);
match obu.obu_type {
let obu_type = ((h >> 3) & 0xf) as u32;
let ext = h & 4 != 0;
let has_size = h & 2 != 0;
let mut p = pos + 1;
let (mut temporal_id, mut spatial_id) = (0, 0);
if ext {
let e = *data.get(p).ok_or(Error::Truncated)?;
temporal_id = (e >> 5) as u32;
spatial_id = ((e >> 3) & 3) as u32;
p += 1;
}
let size = if has_size {
let (v, n) = leb128(data.get(p..).ok_or(Error::Truncated)?)?;
p += n;
v as usize
} else {
data.len().checked_sub(p).ok_or(Error::Truncated)?
};
let end = p.checked_add(size).filter(|&e| e <= data.len()).ok_or(Error::Truncated)?;
let payload = &data[p..end];
pos = end;
if obu_type != OBU_SEQUENCE_HEADER_T
&& obu_type != OBU_TEMPORAL_DELIMITER_T
&& ext
&& let Some(seq) = &self.seq
&& seq.op_idc != 0
{
let idc = seq.op_idc;
let in_t = (idc >> temporal_id) & 1;
let in_s = (idc >> (spatial_id + 8)) & 1;
if in_t == 0 || in_s == 0 {
continue;
}
}
match obu_type {
OBU_SEQUENCE_HEADER_T => {
let s = SequenceHeader::parse(payload)?;
self.seq = Some(s);
@@ -443,11 +457,27 @@ impl Decoder {
tu += 1;
}
OBU_FRAME_HEADER_T | OBU_REDUNDANT_FRAME_HEADER_T | OBU_FRAME_T => {
if let Some(o) = self.frame_obu(&obu)? {
if self.seen_frame_header {
// frame_header_copy(): identical to the active header
if obu_type != OBU_FRAME_T {
continue;
}
}
let mut r = BitReader::new(payload);
let shown = self.frame_header(&mut r, temporal_id, spatial_id)?;
if let Some(o) = shown {
if out.iter().filter(|(o, _, _)| matches!(o, Output::Existing { .. })).count() >= 32 {
return Err(Error::Invalid("too many frame outputs in chunk"));
}
out.push((o, tu, spatial_id));
continue;
}
if obu_type == OBU_FRAME_T {
r.byte_align();
let rest = payload.get(r.byte_pos()..).ok_or(Error::Truncated)?;
if let Some(o) = self.tile_group(rest)? {
out.push((o, tu, spatial_id));
}
}
}
OBU_TILE_GROUP_T => {
@@ -462,22 +492,6 @@ impl Decoder {
Ok(())
}
/// A frame header or frame OBU: returns the picture it completes, if any.
fn frame_obu(&mut self, obu: &ObuUnit) -> Result<Option<Output>> {
// frame_header_copy(): identical to the active header
if self.seen_frame_header && obu.obu_type != OBU_FRAME_T {
return Ok(None);
}
let mut r = BitReader::new(obu.payload);
let shown = self.frame_header(&mut r, obu.temporal_id, obu.spatial_id)?;
if shown.is_some() || obu.obu_type != OBU_FRAME_T {
return Ok(shown);
}
r.byte_align();
let rest = obu.payload.get(r.byte_pos()..).ok_or(Error::Truncated)?;
self.tile_group(rest)
}
/// frame_header_obu( ): returns the output for show_existing_frame.
fn frame_header(&mut self, r: &mut BitReader, temporal_id: u32, spatial_id: u32) -> Result<Option<Output>> {
let seq = self.seq.clone().ok_or(Error::Invalid("frame before sequence header"))?;
@@ -1003,43 +1017,6 @@ mod tests {
}
}
/// One OBU: its header fields, payload and the position just past it.
struct ObuUnit<'a> {
obu_type: u32,
ext: bool,
temporal_id: u32,
spatial_id: u32,
payload: &'a [u8],
end: usize,
}
fn parse_obu_unit(data: &[u8], pos: usize) -> Result<ObuUnit<'_>> {
let h = data[pos];
if h & 0x80 != 0 {
return Err(Error::Invalid("obu_forbidden_bit"));
}
let obu_type = ((h >> 3) & 0xf) as u32;
let ext = h & 4 != 0;
let has_size = h & 2 != 0;
let mut p = pos + 1;
let (mut temporal_id, mut spatial_id) = (0, 0);
if ext {
let e = *data.get(p).ok_or(Error::Truncated)?;
temporal_id = (e >> 5) as u32;
spatial_id = ((e >> 3) & 3) as u32;
p += 1;
}
let size = if has_size {
let (v, n) = leb128(data.get(p..).ok_or(Error::Truncated)?)?;
p += n;
v as usize
} else {
data.len().checked_sub(p).ok_or(Error::Truncated)?
};
let end = p.checked_add(size).filter(|&e| e <= data.len()).ok_or(Error::Truncated)?;
Ok(ObuUnit { obu_type, ext, temporal_id, spatial_id, payload: &data[p..end], end })
}
#[cfg(test)]
mod guard_tests {
use super::*;
+161 -307
View File
@@ -55,79 +55,21 @@ impl Rng {
}
}
/// 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,
}
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;
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();
// Generate grain (7.18.3.3).
let mut rng = Rng(g.grain_seed);
let shift = 12 - bd + g.grain_scale_shift;
let mut luma = vec![[0i32; 82]; 73];
for row in luma.iter_mut() {
for v in row.iter_mut() {
@@ -135,89 +77,77 @@ fn luma_grain(c: &GrainCtx, rng: &mut Rng) -> GrainTemplate {
*v = round2(gv, shift);
}
}
let ar_shift = c.ar_shift();
let lag = c.lag();
let ar_shift = g.ar_coeff_shift_minus_6 + 6;
let lag = g.ar_coeff_lag as i32;
for y in 3..73 {
for x in 3..82 - 3 {
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
}
/// 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;
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;
}
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;
luma[y][x] = (luma[y][x] + round2(s, ar_shift)).clamp(grain_min, grain_max);
}
}
(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);
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);
}
}
}
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] {
// Scaling lookup (7.18.3.4).
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 {
@@ -229,141 +159,101 @@ fn scaling_luts(g: &FilmGrainParams, num_planes: usize) -> [[i32; 256]; 3] {
};
*l = scaling_lut(n, xs, ys);
}
lut
}
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)
}
};
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
// 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,
};
gv = round2(gv, 5).clamp(c.grain_min, c.grain_max);
}
s[idx] = gv;
}
}
}
/// 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);
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;
}
}
}
x += 16;
}
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 noise_at = |plane: usize, y: usize, x: usize| -> i32 {
let psy = if plane > 0 { sub_y } else { 0 };
let luma_num = y >> (5 - psy);
let i = y - (luma_num << (5 - psy));
let mut gv = self.data[luma_num][plane][i * self.stripe_w + x];
let mut gv = stripes[luma_num][plane][i * stripe_w + x];
if psy == 0 {
if i < 2 && luma_num > 0 && self.overlap {
let old = self.data[luma_num - 1][plane][(i + 32) * self.stripe_w + x];
if i < 2 && luma_num > 0 && overlap {
let old = stripes[luma_num - 1][plane][(i + 32) * stripe_w + x];
gv = if i == 0 { old * 27 + gv * 17 } else { old * 17 + gv * 27 };
gv = round2(gv, 5).clamp(self.grain_min, self.grain_max);
gv = round2(gv, 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);
} 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);
}
gv
}
}
};
/// 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);
// 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 {
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;
@@ -382,62 +272,26 @@ impl BlendEnv<'_> {
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, self.range.pix_max)
((combined >> 6) + ((offset as i32 - 256) << (bd - 8))).clamp(0, pix_max)
};
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;
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;
}
}
}
}
fn blend_luma(&self, py: &mut [u16]) {
let (w, h) = (self.c.w, self.c.h);
if g.num_y_points > 0 {
let py = &mut pic.planes[0];
for y in 0..h {
for x in 0..w {
let orig = py[y * w + x] as i32;
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;
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;
}
}
}
}
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::*;
+140 -201
View File
@@ -126,216 +126,155 @@ 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 {
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(),
#[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();
};
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;
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),
_ => {}
}
}
b'\r' | b'\n' => self.i += 1,
_ => self.text(),
}
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}', ""));
}
}
}
}
/// `{`: 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();
}
flush(&mut line, level, indent, &mut out);
finish_surrogates(out)
}
/// Push a UTF-16 unit; surrogate halves are parked as private markers (NUL plus four hex digits)
+1 -1
View File
@@ -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, y0): (i32, i32), (w, h): (usize, usize), out: &mut [u8], os: usize) {
pub fn chroma_window(&self, c: usize, x0: i32, y0: i32, w: usize, h: 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;
+25 -39
View File
@@ -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,38 +665,33 @@ impl<'a> SliceDecoder<'a> {
return Ok(());
}
for p in 0..4 {
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 sub = self.cur.sub[p];
let (x8, y8) = ((p & 1) * 2, (p >> 1) * 2);
if sub.direct {
self.derive_direct(1 << p)?;
continue;
}
}
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])];
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;
}
}
self.set_motion(x, y, w, h, refs, mvs);
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);
}
}
Ok(())
}
@@ -1385,11 +1380,6 @@ 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 {
@@ -1423,10 +1413,6 @@ 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 {
+9 -4
View File
@@ -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, h): (usize, usize), bd: u32, (log2wd, wt, o): (u32, i32, i32), dst: &mut [u16], ds: usize) {
pub fn put_weighted_uni(p: &[i16], w: usize, h: usize, bd: u32, log2wd: u32, wt: i32, o: i32, dst: &mut [u16], ds: usize) {
let max = (1i32 << bd) - 1;
for r in 0..h {
for c in 0..w {
@@ -173,9 +173,14 @@ pub fn put_weighted_uni(p: &[i16], (w, h): (usize, usize), bd: u32, (log2wd, wt,
pub fn put_weighted_bi(
p0: &[i16],
p1: &[i16],
(w, h): (usize, usize),
w: usize,
h: usize,
bd: u32,
(log2wd, w0, w1, o0, o1): (u32, i32, i32, i32, i32),
log2wd: u32,
w0: i32,
w1: i32,
o0: i32,
o1: i32,
dst: &mut [u16],
ds: usize,
) {
+34 -23
View File
@@ -36,23 +36,9 @@ 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, g: &PbGeom, xn: i32, yn: i32) -> bool {
let PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx } = *g;
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 {
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)
@@ -73,13 +59,23 @@ 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;
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 {
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(xc, yc, ncb, xp, yp, w, h, part_idx, 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;
@@ -241,8 +237,20 @@ impl<'a> SliceDecoder<'a> {
}
/// AMVP predictor (8.5.3.2.6, 8.5.3.2.7).
pub fn derive_amvp(&self, g: PbGeom, l: usize, ref_idx: usize, mvp_flag: usize) -> [i16; 2] {
let PbGeom { xp, yp, w, h, .. } = g;
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] {
let target = &self.refs[l][ref_idx];
let (tpoc, tlt) = (target.poc, target.long_term);
let cur_poc = self.pic.poc;
@@ -269,7 +277,10 @@ 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(&g, pa[0].0, pa[0].1), self.pb_avail(&g, pa[1].0, pa[1].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 is_scaled = av_a[0] || av_a[1];
let mut a = None;
for k in 0..2 {
@@ -285,7 +296,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(&g, pb[k].0, pb[k].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 mut b = None;
for k in 0..3 {
if av_b[k] && b.is_none() {
+1 -1
View File
@@ -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, y0): (i32, i32), (w, h): (usize, usize), out: &mut [i16], os: usize) {
pub fn chroma_window(&self, c: usize, x0: i32, y0: i32, w: usize, h: 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;
+30 -29
View File
@@ -7,7 +7,6 @@ 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};
@@ -679,7 +678,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(PbGeom { xc: x0, yc: y0, ncb: n, xp: x0, yp: y0, w: n, h: n, part_idx: 0 }, PartMode::P2Nx2N, true)?;
self.prediction_unit(x0, y0, n, x0, y0, n, n, 0, PartMode::P2Nx2N, true)?;
self.finish_cu(x0, y0, n);
return Ok(());
}
@@ -708,7 +707,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;
@@ -725,8 +724,7 @@ 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(PbGeom { xc: x0, yc: y0, ncb: n, xp: x0 + px, yp: y0 + py, w: pw, h: ph, part_idx: i }, part, false)?;
let merge = self.prediction_unit(x0, y0, n, x0 + px, y0 + py, pw, ph, i, part, false)?;
if part == PartMode::P2Nx2N {
merge2nx2n = merge;
}
@@ -740,7 +738,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);
@@ -914,7 +912,7 @@ impl<'a> SliceDecoder<'a> {
Ok(())
}
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<()> {
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<()> {
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
@@ -939,10 +937,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 =
@@ -951,7 +949,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), base, log2, blk_idx, cbf_luma, cbf_c)
self.transform_unit(x0, y0, xb, yb, log2, blk_idx, cbf_luma, cbf_c)
}
/// cu_qp_delta_abs / sign and QP update (7.3.8.14).
@@ -981,15 +979,7 @@ impl<'a> SliceDecoder<'a> {
Ok(())
}
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<()> {
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<()> {
let n = 1i32 << log2;
let chroma_here = log2 > 2 || blk_idx == 3;
let cbf_chroma = cbf_c[0] || cbf_c[1];
@@ -1294,7 +1284,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> {
@@ -1313,7 +1303,7 @@ impl<'a> SliceDecoder<'a> {
}
/// Scaling, transform and addition to the prediction.
fn reconstruct(&mut self, (x0, y0): (i32, i32), log2: u32, c: usize, qp: i32, ts: bool, (max_x, max_y): (usize, usize)) -> Result<()> {
fn reconstruct(&mut self, x0: i32, y0: i32, log2: u32, c: usize, qp: i32, ts: bool, max_x: usize, max_y: 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];
@@ -1365,8 +1355,19 @@ impl<'a> SliceDecoder<'a> {
}
/// prediction_unit(): parse, derive motion, store it and predict. Returns merge_flag.
fn prediction_unit(&mut self, g: PbGeom, part: PartMode, skip: bool) -> Result<bool> {
let PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx } = g;
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> {
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 {
@@ -1381,7 +1382,7 @@ impl<'a> SliceDecoder<'a> {
} else {
0
};
self.derive_merge(PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx }, part, idx as usize)
self.derive_merge(xc, yc, ncb, xp, yp, w, h, part_idx, part, idx as usize)
} else {
let pred_idc = if self.sh.is_b() {
if w + h != 12 {
@@ -1425,7 +1426,7 @@ impl<'a> SliceDecoder<'a> {
if ref_idx[l] < 0 {
continue;
}
let mvp = self.derive_amvp(PbGeom { xc, yc, ncb, xp, yp, w, h, part_idx }, l, ref_idx[l] as usize, mvp_flag[l] as usize);
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];
}
@@ -1523,11 +1524,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);
}
}
}
+2 -8
View File
@@ -58,15 +58,9 @@
<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="no" />
Description="DeckCraft presentations and slide shows" WorkingDirectory="INSTALLFOLDER" Advertise="yes"
Icon="DeckcraftIcon.ico" IconIndex="0" />
</File>
<!-- .deckcraft: DeckCraft's own format. -->
-9
View File
@@ -83,15 +83,6 @@ 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