1use crate::analysis::{AnalysisConfig, analyze_frames};
4use crate::decoders::BufferedDecoder;
5use crate::error::Result;
6use crate::hashers::DHasher;
7use crate::segment::{apply_operations, apply_segment_operations};
8use crate::traits::{FrameHasher, GifDecoder, GifEncoder};
9use crate::types::{
10 AnalyzedFrame, EncodableFrame, EncodeConfig, FrameOps, GifMetadata, Segment, SegmentOp,
11 SegmentOps,
12};
13use std::path::Path;
14use std::sync::Arc;
15
16#[cfg(feature = "parallel")]
17use crate::analysis::analyze_frames_parallel;
18
19pub type ProgressCallback = Arc<dyn Fn(usize, usize) + Send + Sync>;
22
23#[derive(Clone)]
62pub struct Figif<H: FrameHasher = DHasher> {
63 hasher: H,
64 config: AnalysisConfig,
65 decoder: BufferedDecoder,
66 progress_callback: Option<ProgressCallback>,
67}
68
69impl<H: FrameHasher + std::fmt::Debug> std::fmt::Debug for Figif<H> {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_struct("Figif")
72 .field("hasher", &self.hasher)
73 .field("config", &self.config)
74 .field("decoder", &self.decoder)
75 .field(
76 "progress_callback",
77 &self.progress_callback.as_ref().map(|_| "Some(callback)"),
78 )
79 .finish()
80 }
81}
82
83impl Default for Figif<DHasher> {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89impl Figif<DHasher> {
90 pub fn new() -> Self {
94 Self {
95 hasher: DHasher::new(),
96 config: AnalysisConfig::default(),
97 decoder: BufferedDecoder::new(),
98 progress_callback: None,
99 }
100 }
101}
102
103impl<H: FrameHasher> Figif<H> {
104 pub fn with_hasher<H2: FrameHasher>(self, hasher: H2) -> Figif<H2> {
114 Figif {
115 hasher,
116 config: self.config,
117 decoder: self.decoder,
118 progress_callback: self.progress_callback,
119 }
120 }
121
122 pub fn with_progress_callback(mut self, callback: ProgressCallback) -> Self {
124 self.progress_callback = Some(callback);
125 self
126 }
127
128 pub fn similarity_threshold(mut self, threshold: u32) -> Self {
133 self.config.similarity_threshold = threshold;
134 self
135 }
136
137 pub fn min_segment_frames(mut self, min: usize) -> Self {
141 self.config.min_segment_frames = min.max(1);
142 self
143 }
144
145 pub fn detect_static(mut self, enabled: bool) -> Self {
150 self.config.detect_static = enabled;
151 self
152 }
153
154 pub fn identical_threshold(mut self, threshold: u32) -> Self {
159 self.config.identical_threshold = threshold;
160 self
161 }
162
163 pub fn memory_limit(mut self, limit: usize) -> Self {
165 self.decoder = self.decoder.with_memory_limit(limit);
166 self
167 }
168
169 pub fn config(&self) -> &AnalysisConfig {
171 &self.config
172 }
173
174 pub fn hasher(&self) -> &H {
176 &self.hasher
177 }
178
179 pub fn analyze_file(&self, path: impl AsRef<Path>) -> Result<Analysis<H::Hash>>
183 where
184 H::Hash: Send,
185 {
186 #[cfg(feature = "parallel")]
187 {
188 self.analyze_file_parallel(path)
189 }
190 #[cfg(not(feature = "parallel"))]
191 {
192 let frames: Vec<_> = self
193 .decoder
194 .decode_file(path)?
195 .collect::<Result<Vec<_>>>()?;
196 self.analyze_frames(frames)
197 }
198 }
199
200 pub fn analyze_bytes(&self, data: &[u8]) -> Result<Analysis<H::Hash>>
202 where
203 H::Hash: Send,
204 {
205 #[cfg(feature = "parallel")]
206 {
207 self.analyze_bytes_parallel(data)
208 }
209 #[cfg(not(feature = "parallel"))]
210 {
211 let frames: Vec<_> = self
212 .decoder
213 .decode_bytes(data)?
214 .collect::<Result<Vec<_>>>()?;
215 self.analyze_frames(frames)
216 }
217 }
218
219 pub fn analyze_frames(
221 &self,
222 frames: Vec<crate::types::DecodedFrame>,
223 ) -> Result<Analysis<H::Hash>> {
224 let metadata = if frames.is_empty() {
226 GifMetadata {
227 width: 0,
228 height: 0,
229 frame_count: 0,
230 total_duration_ms: 0,
231 has_transparency: false,
232 loop_count: crate::types::LoopCount::Infinite,
233 global_palette: None,
234 }
235 } else {
236 let (width, height) = frames[0].image.dimensions();
237 let total_duration_ms: u64 = frames.iter().map(|f| f.delay_ms() as u64).sum();
238 GifMetadata {
239 width: width as u16,
240 height: height as u16,
241 frame_count: frames.len(),
242 total_duration_ms,
243 has_transparency: true, loop_count: crate::types::LoopCount::Infinite,
245 global_palette: None,
246 }
247 };
248
249 let progress = self
251 .progress_callback
252 .as_ref()
253 .map(|c| c.as_ref() as &(dyn Fn(usize, usize) + Send + Sync));
254 let (analyzed_frames, segments) =
255 analyze_frames(frames, &self.hasher, &self.config, progress);
256
257 Ok(Analysis {
258 metadata,
259 frames: analyzed_frames,
260 segments,
261 })
262 }
263
264 #[cfg(feature = "parallel")]
268 pub fn analyze_file_parallel(&self, path: impl AsRef<Path>) -> Result<Analysis<H::Hash>>
269 where
270 H::Hash: Send,
271 {
272 let frames: Vec<_> = self
273 .decoder
274 .decode_file(path)?
275 .collect::<Result<Vec<_>>>()?;
276 self.analyze_frames_parallel(frames)
277 }
278
279 #[cfg(feature = "parallel")]
281 pub fn analyze_bytes_parallel(&self, data: &[u8]) -> Result<Analysis<H::Hash>>
282 where
283 H::Hash: Send,
284 {
285 let frames: Vec<_> = self
286 .decoder
287 .decode_bytes(data)?
288 .collect::<Result<Vec<_>>>()?;
289 self.analyze_frames_parallel(frames)
290 }
291
292 #[cfg(feature = "parallel")]
294 pub fn analyze_frames_parallel(
295 &self,
296 frames: Vec<crate::types::DecodedFrame>,
297 ) -> Result<Analysis<H::Hash>>
298 where
299 H::Hash: Send,
300 {
301 let metadata = if frames.is_empty() {
302 GifMetadata {
303 width: 0,
304 height: 0,
305 frame_count: 0,
306 total_duration_ms: 0,
307 has_transparency: false,
308 loop_count: crate::types::LoopCount::Infinite,
309 global_palette: None,
310 }
311 } else {
312 let (width, height) = frames[0].image.dimensions();
313 let total_duration_ms: u64 = frames.iter().map(|f| f.delay_ms() as u64).sum();
314 GifMetadata {
315 width: width as u16,
316 height: height as u16,
317 frame_count: frames.len(),
318 total_duration_ms,
319 has_transparency: true,
320 loop_count: crate::types::LoopCount::Infinite,
321 global_palette: None,
322 }
323 };
324
325 let progress = self
326 .progress_callback
327 .as_ref()
328 .map(|c| c.as_ref() as &(dyn Fn(usize, usize) + Send + Sync));
329 let (analyzed_frames, segments) =
330 analyze_frames_parallel(frames, &self.hasher, &self.config, progress);
331
332 Ok(Analysis {
333 metadata,
334 frames: analyzed_frames,
335 segments,
336 })
337 }
338}
339
340#[derive(Debug, Clone)]
342pub struct Analysis<H> {
343 pub metadata: GifMetadata,
345 pub frames: Vec<AnalyzedFrame<H>>,
347 pub segments: Vec<Segment>,
349}
350
351impl<H: Clone + Sync + Send> Analysis<H> {
352 pub fn frame_count(&self) -> usize {
354 self.frames.len()
355 }
356
357 pub fn segment_count(&self) -> usize {
359 self.segments.len()
360 }
361
362 pub fn total_duration_ms(&self) -> u64 {
364 self.metadata.total_duration_ms
365 }
366
367 pub fn static_segments(&self) -> Vec<&Segment> {
369 self.segments.iter().filter(|s| s.is_static).collect()
370 }
371
372 pub fn apply_operations(&self, ops: &SegmentOps) -> Vec<EncodableFrame> {
376 apply_segment_operations(&self.frames, &self.segments, ops)
377 }
378
379 pub fn export<E: GifEncoder>(
381 &self,
382 encoder: &E,
383 ops: &SegmentOps,
384 config: &EncodeConfig,
385 ) -> Result<Vec<u8>> {
386 let frames = self.apply_operations(ops);
387 encoder.encode(&frames, config)
388 }
389
390 pub fn export_to_file<E: GifEncoder>(
392 &self,
393 encoder: &E,
394 ops: &SegmentOps,
395 path: impl AsRef<Path>,
396 config: &EncodeConfig,
397 ) -> Result<()> {
398 let frames = self.apply_operations(ops);
399 encoder.encode_to_file(&frames, path, config)
400 }
401
402 pub fn apply_all_operations(
409 &self,
410 segment_ops: &SegmentOps,
411 frame_ops: &FrameOps,
412 ) -> Vec<EncodableFrame> {
413 apply_operations(&self.frames, &self.segments, segment_ops, frame_ops)
414 }
415
416 pub fn calculate_impact(&self, segment_ops: &SegmentOps, frame_ops: &FrameOps) -> (usize, u64) {
420 let (frames, cs) = crate::segment::dry_run_all_operations(
421 &self.frames,
422 &self.segments,
423 segment_ops,
424 frame_ops,
425 );
426 (frames, cs * 10)
427 }
428
429 pub fn export_with_frame_ops<E: GifEncoder>(
431 &self,
432 encoder: &E,
433 segment_ops: &SegmentOps,
434 frame_ops: &FrameOps,
435 config: &EncodeConfig,
436 ) -> Result<Vec<u8>> {
437 let frames = self.apply_all_operations(segment_ops, frame_ops);
438 encoder.encode(&frames, config)
439 }
440
441 pub fn export_to_file_with_frame_ops<E: GifEncoder>(
443 &self,
444 encoder: &E,
445 segment_ops: &SegmentOps,
446 frame_ops: &FrameOps,
447 path: impl AsRef<Path>,
448 config: &EncodeConfig,
449 ) -> Result<()> {
450 let frames = self.apply_all_operations(segment_ops, frame_ops);
451 encoder.encode_to_file(&frames, path, config)
452 }
453
454 pub fn split_segments(&self, frame_ops: &FrameOps) -> Analysis<H> {
460 let (new_frames, new_segments) =
461 crate::segment::split_segments_at_points(&self.frames, &self.segments, frame_ops);
462
463 Analysis {
464 metadata: self.metadata.clone(),
465 frames: new_frames,
466 segments: new_segments,
467 }
468 }
469
470 pub fn as_encodable(&self) -> Vec<EncodableFrame> {
472 self.frames
473 .iter()
474 .map(|f| EncodableFrame::from_decoded(&f.frame))
475 .collect()
476 }
477
478 pub fn pauses(&self) -> crate::selector::SegmentSelector<'_> {
498 let segments = self.segments.iter().filter(|s| s.is_static).collect();
499 crate::selector::SegmentSelector::new(segments)
500 }
501
502 pub fn motion(&self) -> crate::selector::SegmentSelector<'_> {
511 let segments = self.segments.iter().filter(|s| !s.is_static).collect();
512 crate::selector::SegmentSelector::new(segments)
513 }
514
515 pub fn all(&self) -> crate::selector::SegmentSelector<'_> {
524 let segments = self.segments.iter().collect();
525 crate::selector::SegmentSelector::new(segments)
526 }
527
528 pub fn segment(&self, id: usize) -> crate::selector::SegmentSelector<'_> {
537 let segments = self.segments.iter().filter(|s| s.id == id).collect();
538 crate::selector::SegmentSelector::new(segments)
539 }
540
541 pub fn segments_by_id(&self, ids: &[usize]) -> crate::selector::SegmentSelector<'_> {
550 let segments = self
551 .segments
552 .iter()
553 .filter(|s| ids.contains(&s.id))
554 .collect();
555 crate::selector::SegmentSelector::new(segments)
556 }
557
558 pub fn frames_range(
569 &self,
570 range: std::ops::Range<usize>,
571 ) -> crate::selector::SegmentSelector<'_> {
572 let segments = self
573 .segments
574 .iter()
575 .filter(|s| {
576 s.frame_range.start < range.end && s.frame_range.end > range.start
578 })
579 .collect();
580 crate::selector::SegmentSelector::new(segments)
581 }
582
583 pub fn cap_pauses(&self, max_ms: u32) -> SegmentOps {
600 let max_cs = (max_ms / 10) as u16;
601 let mut ops = SegmentOps::new();
602
603 for segment in &self.segments {
604 if segment.is_static && segment.total_duration_cs > max_cs {
605 ops.insert(segment.id, SegmentOp::Collapse { delay_cs: max_cs });
606 }
607 }
608
609 ops
610 }
611
612 pub fn collapse_all_pauses(&self, duration_ms: u32) -> SegmentOps {
624 let delay_cs = (duration_ms / 10) as u16;
625 let mut ops = SegmentOps::new();
626
627 for segment in &self.segments {
628 if segment.is_static {
629 ops.insert(segment.id, SegmentOp::Collapse { delay_cs });
630 }
631 }
632
633 ops
634 }
635
636 pub fn remove_long_pauses(&self, min_ms: u32) -> SegmentOps {
645 let min_cs = (min_ms / 10) as u16;
646 let mut ops = SegmentOps::new();
647
648 for segment in &self.segments {
649 if segment.is_static && segment.total_duration_cs >= min_cs {
650 ops.insert(segment.id, SegmentOp::Remove);
651 }
652 }
653
654 ops
655 }
656
657 pub fn speed_up_pauses(&self, factor: f64) -> SegmentOps {
669 let mut ops = SegmentOps::new();
670
671 for segment in &self.segments {
672 if segment.is_static {
673 ops.insert(
674 segment.id,
675 SegmentOp::Scale {
676 factor: 1.0 / factor,
677 },
678 );
679 }
680 }
681
682 ops
683 }
684
685 pub fn speed_up_all(&self, factor: f64) -> SegmentOps {
696 let mut ops = SegmentOps::new();
697
698 for segment in &self.segments {
699 ops.insert(
700 segment.id,
701 SegmentOp::Scale {
702 factor: 1.0 / factor,
703 },
704 );
705 }
706
707 ops
708 }
709
710 pub fn target_duration(&self, target_ms: u64) -> Option<SegmentOps> {
727 let current_ms = self.total_duration_ms();
728 if current_ms <= target_ms {
729 return Some(SegmentOps::new()); }
731
732 let to_remove_ms = current_ms - target_ms;
734
735 let static_duration_ms: u64 = self
737 .segments
738 .iter()
739 .filter(|s| s.is_static)
740 .map(|s| s.duration_ms() as u64)
741 .sum();
742
743 if static_duration_ms < to_remove_ms {
745 return None;
746 }
747
748 let mut static_segs: Vec<_> = self.segments.iter().filter(|s| s.is_static).collect();
750 static_segs.sort_by(|a, b| b.total_duration_cs.cmp(&a.total_duration_cs));
751
752 let mut ops = SegmentOps::new();
753 let mut removed_ms: u64 = 0;
754 let min_pause_ms = 100; for segment in static_segs {
757 if removed_ms >= to_remove_ms {
758 break;
759 }
760
761 let segment_ms = segment.duration_ms() as u64;
762 let can_remove = segment_ms.saturating_sub(min_pause_ms as u64);
763
764 if can_remove > 0 {
765 let to_remove_from_this = can_remove.min(to_remove_ms - removed_ms);
766 let new_duration_ms = segment_ms - to_remove_from_this;
767
768 if new_duration_ms <= min_pause_ms as u64 {
769 ops.insert(
771 segment.id,
772 SegmentOp::Collapse {
773 delay_cs: (min_pause_ms / 10) as u16,
774 },
775 );
776 } else {
777 ops.insert(
779 segment.id,
780 SegmentOp::Collapse {
781 delay_cs: (new_duration_ms / 10) as u16,
782 },
783 );
784 }
785
786 removed_ms += to_remove_from_this;
787 }
788 }
789
790 Some(ops)
791 }
792
793 pub fn merge_ops(op_sets: &[SegmentOps]) -> SegmentOps {
803 let mut merged = SegmentOps::new();
804 for ops in op_sets {
805 merged.extend(ops.iter().map(|(k, v)| (*k, v.clone())));
806 }
807 merged
808 }
809}