use super::Op;
pub(super) fn simplify(mut ops: Vec<Op>) -> Vec<Op> {
for _ in 0..16 {
let next = simplify_once(&ops);
if next == ops {
return ops;
}
ops = next;
}
ops
}
fn simplify_once(ops: &[Op]) -> Vec<Op> {
let no_nops: Vec<Op> = ops.iter().filter(|o| !matches!(o, Op::Nop)).cloned().collect();
let folded = coalesce_dihedral(no_nops);
peephole(folded)
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct D4 {
a: i8,
b: i8,
c: i8,
d: i8,
}
impl D4 {
fn of(op: &Op) -> Option<D4> {
Some(match op {
Op::FlipHorizontal => D4 {
a: -1,
b: 0,
c: 0,
d: 1,
},
Op::FlipVertical => D4 {
a: 1,
b: 0,
c: 0,
d: -1,
},
Op::Rotate90 => D4 {
a: 0,
b: -1,
c: 1,
d: 0,
},
Op::Rotate180 => D4 {
a: -1,
b: 0,
c: 0,
d: -1,
},
Op::Rotate270 => D4 {
a: 0,
b: 1,
c: -1,
d: 0,
},
_ => return None,
})
}
fn then(self, rhs: D4) -> D4 {
D4 {
a: rhs.a * self.a + rhs.b * self.c,
b: rhs.a * self.b + rhs.b * self.d,
c: rhs.c * self.a + rhs.d * self.c,
d: rhs.c * self.b + rhs.d * self.d,
}
}
fn to_ops(self) -> Vec<Op> {
match (self.a, self.b, self.c, self.d) {
(1, 0, 0, 1) => vec![],
(-1, 0, 0, 1) => vec![Op::FlipHorizontal],
(1, 0, 0, -1) => vec![Op::FlipVertical],
(-1, 0, 0, -1) => vec![Op::Rotate180],
(0, -1, 1, 0) => vec![Op::Rotate90],
(0, 1, -1, 0) => vec![Op::Rotate270],
(0, 1, 1, 0) => vec![Op::FlipVertical, Op::Rotate90],
(0, -1, -1, 0) => vec![Op::FlipHorizontal, Op::Rotate90],
_ => unreachable!("D4 is closed under composition"),
}
}
}
fn coalesce_dihedral(ops: Vec<Op>) -> Vec<Op> {
let mut out: Vec<Op> = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
let Some(first) = D4::of(&ops[i]) else {
out.push(ops[i].clone());
i += 1;
continue;
};
let start = i;
let mut m = first;
i += 1;
while i < ops.len() {
match D4::of(&ops[i]) {
Some(d) => {
m = m.then(d);
i += 1;
}
None => break,
}
}
if i - start >= 2 {
out.extend(m.to_ops());
} else {
out.push(ops[start].clone());
}
}
out
}
fn is_luma(op: &Op) -> bool {
matches!(op, Op::ToLuma | Op::ToLumaCustom(_))
}
enum Act {
Fuse(Op),
Drop,
Keep,
}
fn decide(prev: Option<&Op>, op: &Op) -> Act {
match (prev, op) {
(
Some(Op::Crop {
x: ox, y: oy, ..
}),
Op::Crop {
x: ix,
y: iy,
width,
height,
},
) => Act::Fuse(Op::Crop {
x: ox + ix,
y: oy + iy,
width: *width,
height: *height,
}),
(Some(p), _) if is_luma(p) && is_luma(op) => Act::Drop,
(Some(Op::Convert(a)), Op::Convert(b)) if a == b => Act::Drop,
_ => Act::Keep,
}
}
fn peephole(ops: Vec<Op>) -> Vec<Op> {
let mut out: Vec<Op> = Vec::with_capacity(ops.len());
for op in ops {
match decide(out.last(), &op) {
Act::Fuse(n) => {
out.pop();
out.push(n);
}
Act::Drop => {}
Act::Keep => out.push(op),
}
}
out
}