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
38 changes: 11 additions & 27 deletions src/core/animations/effect-preset-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,19 @@ export class EffectPresetBuilder {
case "slideUp":
case "slideDown": {
const isHorizontal = effectName === "slideLeft" || effectName === "slideRight";
const startsPositive = effectName === "slideLeft" || effectName === "slideUp";
// Left and down start on the positive side of their axis (offset y points up).
const startsPositive = effectName === "slideLeft" || effectName === "slideDown";

const fittedSize = this.getFittedSize(editSize, clipSize);
// The render checks for overflow with the media fitted to the frame's other side, whatever the clip's fit.
const editDimension = isHorizontal ? editSize.width : editSize.height;
const fittedDimension = isHorizontal ? fittedSize.width : fittedSize.height;
const fittedDimension = isHorizontal
? (clipSize.width / clipSize.height) * editSize.height
: (clipSize.height / clipSize.width) * editSize.width;

let targetOffset = this.getSlideStart();
const minScale = editDimension + editDimension * targetOffset * 2;
const scaleFactor = 1 + targetOffset * 2;

if (fittedDimension < minScale) {
const scaleFactor = minScale / fittedDimension;
if (fittedDimension < editDimension * scaleFactor) {
scaleKeyframes.push({ from: scaleFactor, to: scaleFactor, start, length, interpolation: "linear" });
} else {
targetOffset = (fittedDimension - editDimension) / 2 / editDimension;
Expand Down Expand Up @@ -111,36 +113,18 @@ export class EffectPresetBuilder {
private getSlideStart(): number {
const { name, speed } = this.effectPreset;

// Fractions of the frame, matching the render.
if (name.startsWith("slide")) {
switch (speed) {
case "Slow":
return 0.03;
case "Fast":
return 0.2;
default:
return 0.12;
default:
return 0.08;
}
}

return 0;
}

private getFittedSize(editSize: Size, clipSize: Size): Size {
const fit = this.clipConfiguration.fit ?? "crop";

switch (fit) {
case "cover":
case "crop": {
const scale = Math.max(editSize.width / clipSize.width, editSize.height / clipSize.height);
return { width: clipSize.width * scale, height: clipSize.height * scale };
}
case "contain": {
const scale = Math.min(editSize.width / clipSize.width, editSize.height / clipSize.height);
return { width: clipSize.width * scale, height: clipSize.height * scale };
}
case "none":
default:
return clipSize;
}
}
}
48 changes: 48 additions & 0 deletions tests/effect-preset-builder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { EffectPresetBuilder } from "@animations/effect-preset-builder";
import { sec } from "@core/timing/types";
import type { ResolvedClip } from "@schemas";

const EDIT = { width: 1920, height: 1080 };

const build = (effect: string, clipSize = EDIT, fit: ResolvedClip["fit"] = "crop") =>
new EffectPresetBuilder({
id: "clip-1",
asset: { type: "image", src: "https://example.com/a.png" },
start: sec(0),
length: sec(5),
fit,
effect: effect as ResolvedClip["effect"]
}).buildRelative(EDIT, clipSize);

describe("EffectPresetBuilder", () => {
it.each([
["slideLeft", 0.08],
["slideLeftSlow", 0.03],
["slideLeftFast", 0.12]
])("pans %s by the render's %s of the frame, enlarged to cover it", (effect, distance) => {
const { offsetXKeyframes, scaleKeyframes } = build(effect);

expect(offsetXKeyframes).toEqual([{ from: distance, to: -distance, start: 0, length: 5 }]);
expect(scaleKeyframes[0]).toMatchObject({ from: 1 + 2 * distance, to: 1 + 2 * distance });
});

it.each([
["slideUp", -0.08, 0.08],
["slideDown", 0.08, -0.08]
])("pans %s the way its name says (offset y points up)", (effect, from, to) => {
expect(build(effect).offsetYKeyframes).toEqual([{ from, to, start: 0, length: 5 }]);
});

it("enlarges narrow media by the same amount whatever its fit", () => {
const { scaleKeyframes } = build("slideLeft", { width: 1080, height: 1920 }, "contain");

expect(scaleKeyframes[0]).toMatchObject({ from: 1.16, to: 1.16 });
});

it.each(["crop", "contain"] as const)("pans the overflow of media fitted to the frame's height (%s)", fit => {
const { offsetXKeyframes, scaleKeyframes } = build("slideRight", { width: 4000, height: 1000 }, fit);

expect(offsetXKeyframes).toEqual([{ from: -0.625, to: 0.625, start: 0, length: 5 }]);
expect(scaleKeyframes).toEqual([]);
});
});
Loading