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
3 changes: 2 additions & 1 deletion packages/web-renderer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"formatting": "prettier --experimental-cli src --check",
"lint": "eslint src",
"make": "tsc -d && bun --env-file=../.env.bundle bundle.ts",
"testwebrenderer": "vitest src/test --browser --run"
"testwebrenderer": "vitest src/test --browser --run",
"studio": "cd ../example && bunx remotion studio ../web-renderer/src/test/studio.ts"
},
"author": "Remotion <jonny@remotion.dev>",
"license": "UNLICENSED",
Expand Down
19 changes: 12 additions & 7 deletions packages/web-renderer/src/compose-canvas.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import {calculateTransforms} from './calculate-transforms';
import {turnSvgIntoDrawable} from './compose-svg';

export const composeCanvas = (
canvas: HTMLCanvasElement | HTMLImageElement,
export const composeCanvas = async (
canvas: HTMLCanvasElement | HTMLImageElement | SVGSVGElement,
context: OffscreenCanvasRenderingContext2D,
) => {
const {totalMatrix, reset, dimensions, nativeTransformOrigin} =
calculateTransforms(canvas);
const {totalMatrix, reset, dimensions} = calculateTransforms(canvas);

const translateX = nativeTransformOrigin.x + dimensions.left;
const translateY = nativeTransformOrigin.y + dimensions.top;
const translateX = dimensions.left + dimensions.width / 2;
const translateY = dimensions.top + dimensions.height / 2;

const matrix = new DOMMatrix()
.translate(translateX, translateY)
.multiply(totalMatrix)
.translate(-translateX, -translateY);

context.setTransform(matrix);
const drawable =
canvas instanceof SVGSVGElement
? await turnSvgIntoDrawable(canvas)
: canvas;

context.drawImage(
canvas,
drawable,
dimensions.left,
dimensions.top,
dimensions.width,
Expand Down
53 changes: 21 additions & 32 deletions packages/web-renderer/src/compose-svg.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,34 @@
import {calculateTransforms} from './calculate-transforms';

type ImgDrawable = {
image: HTMLImageElement;
width: number;
height: number;
left: number;
top: number;
};

export const svgToImageBitmap = (
svg: SVGSVGElement,
): Promise<ImgDrawable | null> => {
const {
dimensions: svgDimensions,
totalMatrix,
reset,
} = calculateTransforms(svg);

export const turnSvgIntoDrawable = (svg: SVGSVGElement) => {
const originalTransform = svg.style.transform;
const originalTransformOrigin = svg.style.transformOrigin;
svg.style.transform = totalMatrix.toString();
svg.style.transformOrigin = '50% 50%';
const originalMarginLeft = svg.style.marginLeft;
const originalMarginRight = svg.style.marginRight;
const originalMarginTop = svg.style.marginTop;
const originalMarginBottom = svg.style.marginBottom;

svg.style.transform = 'none';
svg.style.transformOrigin = '';
// Margins were already included in the positioning calculation,
// so we need to remove them to avoid double counting.
svg.style.marginLeft = '0';
svg.style.marginRight = '0';
svg.style.marginTop = '0';
svg.style.marginBottom = '0';
const svgData = new XMLSerializer().serializeToString(svg);

svg.style.marginLeft = originalMarginLeft;
svg.style.marginRight = originalMarginRight;
svg.style.marginTop = originalMarginTop;
svg.style.marginBottom = originalMarginBottom;
svg.style.transform = originalTransform;
svg.style.transformOrigin = originalTransformOrigin;

reset();

return new Promise<ImgDrawable>((resolve, reject) => {
const image = new Image(svgDimensions.width, svgDimensions.height);
return new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image();
const url = `data:image/svg+xml;base64,${btoa(svgData)}`;

image.onload = function () {
resolve({
image,
width: svgDimensions.width,
height: svgDimensions.height,
left: svgDimensions.left,
top: svgDimensions.top,
});
resolve(image);
};

image.onerror = () => {
Expand Down
18 changes: 1 addition & 17 deletions packages/web-renderer/src/compose.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type {Composable} from './composable';
import {composeCanvas} from './compose-canvas';
import {svgToImageBitmap} from './compose-svg';

export const compose = async ({
composables,
Expand All @@ -20,22 +19,7 @@ export const compose = async ({

// TODO: Consider z-index
for (const composable of composables) {
if (composable.type === 'canvas' || composable.type === 'img') {
composeCanvas(composable.element, context);
} else if (composable.type === 'svg') {
// This already accumulates the transforms of the parent
const imageBitmap = await svgToImageBitmap(composable.element);

if (imageBitmap) {
context.drawImage(
imageBitmap.image,
imageBitmap.left,
imageBitmap.top,
imageBitmap.width,
imageBitmap.height,
);
}
}
await composeCanvas(composable.element, context);
}

return canvas;
Expand Down
14 changes: 14 additions & 0 deletions packages/web-renderer/src/test/Root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// bun run studio
import React from 'react';
import {Composition} from 'remotion';
import {evenHarderCase} from './fixtures';
import {transformOriginFixture} from './transform-origin-fixture';

export const Root: React.FC = () => {
return (
<>
<Composition {...evenHarderCase} />
<Composition {...transformOriginFixture} />
</>
);
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 87 additions & 0 deletions packages/web-renderer/src/test/fixtures.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {AbsoluteFill} from 'remotion';

const Component: React.FC = () => {
return (
<AbsoluteFill>
<AbsoluteFill
style={{
width: 1080,
height: 1080,
justifyContent: 'center',
alignItems: 'center',
}}
>
<div
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
}}
>
<div
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
display: 'flex',
}}
>
<div
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
display: 'flex',
transformOrigin: 'center center',
transform:
'scale(0.68) translateX(-525.043px) translateY(52.1668px)',
}}
>
<div
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<svg
width={104.4}
height={104.4}
viewBox="0 0 104.39999999999999 104.39999999999999"
xmlns="http://www.w3.org/2000/svg"
style={{
marginLeft: -19.4897,
}}
>
<path
d="M 52.199999999999996 0 C 86.46666752667024 0 104.39999999999999 17.933332473329756 104.39999999999999 52.199999999999996 C 104.39999999999999 86.46666752667024 86.46666752667024 104.39999999999999 52.199999999999996 104.39999999999999 C 17.933332473329756 104.39999999999999 0 86.46666752667024 0 52.199999999999996 C 0 17.933332473329756 17.933332473329756 0 52.199999999999996 0 Z"
fill="rgba(216, 68, 30, 1)"
/>
</svg>
</div>
</div>
</div>
</div>
</AbsoluteFill>
</AbsoluteFill>
);
};

export const evenHarderCase = {
component: Component,
id: 'even-harder-case',
width: 550,
height: 700,
fps: 30,
durationInFrames: 100,
} as const;
4 changes: 4 additions & 0 deletions packages/web-renderer/src/test/studio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {registerRoot} from 'remotion';
import {Root} from './Root';

registerRoot(Root);
33 changes: 33 additions & 0 deletions packages/web-renderer/src/test/transform-origin-fixture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {AbsoluteFill} from 'remotion';

const Component: React.FC = () => {
return (
<AbsoluteFill
style={{
transform: 'rotate(25deg)',
transformOrigin: '10px 10px',
}}
>
<svg
viewBox="0 0 100 100"
width="100"
height="100"
style={{
transform: 'rotate(-25deg) scale(0.8)',
transformOrigin: '90px 10px',
}}
>
<polygon points="50,10 90,90 10,90" fill="orange" />
</svg>
</AbsoluteFill>
);
};

export const transformOriginFixture = {
component: Component,
id: 'transform-origin-fixture',
width: 100,
height: 100,
fps: 30,
durationInFrames: 100,
} as const;
Loading
Loading