1use std::collections::{HashMap, HashSet};
30
31use rusqlite::types::Value as SqlValue;
32
33use crate::binary::{Reader, Writer};
34use crate::clip::ClipFile;
35use crate::cmt::{self, Document, Node, Value};
36use crate::database::{Row, select_all, table_exists, value_as_text};
37use crate::error::{Error, Result, bail};
38use crate::model::LayerNode;
39
40pub const LAYER_TYPE_LIGHT_TABLE: i64 = 10016;
43pub const LAYER_TYPE_CAMERA: i64 = 512;
45pub const TICKS_PER_SECOND: f64 = 60.0;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
50#[non_exhaustive]
51pub enum TrackKind {
52 Cels,
54 Keyframes,
56 Paper,
58 Camera,
60 Other(i64),
62}
63
64impl TrackKind {
65 pub fn from_code(code: i64) -> Self {
67 match code {
68 2000 => TrackKind::Cels,
69 2001 => TrackKind::Keyframes,
70 2003 => TrackKind::Paper,
71 2005 => TrackKind::Camera,
72 other => TrackKind::Other(other),
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
79#[non_exhaustive]
80pub enum Interp {
81 Constant,
83 Smooth,
85 Linear,
87 Other(String),
89}
90
91impl Interp {
92 pub fn from_name(name: &str) -> Self {
94 match name {
95 "Constant" => Interp::Constant,
96 "Default" => Interp::Smooth,
97 "Linear" => Interp::Linear,
98 other => Interp::Other(other.to_owned()),
99 }
100 }
101
102 pub fn name(&self) -> &str {
104 match self {
105 Interp::Constant => "Constant",
106 Interp::Smooth => "Default",
107 Interp::Linear => "Linear",
108 Interp::Other(name) => name,
109 }
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, serde::Serialize)]
115pub struct Key {
116 pub frame: f64,
118 pub value: f64,
120 pub left_slope: f64,
122 pub right_slope: f64,
124 pub interp: Interp,
126 pub tag: Option<String>,
128}
129
130#[derive(Debug, Clone, PartialEq, serde::Serialize)]
132pub struct Curve {
133 pub kind: String,
135 pub axis: Option<String>,
137 pub keys: Vec<Key>,
139}
140
141impl Curve {
142 fn from_node(node: &Node) -> Result<Self> {
143 let kind = node.attribute("Type").unwrap_or_default().to_owned();
144 let axis = node.attribute("Axis").map(str::to_owned);
145 let numbers = |name: &str| node.child(name).and_then(|n| n.value.as_numbers());
146 let frames = numbers("Frame").ok_or_else(|| Error::format(format!("FCurve {kind} has no Frame")))?;
147 let values = numbers("Value").ok_or_else(|| Error::format(format!("FCurve {kind} has no Value")))?;
148 if values.len() != frames.len() {
149 bail!("FCurve {kind}: {} frames, {} values", frames.len(), values.len());
150 }
151 let left = numbers("LeftSlope");
152 let right = numbers("RightSlope");
153 let interp = node.child("Interp").and_then(|n| n.value.as_strings());
154 let tags = node.child("Tag").and_then(|n| n.value.as_strings());
155 let keys = frames
156 .iter()
157 .zip(&values)
158 .enumerate()
159 .map(|(i, (frame, value))| Key {
160 frame: *frame,
161 value: *value,
162 left_slope: left.as_ref().and_then(|v| v.get(i)).copied().unwrap_or(0.0),
163 right_slope: right.as_ref().and_then(|v| v.get(i)).copied().unwrap_or(0.0),
164 interp: interp.and_then(|v| v.get(i)).map_or(Interp::Smooth, |name| Interp::from_name(name)),
165 tag: tags.and_then(|v| v.get(i)).cloned(),
166 })
167 .collect();
168 Ok(Curve { kind, axis, keys })
169 }
170
171 pub fn evaluate(&self, m: f64) -> Option<(f64, Option<usize>)> {
175 let first = self.keys.first()?;
176 if m < first.frame {
177 return Some((first.value, None));
178 }
179 let index = self.keys.iter().rposition(|key| key.frame <= m).unwrap_or(0);
180 let key = &self.keys[index];
181 let Some(next) = self.keys.get(index + 1) else { return Some((key.value, Some(index))) };
182 let span = next.frame - key.frame;
183 if span <= 0.0 {
184 return Some((key.value, Some(index)));
185 }
186 let u = (m - key.frame) / span;
187 let value = match key.interp {
188 Interp::Constant => key.value,
189 Interp::Smooth => {
190 let (u2, u3) = (u * u, u * u * u);
191 let h00 = 2.0 * u3 - 3.0 * u2 + 1.0;
192 let h10 = u3 - 2.0 * u2 + u;
193 let h01 = -2.0 * u3 + 3.0 * u2;
194 let h11 = u3 - u2;
195 h00 * key.value + h10 * span * key.right_slope + h01 * next.value + h11 * span * next.left_slope
196 }
197 Interp::Linear | Interp::Other(_) => key.value + (next.value - key.value) * u,
198 };
199 Some((value, Some(index)))
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, serde::Serialize)]
205pub struct Param {
206 pub name: String,
208 pub tag: Option<String>,
210 pub value: Value,
212}
213
214#[derive(Debug, Clone, PartialEq, serde::Serialize)]
216pub struct Clip {
217 pub time: (f64, f64),
219 pub motion: (f64, f64),
221 pub params: Vec<Param>,
223 pub curves: Vec<Curve>,
225}
226
227impl Clip {
228 pub fn from_document(document: &Document) -> Result<Self> {
230 let clip = document.action_clip().ok_or_else(|| Error::format("Track mixer has no ActionNodeClip"))?;
231 let number = |path: &[&str]| clip.find(path).and_then(|n| n.value.as_f64()).unwrap_or(0.0);
232 let mut params = Vec::new();
233 let mut curves = Vec::new();
234 for node in clip.child("AnimInfo").map(|n| n.children.as_slice()).unwrap_or_default() {
235 match node.name.as_str() {
236 "Param" => params.push(Param {
237 name: node.attribute("Name").unwrap_or_default().to_owned(),
238 tag: node.attribute("Tag").map(str::to_owned),
239 value: node.value.clone(),
240 }),
241 "FCurve" => curves.push(Curve::from_node(node)?),
242 _ => {}
243 }
244 }
245 Ok(Clip {
246 time: (number(&["TimeClip", "Start"]), number(&["TimeClip", "End"])),
247 motion: (number(&["MotionClip", "Start"]), number(&["MotionClip", "End"])),
248 params,
249 curves,
250 })
251 }
252
253 pub fn motion_at(&self, t: f64) -> f64 {
255 let (ts, te) = self.time;
256 let (ms, me) = self.motion;
257 if te == ts { ms } else { ms + (t - ts) * (me - ms) / (te - ts) }
258 }
259
260 pub fn time_at(&self, m: f64) -> f64 {
262 let (ts, te) = self.time;
263 let (ms, me) = self.motion;
264 if me == ms { ts } else { ts + (m - ms) * (te - ts) / (me - ms) }
265 }
266
267 pub fn curve(&self, kind: &str, axis: Option<&str>) -> Option<&Curve> {
269 self.curves.iter().find(|c| c.kind == kind && c.axis.as_deref() == axis)
270 }
271
272 pub fn param(&self, name: &str) -> Option<&Param> {
274 self.params.iter().find(|p| p.name == name)
275 }
276
277 pub fn value(&self, kind: &str, axis: Option<&str>, t: f64) -> Option<f64> {
280 if let Some((value, _)) = self.curve(kind, axis).and_then(|c| c.evaluate(self.motion_at(t))) {
281 return Some(value);
282 }
283 let value = &self.param(kind)?.value;
284 match axis {
285 Some("X") => value.as_pair().map(|p| p[0]),
286 Some("Y") => value.as_pair().map(|p| p[1]),
287 _ => value.as_f64(),
288 }
289 }
290
291 fn pair(&self, kind: &str, t: f64) -> Option<[f64; 2]> {
292 Some([self.value(kind, Some("X"), t)?, self.value(kind, Some("Y"), t)?])
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, serde::Serialize)]
299pub struct MapEntry {
300 pub name: String,
302 pub tag: String,
304 pub value: MapValue,
306}
307
308#[derive(Debug, Clone, PartialEq, serde::Serialize)]
310pub enum MapValue {
311 Double(f64),
313 Int(i32),
315 Pair([f64; 2]),
317 Raw(u32, Vec<u8>),
319}
320
321pub fn parse_value_map(data: &[u8]) -> Result<Vec<MapEntry>> {
323 let mut reader = Reader::new(data);
324 let header = reader.be_u32()?;
325 if header != 8 {
326 bail!("TrackValueMap header {header}");
327 }
328 let count = reader.be_u32()? as usize;
329 let mut entries = Vec::with_capacity(count.min(64));
330 for _ in 0..count {
331 let start = reader.position();
332 let size = reader.be_u32()? as usize;
333 let end = start + size;
334 let name = utf16(&mut reader)?;
335 let tag = utf16(&mut reader)?;
336 let kind = reader.be_u32()?;
337 let value = match kind {
338 0 => MapValue::Double(reader.be_f64()?),
339 2 => MapValue::Int(reader.be_i32()?),
340 3 => MapValue::Pair([reader.be_f64()?, reader.be_f64()?]),
341 other => {
342 let rest = end.checked_sub(reader.position()).ok_or_else(|| Error::format("TrackValueMap entry"))?;
343 MapValue::Raw(other, reader.bytes(rest)?.to_vec())
344 }
345 };
346 if reader.position() != end {
347 bail!("TrackValueMap entry {name:?} is {} bytes, says {size}", reader.position() - start);
348 }
349 entries.push(MapEntry { name, tag, value });
350 }
351 Ok(entries)
352}
353
354pub fn value_map_bytes(entries: &[MapEntry]) -> Vec<u8> {
356 let mut out = Writer::new();
357 out.be_u32(8).be_u32(entries.len() as u32);
358 for entry in entries {
359 let mut body = Writer::new();
360 put_utf16(&mut body, &entry.name);
361 put_utf16(&mut body, &entry.tag);
362 match &entry.value {
363 MapValue::Double(v) => {
364 body.be_u32(0).be_f64(*v);
365 }
366 MapValue::Int(v) => {
367 body.be_u32(2).be_i32(*v);
368 }
369 MapValue::Pair([x, y]) => {
370 body.be_u32(3).be_f64(*x).be_f64(*y);
371 }
372 MapValue::Raw(kind, bytes) => {
373 body.be_u32(*kind).bytes(bytes);
374 }
375 }
376 let body = body.into_inner();
377 out.be_u32(body.len() as u32 + 4).bytes(&body);
378 }
379 out.into_inner()
380}
381
382fn utf16(reader: &mut Reader) -> Result<String> {
383 let units = reader.be_u32()? as usize;
384 let bytes = reader.bytes(units.checked_mul(2).ok_or_else(|| Error::format("UTF-16 length"))?)?;
385 let units: Vec<u16> = bytes.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes(*c)).collect();
386 Ok(String::from_utf16_lossy(&units))
387}
388
389fn put_utf16(out: &mut Writer, text: &str) {
390 let units: Vec<u16> = text.encode_utf16().collect();
391 out.be_u32(units.len() as u32);
392 for unit in units {
393 out.be_u16(unit);
394 }
395}
396
397#[derive(Debug, Clone, serde::Serialize)]
399pub struct Track {
400 pub main_id: i64,
402 pub timeline: i64,
404 pub kind: TrackKind,
406 pub layer_uuid: String,
408 pub layer_id: Option<i64>,
410 pub option_flag: i64,
412 pub labels: Vec<Label>,
414 pub clip: Option<Clip>,
416 pub value_map: Vec<MapEntry>,
418 #[serde(skip)]
420 pub mixers: [Option<(String, Document)>; 2],
421}
422
423#[derive(Debug, Clone, PartialEq, serde::Serialize)]
426pub struct Label {
427 pub main_id: i64,
429 pub name: String,
431 pub frame: f64,
433 pub kind: i64,
435 pub length: Option<f64>,
437}
438
439#[derive(Debug, Clone, serde::Serialize)]
441pub struct Timeline {
442 pub main_id: i64,
444 pub name: String,
446 pub frame_rate: f64,
448 pub start_frame: f64,
450 pub end_frame: f64,
452 pub current_frame: f64,
454 pub guideline: i64,
456 pub scene: i64,
458 pub cut: i64,
460 pub uuid: Option<String>,
462 pub labels: Vec<Label>,
464 pub tracks: Vec<Track>,
466}
467
468impl Timeline {
469 pub fn ticks(&self, frame: f64) -> f64 {
471 frame * TICKS_PER_SECOND / self.frame_rate.max(1e-9)
472 }
473
474 pub fn track_for_layer(&self, layer_id: i64) -> Option<&Track> {
476 self.tracks.iter().find(|t| t.layer_id == Some(layer_id))
477 }
478}
479
480#[derive(Debug, Clone, PartialEq, serde::Serialize)]
482pub struct OnionSkin {
483 pub enabled: bool,
485 pub previous: i64,
487 pub next: i64,
489 pub use_opacity: bool,
491 pub start_opacity: i64,
493 pub step_opacity: i64,
495 pub use_color: bool,
497 pub half_color: bool,
499 pub front_color: [u8; 3],
501 pub behind_color: [u8; 3],
503}
504
505#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
508pub struct Frame {
509 pub x: f64,
511 pub y: f64,
513 pub width: f64,
515 pub height: f64,
517}
518
519#[derive(Debug, Clone, serde::Serialize)]
521pub struct Animation {
522 pub timelines: Vec<Timeline>,
524 pub current: usize,
526 pub onion_skin: Option<OnionSkin>,
528 pub frame: Frame,
530}
531
532impl Animation {
533 pub fn read(file: &ClipFile) -> Result<Option<Self>> {
535 let db = file.database();
536 if !table_exists(db, "TimeLine")? {
537 return Ok(None);
538 }
539 let timeline_rows: HashMap<i64, Row> =
540 select_all(db, "TimeLine")?.into_iter().map(|row| (row.int("MainId"), row)).collect();
541 if timeline_rows.is_empty() {
542 return Ok(None);
543 }
544 let track_rows: HashMap<i64, Row> = if table_exists(db, "Track")? {
545 select_all(db, "Track")?.into_iter().map(|row| (row.int("MainId"), row)).collect()
546 } else {
547 HashMap::new()
548 };
549 let label_rows: HashMap<i64, Row> = if table_exists(db, "TimeLineLabel")? {
550 select_all(db, "TimeLineLabel")?.into_iter().map(|row| (row.int("MainId"), row)).collect()
551 } else {
552 HashMap::new()
553 };
554 let bank = if table_exists(db, "AnimationCutBank")? {
555 select_all(db, "AnimationCutBank")?.into_iter().next()
556 } else {
557 None
558 };
559 let mut layer_by_uuid = HashMap::new();
560 for layer in &file.layers.layers {
561 if let Some(uuid) = &layer.uuid {
562 layer_by_uuid.insert(uuid.replace('-', "").to_ascii_lowercase(), layer.main_id);
563 }
564 }
565 let labels = |first: i64| -> Result<Vec<Label>> {
566 let mut out = Vec::new();
567 let mut next = first;
568 while next != 0 {
569 if out.len() > label_rows.len() {
570 bail!("TimeLineLabel list loops");
571 }
572 let row = label_rows.get(&next).ok_or_else(|| Error::format(format!("No TimeLineLabel {next}")))?;
573 out.push(Label {
574 main_id: next,
575 name: row.text("LabelName").unwrap_or_default(),
576 frame: row.real("LabelFrame"),
577 kind: row.int("LabelType"),
578 length: row.opt_real("LabelLength"),
579 });
580 next = row.int("LabelNextIndex");
581 }
582 Ok(out)
583 };
584 let first = bank.as_ref().map(|b| b.int("FirstTimeLine")).filter(|id| *id != 0).unwrap_or_else(|| {
585 let pointed: HashSet<i64> = timeline_rows.values().map(|r| r.int("NextTimeLine")).collect();
587 timeline_rows.keys().copied().filter(|id| !pointed.contains(id)).min().unwrap_or(0)
588 });
589 let mut timelines = Vec::new();
590 let mut next = first;
591 while next != 0 {
592 if timelines.len() > timeline_rows.len() {
593 bail!("TimeLine list loops");
594 }
595 let row = timeline_rows.get(&next).ok_or_else(|| Error::format(format!("No TimeLine {next}")))?;
596 let mut tracks = Vec::new();
597 let mut track_id = row.int("FirstTrack");
598 while track_id != 0 {
599 if tracks.len() > track_rows.len() {
600 bail!("Track list loops");
601 }
602 let track = track_rows.get(&track_id).ok_or_else(|| Error::format(format!("No Track {track_id}")))?;
603 tracks.push(read_track(file, track, &layer_by_uuid, &labels)?);
604 track_id = track.int("TrackNextIndex");
605 }
606 timelines.push(Timeline {
607 main_id: next,
608 name: row.text("TimeLineName").unwrap_or_default(),
609 frame_rate: row.real("FrameRate"),
610 start_frame: row.real("StartFrame"),
611 end_frame: row.real("EndFrame"),
612 current_frame: row.real("CurrentFrame"),
613 guideline: row.int("GuidelineFrameRate"),
614 scene: row.int("SceneIndexForName"),
615 cut: row.int("CutIndexForName"),
616 uuid: row.get("TimeLineUuid").map(uuid_text),
617 labels: labels(row.int("LabelFirstIndex"))?,
618 tracks,
619 });
620 next = row.int("NextTimeLine");
621 }
622 let current = bank.map(|b| b.int("CurrentIndex")).unwrap_or(0).clamp(0, timelines.len() as i64 - 1) as usize;
623 Ok(Some(Animation { timelines, current, onion_skin: onion_skin(&file.canvas.row), frame: frame(file) }))
624 }
625
626 pub fn current_timeline(&self) -> Option<&Timeline> {
628 self.timelines.get(self.current)
629 }
630
631 pub fn timeline(&self, main_id: i64) -> Option<&Timeline> {
633 self.timelines.iter().find(|t| t.main_id == main_id)
634 }
635}
636
637fn read_track(
638 file: &ClipFile,
639 row: &Row,
640 layer_by_uuid: &HashMap<String, i64>,
641 labels: &dyn Fn(i64) -> Result<Vec<Label>>,
642) -> Result<Track> {
643 let layer_uuid = row.get("LayerUuidWithTrack").map(uuid_text).unwrap_or_default();
644 let mut mixers: [Option<(String, Document)>; 2] = [None, None];
645 for (slot, column) in ["TrackActionMixer", "TrackActionMixer2"].iter().enumerate() {
646 let Some(id) = row.get(column).and_then(value_as_text) else { continue };
647 let Some(payload) = file.external(&id) else { continue };
648 let document = Document::parse(&cmt::unpack(payload)?)?;
649 mixers[slot] = Some((id, document));
650 }
651 let clip = match mixers[1].as_ref().or(mixers[0].as_ref()) {
652 Some((_, document)) => Some(Clip::from_document(document)?),
653 None => None,
654 };
655 let value_map = match row.blob("TrackValueMap") {
656 Some(data) if !data.is_empty() => parse_value_map(data)?,
657 _ => Vec::new(),
658 };
659 Ok(Track {
660 main_id: row.int("MainId"),
661 timeline: row.int("ItemId"),
662 kind: TrackKind::from_code(row.int("TrackKind")),
663 layer_id: layer_by_uuid.get(&layer_uuid).copied(),
664 layer_uuid,
665 option_flag: row.int("TrackOptionFlag"),
666 labels: labels(row.int("TrackLabelFirstIndex"))?,
667 clip,
668 value_map,
669 mixers,
670 })
671}
672
673fn uuid_text(value: &SqlValue) -> String {
675 match value {
676 SqlValue::Blob(bytes) => bytes.iter().map(|b| format!("{b:02x}")).collect(),
677 other => value_as_text(other).unwrap_or_default().replace('-', "").to_ascii_lowercase(),
678 }
679}
680
681fn channel(row: &Row, column: &str) -> u8 {
683 (row.int(column) as u32 & 0xff) as u8
684}
685
686fn onion_skin(row: &Row) -> Option<OnionSkin> {
687 if !row.has("OnionSkinStatus") {
688 return None;
689 }
690 Some(OnionSkin {
691 enabled: row.truthy("OnionSkinStatus"),
692 previous: row.int("OnionSkinPrevDisplayCount"),
693 next: row.int("OnionSkinNextDisplayCount"),
694 use_opacity: row.truthy("OnionSkinUseOpacity"),
695 start_opacity: row.int("OnionSkinStartOpacity"),
696 step_opacity: row.int("OnionSkinStepOpaciy"),
697 use_color: row.truthy("OnionSkinUseColor"),
698 half_color: row.truthy("OnionSkinHalfColor"),
699 front_color: [
700 channel(row, "OnionSkinFrontColorRed"),
701 channel(row, "OnionSkinFrontColorGreen"),
702 channel(row, "OnionSkinFrontColorBlue"),
703 ],
704 behind_color: [
705 channel(row, "OnionSkinBehindColorRed"),
706 channel(row, "OnionSkinBehindColorGreen"),
707 channel(row, "OnionSkinBehindColorBlue"),
708 ],
709 })
710}
711
712fn frame(file: &ClipFile) -> Frame {
717 let row = &file.canvas.row;
718 let unit = crate::model::Unit::from_code(row.int("CropFrameUnitKind"));
719 let px = |column: &str| unit.to_pixels(row.real(column), file.canvas.resolution);
720 let (cw, ch) = (file.canvas.width_px as f64, file.canvas.height_px as f64);
721 let crop = (px("CropFrameWidth"), px("CropFrameHeight"));
722 let inner = (px("CropFrameInnerWidth"), px("CropFrameInnerHeight"));
723 if crop.0 <= 0.0 || crop.1 <= 0.0 {
724 return Frame { x: 0.0, y: 0.0, width: cw, height: ch };
725 }
726 let crop_x = (cw - crop.0) / 2.0 + px("CropFrameCropOffsetX");
727 let crop_y = (ch - crop.1) / 2.0 + px("CropFrameCropOffsetY");
728 let (width, height) = if inner.0 > 0.0 && inner.1 > 0.0 { inner } else { crop };
729 Frame {
730 x: crop_x + (crop.0 - width) / 2.0 + px("CropFrameInnerOffsetX"),
731 y: crop_y + (crop.1 - height) / 2.0 + px("CropFrameInnerOffsetY"),
732 width,
733 height,
734 }
735}
736
737#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
739pub struct Affine(pub [f64; 6]);
740
741impl Affine {
742 pub const IDENTITY: Affine = Affine([1.0, 0.0, 0.0, 0.0, 1.0, 0.0]);
744
745 pub fn apply(&self, (x, y): (f64, f64)) -> (f64, f64) {
747 let [a, b, c, d, e, f] = self.0;
748 (a * x + b * y + c, d * x + e * y + f)
749 }
750
751 pub fn inverse(&self) -> Option<Affine> {
753 let [a, b, c, d, e, f] = self.0;
754 let det = a * e - b * d;
755 if det.abs() < 1e-12 {
756 return None;
757 }
758 let (ia, ib, id, ie) = (e / det, -b / det, -d / det, a / det);
759 Some(Affine([ia, ib, -(ia * c + ib * f), id, ie, -(id * c + ie * f)]))
760 }
761
762 pub fn then_after(&self, other: &Affine) -> Affine {
764 let [a, b, c, d, e, f] = self.0;
765 let [p, q, r, s, t, u] = other.0;
766 Affine([a * p + b * s, a * q + b * t, a * r + b * u + c, d * p + e * s, d * q + e * t, d * r + e * u + f])
767 }
768
769 pub fn keyframe(center: [f64; 2], position: [f64; 2], scale: [f64; 2], rotation: f64) -> Affine {
772 let (sin, cos) = rotation.to_radians().sin_cos();
773 let (a, b, d, e) = (cos * scale[0], -sin * scale[1], sin * scale[0], cos * scale[1]);
774 Affine([a, b, position[0] - a * center[0] - b * center[1], d, e, position[1] - d * center[0] - e * center[1]])
775 }
776}
777
778#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize)]
780pub struct LayerTransform {
781 pub map: Affine,
783 pub opacity: Option<i64>,
785}
786
787#[derive(Debug, Clone, Default, serde::Serialize)]
791pub struct FrameState {
792 pub hidden: HashSet<i64>,
794 pub transforms: HashMap<i64, LayerTransform>,
796 pub cameras: HashMap<i64, Affine>,
798 pub cels: HashMap<i64, Option<i64>>,
800 pub unknown_interp: HashSet<String>,
802}
803
804fn opacity_256(percent: f64) -> i64 {
806 (percent * 256.0 / 100.0).round().clamp(0.0, 256.0) as i64
807}
808
809impl Animation {
810 pub fn frame_state(&self, file: &ClipFile, index: usize, frame: f64) -> Result<FrameState> {
812 let timeline =
813 self.timelines.get(index).ok_or_else(|| Error::format(format!("No timeline at index {index}")))?;
814 let t = timeline.ticks(frame);
815 let mut state = FrameState::default();
816 let mut tracks: HashMap<i64, &Track> = HashMap::new();
817 for track in &timeline.tracks {
818 if let Some(id) = track.layer_id {
819 tracks.insert(id, track);
820 }
821 }
822 let mut stack: Vec<&LayerNode> = vec![&file.layers.root];
823 while let Some(node) = stack.pop() {
824 stack.extend(node.children.iter());
825 let layer = &node.layer;
826 if layer.layer_type == LAYER_TYPE_LIGHT_TABLE {
827 state.hidden.insert(layer.main_id);
828 continue;
829 }
830 let Some(track) = tracks.get(&layer.main_id) else {
831 if layer.row.truthy("AnimationFolder") {
832 for child in &node.children {
834 state.hidden.insert(child.layer.main_id);
835 }
836 state.cels.insert(layer.main_id, None);
837 }
838 continue;
839 };
840 let Some(clip) = &track.clip else { continue };
841 for curve in &clip.curves {
842 for key in &curve.keys {
843 if let Interp::Other(name) = &key.interp {
844 state.unknown_interp.insert(name.clone());
845 }
846 }
847 }
848 match track.kind {
849 TrackKind::Cels => {
850 let cel = clip
851 .curve("ImageCelName", None)
852 .and_then(|curve| {
853 let (_, index) = curve.evaluate(clip.motion_at(t))?;
854 curve.keys.get(index?)
855 })
856 .and_then(|key| key.tag.clone());
857 let shown = cel.as_ref().and_then(|name| {
858 node.children.iter().rev().find(|c| &c.layer.name == name).map(|c| c.layer.main_id)
859 });
860 for child in &node.children {
861 if Some(child.layer.main_id) != shown {
862 state.hidden.insert(child.layer.main_id);
863 }
864 }
865 state.cels.insert(layer.main_id, shown);
866 }
867 TrackKind::Keyframes => {
868 if !layer.row.truthy("TimeLineLayerKeyFrameEnabled") {
869 continue;
870 }
871 if t < clip.time.0 || t >= clip.time.1 {
874 state.hidden.insert(layer.main_id);
875 continue;
876 }
877 let center = clip.pair("ImageCenter", t).unwrap_or([0.0, 0.0]);
878 let position = clip.pair("ImagePosition", t).unwrap_or(center);
879 let scale = clip.pair("ImageAspectScale", t).unwrap_or([100.0, 100.0]);
880 let rotation = clip.value("ImageRotation", None, t).unwrap_or(0.0);
881 state.transforms.insert(
882 layer.main_id,
883 LayerTransform {
884 map: Affine::keyframe(center, position, [scale[0] / 100.0, scale[1] / 100.0], rotation),
885 opacity: clip.value("Opacity", None, t).map(opacity_256),
886 },
887 );
888 }
889 TrackKind::Camera => {
890 let center =
891 clip.pair("ImageCenter", t).unwrap_or([self.frame.width / 2.0, self.frame.height / 2.0]);
892 let position =
893 clip.pair("ImagePosition", t).unwrap_or([self.frame.x + center[0], self.frame.y + center[1]]);
894 let scale = clip.value("ImageScale", None, t).unwrap_or(100.0) / 100.0;
895 let rotation = clip.value("ImageRotation", None, t).unwrap_or(0.0);
896 let camera = Affine::keyframe(center, position, [scale, scale], rotation);
899 let reference = Affine([1.0, 0.0, -self.frame.x, 0.0, 1.0, -self.frame.y]);
900 state.cameras.insert(layer.main_id, camera.then_after(&reference));
901 if let Some(opacity) = clip.value("Opacity", None, t) {
902 state.transforms.insert(
903 layer.main_id,
904 LayerTransform { map: Affine::IDENTITY, opacity: Some(opacity_256(opacity)) },
905 );
906 }
907 }
908 TrackKind::Paper | TrackKind::Other(_) => {}
909 }
910 }
911 Ok(state)
912 }
913}
914
915impl ClipFile {
918 pub fn animation(&self) -> Result<Option<Animation>> {
920 Animation::read(self)
921 }
922
923 fn timeline_row(&self, timeline_id: i64) -> Result<Row> {
924 self.row("TimeLine", timeline_id)?.ok_or_else(|| Error::format(format!("No TimeLine {timeline_id}")))
925 }
926
927 pub fn set_frame_rate(&mut self, timeline_id: i64, frame_rate: f64) -> Result<()> {
930 if !(frame_rate > 0.0 && frame_rate <= 240.0) {
931 bail!("Frame rate {frame_rate} out of range");
932 }
933 let old = self.timeline_row(timeline_id)?.real("FrameRate");
934 if old <= 0.0 {
935 bail!("TimeLine {timeline_id} has no frame rate");
936 }
937 let factor = old / frame_rate;
938 let animation = self.animation()?.ok_or_else(|| Error::format("No animation"))?;
939 let timeline = animation.timeline(timeline_id).ok_or_else(|| Error::format("No timeline"))?;
940 for track in &timeline.tracks {
941 self.edit_mixers(track, |clip| {
942 for name in ["Start", "End"] {
943 if let Some(node) = clip.find_mut(&["TimeClip", name])
944 && let Value::Double(v) = &mut node.value
945 {
946 *v *= factor;
947 }
948 }
949 Ok(())
950 })?;
951 }
952 self.database().execute("UPDATE TimeLine SET FrameRate = ?1 WHERE MainId = ?2", (frame_rate, timeline_id))?;
953 Ok(())
954 }
955
956 pub fn set_frame_range(&mut self, timeline_id: i64, start: i64, end: i64) -> Result<()> {
959 if start < 0 || end <= start {
960 bail!("Frame range {start}..{end} is empty");
961 }
962 let row = self.timeline_row(timeline_id)?;
963 let biggest = row.real("BiggestEndFrame").max(end as f64);
964 let current = row.real("CurrentFrame").clamp(start as f64, (end - 1) as f64);
965 self.database().execute(
966 "UPDATE TimeLine SET StartFrame = ?1, EndFrame = ?2, BiggestEndFrame = ?3, CurrentFrame = ?4 WHERE MainId = ?5",
967 (start as f64, end as f64, biggest, current, timeline_id),
968 )?;
969 Ok(())
970 }
971
972 pub fn set_cel_keys(&mut self, timeline_id: i64, folder_id: i64, keys: &[(i64, String)]) -> Result<()> {
976 let animation = self.animation()?.ok_or_else(|| Error::format("No animation"))?;
977 let timeline = animation.timeline(timeline_id).ok_or_else(|| Error::format("No timeline"))?;
978 let track =
979 timeline.tracks.iter().find(|t| t.layer_id == Some(folder_id) && t.kind == TrackKind::Cels).ok_or_else(
980 || Error::format(format!("Layer {folder_id} has no cel track in timeline {timeline_id}")),
981 )?;
982 let clip = track.clip.as_ref().ok_or_else(|| Error::format("Cel track has no mixer"))?;
983 let folder = self.layers.root.find(folder_id).ok_or_else(|| Error::format("No such folder"))?;
984 let cels: Vec<String> = folder.children.iter().map(|c| c.layer.name.clone()).collect();
985 let mut sorted = keys.to_vec();
986 sorted.sort_by_key(|(frame, _)| *frame);
987 sorted.dedup_by_key(|(frame, _)| *frame);
988 let mut frames = Vec::new();
989 let mut values = Vec::new();
990 let mut tags = Vec::new();
991 for (frame, name) in &sorted {
992 let index = cels
993 .iter()
994 .position(|c| c == name)
995 .ok_or_else(|| Error::format(format!("No cel {name:?} in folder {folder_id}")))?;
996 frames.push(clip.motion_at(timeline.ticks(*frame as f64)));
997 values.push(index as f64);
998 tags.push(name.clone());
999 }
1000 let current = sorted.iter().rev().find(|(frame, _)| *frame as f64 <= timeline.current_frame).or(sorted.first());
1001 let n = frames.len();
1002 self.edit_mixers(track, |clip| {
1003 let double = clip_is_double(clip);
1004 let numbers = |v: &[f64]| {
1005 if double {
1006 Value::DoubleArray(v.to_vec())
1007 } else {
1008 Value::SingleArray(v.iter().map(|x| *x as f32).collect())
1009 }
1010 };
1011 let anim = clip.child_mut("AnimInfo").ok_or_else(|| Error::format("No AnimInfo"))?;
1012 let mut curve = Node::new("FCurve", Value::Null);
1013 curve.attributes.push(("Type".into(), "ImageCelName".into()));
1014 curve.children = vec![
1015 Node::new("Frame", numbers(&frames)),
1016 Node::new("Value", numbers(&values)),
1017 Node::new("LeftSlope", numbers(&vec![0.0; n])),
1018 Node::new("RightSlope", numbers(&vec![0.0; n])),
1019 Node::new("Interp", Value::StringArray(vec!["Constant".into(); n])),
1020 Node::new("ReviseConstant", Value::ByteArray(vec![1; n])),
1021 Node::new("Tag", Value::StringArray(tags.clone())),
1022 ];
1023 match anim.children.iter_mut().find(|c| c.name == "FCurve" && c.attribute("Type") == Some("ImageCelName")) {
1024 Some(existing) => *existing = curve,
1025 None => anim.children.push(curve),
1026 }
1027 if let Some((_, name)) = current
1028 && let Some(param) =
1029 anim.children.iter_mut().find(|c| c.name == "Param" && c.attribute("Name") == Some("ImageCelName"))
1030 {
1031 param.value = Value::Int32(cels.iter().position(|c| c == name).unwrap_or(0) as i32);
1032 match param.attributes.iter_mut().find(|(k, _)| k == "Tag") {
1033 Some(tag) => tag.1 = name.clone(),
1034 None => param.attributes.push(("Tag".into(), name.clone())),
1035 }
1036 }
1037 Ok(())
1038 })?;
1039 if let Some((_, name)) = current {
1040 let entries = vec![MapEntry {
1041 name: "ImageCelName".into(),
1042 tag: name.clone(),
1043 value: MapValue::Int(cels.iter().position(|c| c == name).unwrap_or(0) as i32),
1044 }];
1045 self.database().execute(
1046 "UPDATE Track SET TrackValueMap = ?1 WHERE MainId = ?2",
1047 (value_map_bytes(&entries), track.main_id),
1048 )?;
1049 }
1050 Ok(())
1051 }
1052
1053 fn edit_mixers(&mut self, track: &Track, edit: impl Fn(&mut Node) -> Result<()>) -> Result<()> {
1055 for (slot, column) in ["TrackActionMixer", "TrackActionMixer2"].iter().enumerate() {
1056 let Some((id, document)) = &track.mixers[slot] else { continue };
1057 let mut document = document.clone();
1058 edit(document.action_clip_mut().ok_or_else(|| Error::format("No ActionNodeClip"))?)?;
1059 let bytes = document.to_bytes()?;
1060 self.set_external(id, cmt::pack(&bytes)?);
1061 let size_column = format!("{column}Size");
1062 self.database().execute(
1063 &format!("UPDATE Track SET {size_column} = ?1 WHERE MainId = ?2"),
1064 (bytes.len() as i64, track.main_id),
1065 )?;
1066 }
1067 Ok(())
1068 }
1069}
1070
1071fn clip_is_double(clip: &Node) -> bool {
1073 fn any_double(node: &Node) -> Option<bool> {
1074 match node.value {
1075 Value::DoubleArray(_) | Value::Double2(_) | Value::Double2Array(_) => return Some(true),
1076 Value::SingleArray(_) | Value::Float2(_) | Value::Float2Array(_) => return Some(false),
1077 _ => {}
1078 }
1079 node.children.iter().find_map(any_double)
1080 }
1081 any_double(clip).unwrap_or(true)
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086 use super::*;
1087
1088 fn curve(keys: &[(f64, f64, &str)]) -> Curve {
1089 Curve {
1090 kind: "Opacity".into(),
1091 axis: None,
1092 keys: keys
1093 .iter()
1094 .map(|(frame, value, interp)| Key {
1095 frame: *frame,
1096 value: *value,
1097 left_slope: 0.0,
1098 right_slope: 0.0,
1099 interp: Interp::from_name(interp),
1100 tag: None,
1101 })
1102 .collect(),
1103 }
1104 }
1105
1106 #[test]
1107 fn constant_holds_and_default_eases() {
1108 let c = curve(&[(50.0, 322.0, "Constant"), (110.0, 409.0, "Default"), (130.0, 300.0, "Default")]);
1109 assert_eq!(c.evaluate(0.0), Some((322.0, None)));
1110 assert_eq!(c.evaluate(109.0).unwrap().0, 322.0);
1111 assert_eq!(c.evaluate(110.0).unwrap().0, 409.0);
1112 let v = c.evaluate(115.0).unwrap().0;
1114 assert!((v - (409.0 - 109.0 * 0.15625)).abs() < 1e-9, "{v}");
1115 assert_eq!(c.evaluate(120.0).unwrap().0, 354.5);
1116 assert_eq!(c.evaluate(500.0), Some((300.0, Some(2))));
1117 }
1118
1119 #[test]
1120 fn clip_maps_time_to_motion() {
1121 let clip = Clip { time: (0.0, 180.0), motion: (0.0, 90.0), params: vec![], curves: vec![] };
1122 assert_eq!(clip.motion_at(20.0), 10.0);
1123 assert_eq!(clip.time_at(10.0), 20.0);
1124 }
1125
1126 #[test]
1127 fn keyframe_transform_rotates_clockwise_about_the_center() {
1128 let map = Affine::keyframe([10.0, 10.0], [100.0, 50.0], [2.0, 1.0], 90.0);
1129 let (x, y) = map.apply((11.0, 10.0));
1130 assert!((x - 100.0).abs() < 1e-9 && (y - 52.0).abs() < 1e-9, "{x} {y}");
1132 let back = map.inverse().unwrap().apply((x, y));
1133 assert!((back.0 - 11.0).abs() < 1e-9 && (back.1 - 10.0).abs() < 1e-9);
1134 }
1135
1136 #[test]
1137 fn value_map_round_trips() {
1138 let entries = vec![
1139 MapEntry { name: "ImageCelName".into(), tag: "2".into(), value: MapValue::Int(1) },
1140 MapEntry { name: "ImageCenter".into(), tag: String::new(), value: MapValue::Pair([190.0, 130.0]) },
1141 MapEntry { name: "Opacity".into(), tag: String::new(), value: MapValue::Double(89.84375) },
1142 ];
1143 let bytes = value_map_bytes(&entries);
1144 assert_eq!(&bytes[8..12], &46u32.to_be_bytes());
1146 assert_eq!(parse_value_map(&bytes).unwrap(), entries);
1147 }
1148
1149 fn cel_mixer(version: &str, keys: &[(f64, &str, i32)]) -> Document {
1152 let double = version == "0110";
1153 let numbers = |v: Vec<f64>| {
1154 if double { Value::DoubleArray(v) } else { Value::SingleArray(v.iter().map(|x| *x as f32).collect()) }
1155 };
1156 let range = |name: &str| {
1157 let mut node = Node::new(name, Value::Null);
1158 node.children = vec![
1159 Node::new("Start", Value::Double(0.0)),
1160 Node::new("End", Value::Double(90.0)),
1161 Node::new("Rate", Value::Double(60.0)),
1162 ];
1163 node
1164 };
1165 let mut curve = Node::new("FCurve", Value::Null);
1166 curve.attributes.push(("Type".into(), "ImageCelName".into()));
1167 curve.children = vec![
1168 Node::new("Frame", numbers(keys.iter().map(|k| k.0).collect())),
1169 Node::new("Value", numbers(keys.iter().map(|k| k.2 as f64).collect())),
1170 Node::new("Interp", Value::StringArray(vec!["Constant".into(); keys.len()])),
1171 Node::new("Tag", Value::StringArray(keys.iter().map(|k| k.1.to_owned()).collect())),
1172 ];
1173 let mut param = Node::new("Param", Value::Int32(0));
1174 param.attributes = vec![("Name".into(), "ImageCelName".into()), ("Tag".into(), keys[0].1.into())];
1175 let mut anim = Node::new("AnimInfo", Value::Null);
1176 anim.children = vec![param, curve];
1177 let mut clip = Node::new("ActionNodeClip", Value::Null);
1178 clip.attributes.push(("Name".into(), String::new()));
1179 clip.children = vec![range("TimeClip"), range("MotionClip"), anim];
1180 let mut general = Node::new("General", Value::Null);
1181 general.children = vec![clip];
1182 let mut root = Node::new("celsysdocument", Value::Null);
1183 root.children = vec![general];
1184 Document { version: version.into(), root }
1185 }
1186
1187 fn fixture() -> (ClipFile, i64) {
1191 use crate::edit::{NewLayer, new_external_id};
1192 let mut file = ClipFile::create(&crate::NewCanvas { width: 8, height: 8, ..Default::default() }).unwrap();
1193 let folder = file.add_layer(file.canvas.root_folder, NewLayer::Folder, "anim").unwrap();
1194 for (name, color) in [("A", [255, 0, 0, 255]), ("B", [0, 0, 255, 255])] {
1195 let cel = file.add_layer(folder, NewLayer::Raster, name).unwrap();
1196 let mut image = crate::RgbaImage::new(8, 8);
1197 image.fill(color);
1198 file.set_layer_pixels(cel, &image, (0, 0)).unwrap();
1199 }
1200 let uuid = file.layers.get(folder).unwrap().uuid.clone().unwrap().replace('-', "");
1201 let uuid: Vec<u8> = (0..16).map(|i| u8::from_str_radix(&uuid[2 * i..2 * i + 2], 16).unwrap()).collect();
1202 let ids = [new_external_id(), new_external_id()];
1203 let keys = [(0.0, "A", 0), (7.5, "B", 1)];
1204 let mut sizes = Vec::new();
1205 for (id, version) in ids.iter().zip(["0100", "0110"]) {
1206 let bytes = cel_mixer(version, &keys).to_bytes().unwrap();
1207 sizes.push(bytes.len() as i64);
1208 file.set_external(id, cmt::pack(&bytes).unwrap());
1209 }
1210 let db = file.database();
1211 db.execute_batch(
1212 "ALTER TABLE Layer ADD COLUMN AnimationFolder INTEGER DEFAULT NULL;
1213 UPDATE Layer SET AnimationFolder = 1 WHERE MainId = (SELECT MAX(MainId) FROM Layer WHERE LayerFolder = 1);
1214 UPDATE AnimationCutBank SET FirstTimeLine = 1, CurrentIndex = 0, Enable = 1;
1215 CREATE TABLE TimeLine(_PW_ID INTEGER PRIMARY KEY AUTOINCREMENT, MainId INTEGER, NextTimeLine INTEGER,
1216 FirstTrack INTEGER, TimeLineName TEXT, FrameRate REAL, StartFrame REAL, EndFrame REAL,
1217 CurrentFrame REAL, BiggestEndFrame REAL, LabelFirstIndex INTEGER);
1218 INSERT INTO TimeLine(MainId, NextTimeLine, FirstTrack, TimeLineName, FrameRate, StartFrame, EndFrame,
1219 CurrentFrame, BiggestEndFrame, LabelFirstIndex) VALUES (1, 0, 1, 't', 24, 0, 6, 0, 6, 0);
1220 CREATE TABLE Track(_PW_ID INTEGER PRIMARY KEY AUTOINCREMENT, MainId INTEGER, ItemId INTEGER,
1221 TrackNextIndex INTEGER, TrackKind INTEGER, LayerUuidWithTrack BLOB, TrackActionMixer BLOB,
1222 TrackActionMixerSize INTEGER, TrackActionMixer2 BLOB, TrackActionMixer2Size INTEGER,
1223 TrackValueMap BLOB, TrackLabelFirstIndex INTEGER);",
1224 )
1225 .unwrap();
1226 db.execute(
1227 "INSERT INTO Track(MainId, ItemId, TrackNextIndex, TrackKind, LayerUuidWithTrack, TrackActionMixer,
1228 TrackActionMixerSize, TrackActionMixer2, TrackActionMixer2Size, TrackLabelFirstIndex)
1229 VALUES (1, 1, 0, 2000, ?1, ?2, ?3, ?4, ?5, 0)",
1230 (uuid, ids[0].as_bytes(), sizes[0], ids[1].as_bytes(), sizes[1]),
1231 )
1232 .unwrap();
1233 file.reload().unwrap();
1234 (file, folder)
1235 }
1236
1237 fn shown(file: &ClipFile, frame: f64) -> [u8; 4] {
1238 let animation = Animation::read(file).unwrap().unwrap();
1239 let state = animation.frame_state(file, 0, frame).unwrap();
1240 let (image, _) = crate::render::render_frame(file, &state, Default::default()).unwrap();
1241 image.pixel(4, 4)
1242 }
1243
1244 #[test]
1245 fn cels_switch_by_frame_and_can_be_rekeyed() {
1246 let (mut file, folder) = fixture();
1247 let animation = Animation::read(&file).unwrap().unwrap();
1248 let track = &animation.timelines[0].tracks[0];
1249 assert_eq!((track.kind, track.layer_id), (TrackKind::Cels, Some(folder)));
1250 assert_eq!(shown(&file, 0.0), [255, 0, 0, 255]);
1251 assert_eq!(shown(&file, 2.0), [255, 0, 0, 255]);
1252 assert_eq!(shown(&file, 3.0), [0, 0, 255, 255]);
1253
1254 file.set_cel_keys(1, folder, &[(0, "B".into()), (4, "A".into())]).unwrap();
1255 file.set_frame_rate(1, 12.0).unwrap();
1256 file.set_frame_range(1, 1, 5).unwrap();
1257 let file = ClipFile::from_bytes(file.to_bytes().unwrap()).unwrap();
1258 let animation = Animation::read(&file).unwrap().unwrap();
1259 let timeline = &animation.timelines[0];
1260 assert_eq!((timeline.frame_rate, timeline.start_frame, timeline.end_frame), (12.0, 1.0, 5.0));
1261 let clip = timeline.tracks[0].clip.as_ref().unwrap();
1262 assert_eq!(clip.time, (0.0, 180.0));
1264 let keys: Vec<(f64, Option<&str>)> = clip.curves[0].keys.iter().map(|k| (k.frame, k.tag.as_deref())).collect();
1265 assert_eq!(keys, [(0.0, Some("B")), (10.0, Some("A"))]);
1266 for (_, document) in timeline.tracks[0].mixers.iter().flatten() {
1268 assert_eq!(Clip::from_document(document).unwrap().curves[0].keys.len(), 2);
1269 }
1270 assert_eq!(timeline.tracks[0].value_map[0].tag, "B");
1271 assert_eq!(shown(&file, 3.0), [0, 0, 255, 255]);
1272 assert_eq!(shown(&file, 4.0), [255, 0, 0, 255]);
1273 }
1274
1275 #[test]
1278 #[ignore]
1279 fn write_python_fixture() {
1280 let (mut file, _) = fixture();
1281 file.update_preview().unwrap();
1282 file.save(concat!(env!("CARGO_MANIFEST_DIR"), "/../../python/tests/data/anim.clip")).unwrap();
1283 }
1284
1285 #[test]
1286 fn opacity_is_quantized_to_256ths() {
1287 assert_eq!(opacity_256(89.84375), 230);
1288 assert_eq!(opacity_256(100.0), 256);
1289 }
1290}