Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/compositor-view-napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ pub struct GifParamsInput {
pub width: Option<u32>,
pub height: Option<u32>,
pub fps: Option<u32>,
/// Compteur de loop GIF : `None` ou `0` = infini, sinon `n` boucles.
/// Nombre de lectures du GIF : `None` ou `0` = en boucle, `1` = une fois, `n` = n fois.
pub loop_count: Option<u16>,
/// Floyd-Steinberg error diffusion à la quantification. Actif par défaut :
/// sans lui, un fond en dégradé se découpe en bandes (cf. `GifExportParams`).
Expand Down
35 changes: 25 additions & 10 deletions crates/compositor/src/gif_export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ pub struct GifExportParams {
pub width: Option<u32>,
pub height: Option<u32>,
pub fps: Option<u32>,
/// `None` or `0` → infinite loop (the historical GIF default).
/// Otherwise finite count.
/// How many times the animation plays: `None` or `0` → forever (the
/// historical GIF default), `1` → once, `n` → n times.
pub loop_count: Option<u16>,
/// Floyd-Steinberg dithering at quantization. Default ON: the median
/// cut spreads its 256 entries over the whole frame, so a gradient
Expand All @@ -156,6 +156,18 @@ impl Default for GifExportParams {
}
}

/// The count to write in the Netscape 2.0 loop block for `loop_count` plays,
/// or `None` for no block. Viewers read the block's count as repeats after
/// the first play (Chromium, Firefox, ffmpeg's `-loop`), and 0 as forever, so
/// a GIF that plays once must carry no block at all: a count of 1 plays twice.
fn netscape_repeats(loop_count: Option<u16>) -> Option<u16> {
match loop_count {
None | Some(0) => Some(0),
Some(1) => None,
Some(plays) => Some(plays - 1),
}
}

/// Drive a multiclip GIF export end-to-end.
///
/// Deliberately the same shape as `pipeline::run_composited_multi`, and
Expand Down Expand Up @@ -238,14 +250,9 @@ fn export_gif_inner(
let frames = {
let mut gw = GifWriter::new(&mut writer, width as u16, height as u16)?;
gw.write_header()?;
// Netscape 2.0 application extension drives the loop count.
// 0 = infinite; some viewers also treat 0 as infinite, so we
// keep that as the "default".
let loops = match params.loop_count {
None | Some(0) => 0u16,
Some(n) => n,
};
gw.write_netscape_loop(loops)?;
if let Some(repeats) = netscape_repeats(params.loop_count) {
gw.write_netscape_loop(repeats)?;
}

let mut screen_decs: HashMap<String, Decoder> = HashMap::new();
let mut webcam_decs: HashMap<String, Decoder> = HashMap::new();
Expand Down Expand Up @@ -1124,6 +1131,14 @@ mod tests {
use super::*;
use std::io::Cursor;

#[test]
fn a_gif_that_plays_once_carries_no_loop_block() {
assert_eq!(netscape_repeats(None), Some(0));
assert_eq!(netscape_repeats(Some(0)), Some(0));
assert_eq!(netscape_repeats(Some(1)), None);
assert_eq!(netscape_repeats(Some(3)), Some(2));
}

#[test]
fn frame_delays_sum_to_programme_duration() {
for fps in [15u32, 30, 12, 20, 25, 24, 60] {
Expand Down
2 changes: 1 addition & 1 deletion electron/native/compositor-view/addon.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export interface GifParamsInput {
width?: number;
height?: number;
fps?: number;
/** GIF loop count: `0` or omitted = infinite, else `n` finite loops. */
/** How many times the GIF plays: `0` or omitted = forever, `1` = once, `n` = n times. */
loopCount?: number;
/** Floyd-Steinberg error diffusion at quantization. On by default. */
dither?: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/native/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export interface CompositorExportGifParams {
width?: number;
height?: number;
fps?: number;
/** Compteur de loop GIF : `0` ou omis = infini, sinon `n` boucles finies. */
/** Nombre de lectures du GIF : `0` ou omis = en boucle, `1` = une fois, `n` = n fois. */
loopCount?: number;
/** Floyd-Steinberg error diffusion à la quantification. `true` par défaut. */
dither?: boolean;
Expand Down
Loading