padding video control
This commit is contained in:
@@ -78,7 +78,7 @@ const ZOOM_DEPTH_OPTIONS: Array<{ depth: ZoomDepth; label: string }> = [
|
|||||||
{ depth: 6, label: "5×" },
|
{ depth: 6, label: "5×" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth, onZoomDepthChange, selectedZoomId, onZoomDelete, shadowIntensity = 0, onShadowChange, showBlur, onBlurChange, motionBlurEnabled = true, onMotionBlurChange, borderRadius = 0, onBorderRadiusChange, padding = 0, onPaddingChange, cropRegion, onCropChange, videoElement, onExport }: SettingsPanelProps) {
|
export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth, onZoomDepthChange, selectedZoomId, onZoomDelete, shadowIntensity = 0, onShadowChange, showBlur, onBlurChange, motionBlurEnabled = true, onMotionBlurChange, borderRadius = 0, onBorderRadiusChange, padding = 50, onPaddingChange, cropRegion, onCropChange, videoElement, onExport }: SettingsPanelProps) {
|
||||||
const [wallpaperPaths, setWallpaperPaths] = useState<string[]>([]);
|
const [wallpaperPaths, setWallpaperPaths] = useState<string[]>([]);
|
||||||
const [customImages, setCustomImages] = useState<string[]>([]);
|
const [customImages, setCustomImages] = useState<string[]>([]);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
@@ -265,8 +265,8 @@ export function SettingsPanel({ selected, onWallpaperChange, selectedZoomDepth,
|
|||||||
{/* Padding Slider */}
|
{/* Padding Slider */}
|
||||||
<div className="p-3 rounded-xl bg-white/5 border border-white/5 space-y-2">
|
<div className="p-3 rounded-xl bg-white/5 border border-white/5 space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="text-xs font-medium text-slate-200">Padding (TODO)</div>
|
<div className="text-xs font-medium text-slate-200">Padding</div>
|
||||||
<span className="text-[10px] text-slate-400 font-mono">{padding}px</span>
|
<span className="text-[10px] text-slate-400 font-mono">{padding}%</span>
|
||||||
</div>
|
</div>
|
||||||
<Slider
|
<Slider
|
||||||
value={[padding]}
|
value={[padding]}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function VideoEditor() {
|
|||||||
const [showBlur, setShowBlur] = useState(false);
|
const [showBlur, setShowBlur] = useState(false);
|
||||||
const [motionBlurEnabled, setMotionBlurEnabled] = useState(true);
|
const [motionBlurEnabled, setMotionBlurEnabled] = useState(true);
|
||||||
const [borderRadius, setBorderRadius] = useState(0);
|
const [borderRadius, setBorderRadius] = useState(0);
|
||||||
const [padding, setPadding] = useState(0);
|
const [padding, setPadding] = useState(50);
|
||||||
const [cropRegion, setCropRegion] = useState<CropRegion>(DEFAULT_CROP_REGION);
|
const [cropRegion, setCropRegion] = useState<CropRegion>(DEFAULT_CROP_REGION);
|
||||||
const [zoomRegions, setZoomRegions] = useState<ZoomRegion[]>([]);
|
const [zoomRegions, setZoomRegions] = useState<ZoomRegion[]>([]);
|
||||||
const [selectedZoomId, setSelectedZoomId] = useState<string | null>(null);
|
const [selectedZoomId, setSelectedZoomId] = useState<string | null>(null);
|
||||||
@@ -306,6 +306,7 @@ export default function VideoEditor() {
|
|||||||
showBlur,
|
showBlur,
|
||||||
motionBlurEnabled,
|
motionBlurEnabled,
|
||||||
borderRadius,
|
borderRadius,
|
||||||
|
padding,
|
||||||
cropRegion,
|
cropRegion,
|
||||||
onProgress: (progress: ExportProgress) => {
|
onProgress: (progress: ExportProgress) => {
|
||||||
setExportProgress(progress);
|
setExportProgress(progress);
|
||||||
@@ -347,7 +348,7 @@ export default function VideoEditor() {
|
|||||||
setIsExporting(false);
|
setIsExporting(false);
|
||||||
exporterRef.current = null;
|
exporterRef.current = null;
|
||||||
}
|
}
|
||||||
}, [videoPath, wallpaper, zoomRegions, trimRegions, shadowIntensity, showBlur, motionBlurEnabled, borderRadius, cropRegion, isPlaying]);
|
}, [videoPath, wallpaper, zoomRegions, trimRegions, shadowIntensity, showBlur, motionBlurEnabled, borderRadius, padding, cropRegion, isPlaying]);
|
||||||
|
|
||||||
const handleCancelExport = useCallback(() => {
|
const handleCancelExport = useCallback(() => {
|
||||||
if (exporterRef.current) {
|
if (exporterRef.current) {
|
||||||
@@ -413,6 +414,7 @@ export default function VideoEditor() {
|
|||||||
showBlur={showBlur}
|
showBlur={showBlur}
|
||||||
motionBlurEnabled={motionBlurEnabled}
|
motionBlurEnabled={motionBlurEnabled}
|
||||||
borderRadius={borderRadius}
|
borderRadius={borderRadius}
|
||||||
|
padding={padding}
|
||||||
cropRegion={cropRegion}
|
cropRegion={cropRegion}
|
||||||
trimRegions={trimRegions}
|
trimRegions={trimRegions}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ interface VideoPlaybackProps {
|
|||||||
showBlur?: boolean;
|
showBlur?: boolean;
|
||||||
motionBlurEnabled?: boolean;
|
motionBlurEnabled?: boolean;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
|
padding?: number;
|
||||||
cropRegion?: import('./types').CropRegion;
|
cropRegion?: import('./types').CropRegion;
|
||||||
trimRegions?: TrimRegion[];
|
trimRegions?: TrimRegion[];
|
||||||
}
|
}
|
||||||
@@ -59,6 +60,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
|||||||
showBlur,
|
showBlur,
|
||||||
motionBlurEnabled = true,
|
motionBlurEnabled = true,
|
||||||
borderRadius = 0,
|
borderRadius = 0,
|
||||||
|
padding = 50,
|
||||||
cropRegion,
|
cropRegion,
|
||||||
trimRegions = [],
|
trimRegions = [],
|
||||||
}, ref) => {
|
}, ref) => {
|
||||||
@@ -153,6 +155,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
|||||||
cropRegion,
|
cropRegion,
|
||||||
lockedVideoDimensions: lockedVideoDimensionsRef.current,
|
lockedVideoDimensions: lockedVideoDimensionsRef.current,
|
||||||
borderRadius,
|
borderRadius,
|
||||||
|
padding,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -174,7 +177,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
|||||||
|
|
||||||
updateOverlayForRegion(activeRegion);
|
updateOverlayForRegion(activeRegion);
|
||||||
}
|
}
|
||||||
}, [updateOverlayForRegion, cropRegion, borderRadius]);
|
}, [updateOverlayForRegion, cropRegion, borderRadius, padding]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
layoutVideoContentRef.current = layoutVideoContent;
|
layoutVideoContentRef.current = layoutVideoContent;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ interface LayoutParams {
|
|||||||
cropRegion?: CropRegion;
|
cropRegion?: CropRegion;
|
||||||
lockedVideoDimensions?: { width: number; height: number } | null;
|
lockedVideoDimensions?: { width: number; height: number } | null;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
|
padding?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LayoutResult {
|
interface LayoutResult {
|
||||||
@@ -23,7 +24,7 @@ interface LayoutResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function layoutVideoContent(params: LayoutParams): LayoutResult | null {
|
export function layoutVideoContent(params: LayoutParams): LayoutResult | null {
|
||||||
const { container, app, videoSprite, maskGraphics, videoElement, cropRegion, lockedVideoDimensions, borderRadius = 0 } = params;
|
const { container, app, videoSprite, maskGraphics, videoElement, cropRegion, lockedVideoDimensions, borderRadius = 0, padding = 0 } = params;
|
||||||
|
|
||||||
const videoWidth = lockedVideoDimensions?.width || videoElement.videoWidth;
|
const videoWidth = lockedVideoDimensions?.width || videoElement.videoWidth;
|
||||||
const videoHeight = lockedVideoDimensions?.height || videoElement.videoHeight;
|
const videoHeight = lockedVideoDimensions?.height || videoElement.videoHeight;
|
||||||
@@ -56,8 +57,10 @@ export function layoutVideoContent(params: LayoutParams): LayoutResult | null {
|
|||||||
const cropEndY = cropStartY + croppedVideoHeight;
|
const cropEndY = cropStartY + croppedVideoHeight;
|
||||||
|
|
||||||
// Calculate scale to fit the cropped area in the viewport
|
// Calculate scale to fit the cropped area in the viewport
|
||||||
const maxDisplayWidth = width * VIEWPORT_SCALE;
|
// Padding is a percentage (0-100), where 50 matches the original VIEWPORT_SCALE of 0.8
|
||||||
const maxDisplayHeight = height * VIEWPORT_SCALE;
|
const paddingScale = 1.0 - (padding / 100) * 0.4;
|
||||||
|
const maxDisplayWidth = width * paddingScale;
|
||||||
|
const maxDisplayHeight = height * paddingScale;
|
||||||
|
|
||||||
const scale = Math.min(
|
const scale = Math.min(
|
||||||
maxDisplayWidth / croppedVideoWidth,
|
maxDisplayWidth / croppedVideoWidth,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ interface FrameRenderConfig {
|
|||||||
showBlur: boolean;
|
showBlur: boolean;
|
||||||
motionBlurEnabled?: boolean;
|
motionBlurEnabled?: boolean;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
|
padding?: number;
|
||||||
cropRegion: CropRegion;
|
cropRegion: CropRegion;
|
||||||
videoWidth: number;
|
videoWidth: number;
|
||||||
videoHeight: number;
|
videoHeight: number;
|
||||||
@@ -302,20 +303,10 @@ export class FrameRenderer {
|
|||||||
if (!this.app || !this.videoSprite || !this.maskGraphics || !this.videoContainer) return;
|
if (!this.app || !this.videoSprite || !this.maskGraphics || !this.videoContainer) return;
|
||||||
|
|
||||||
const { width, height } = this.config;
|
const { width, height } = this.config;
|
||||||
const { cropRegion, borderRadius = 0 } = this.config;
|
const { cropRegion, borderRadius = 0, padding = 0 } = this.config;
|
||||||
const videoWidth = this.config.videoWidth;
|
const videoWidth = this.config.videoWidth;
|
||||||
const videoHeight = this.config.videoHeight;
|
const videoHeight = this.config.videoHeight;
|
||||||
|
|
||||||
// Log layout calculation once (only on first layout)
|
|
||||||
if (!this.layoutCache) {
|
|
||||||
console.log('[FrameRenderer] Initial updateLayout', {
|
|
||||||
canvasSize: { width, height },
|
|
||||||
videoSize: { width: videoWidth, height: videoHeight },
|
|
||||||
cropRegion,
|
|
||||||
borderRadius,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate cropped video dimensions
|
// Calculate cropped video dimensions
|
||||||
const cropStartX = cropRegion.x;
|
const cropStartX = cropRegion.x;
|
||||||
const cropStartY = cropRegion.y;
|
const cropStartY = cropRegion.y;
|
||||||
@@ -325,9 +316,11 @@ export class FrameRenderer {
|
|||||||
const croppedVideoWidth = videoWidth * (cropEndX - cropStartX);
|
const croppedVideoWidth = videoWidth * (cropEndX - cropStartX);
|
||||||
const croppedVideoHeight = videoHeight * (cropEndY - cropStartY);
|
const croppedVideoHeight = videoHeight * (cropEndY - cropStartY);
|
||||||
|
|
||||||
// Calculate scale to fit in viewport (using VIEWPORT_SCALE from constants)
|
// Calculate scale to fit in viewport
|
||||||
const viewportWidth = width * 0.8; // VIEWPORT_SCALE = 0.8
|
// Padding is a percentage (0-100), where 50% ~ 0.8 scale
|
||||||
const viewportHeight = height * 0.8;
|
const paddingScale = 1.0 - (padding / 100) * 0.4;
|
||||||
|
const viewportWidth = width * paddingScale;
|
||||||
|
const viewportHeight = height * paddingScale;
|
||||||
const scale = Math.min(viewportWidth / croppedVideoWidth, viewportHeight / croppedVideoHeight);
|
const scale = Math.min(viewportWidth / croppedVideoWidth, viewportHeight / croppedVideoHeight);
|
||||||
|
|
||||||
// Position video sprite
|
// Position video sprite
|
||||||
@@ -348,22 +341,6 @@ export class FrameRenderer {
|
|||||||
this.videoContainer.y = centerOffsetY;
|
this.videoContainer.y = centerOffsetY;
|
||||||
|
|
||||||
// Update mask
|
// Update mask
|
||||||
// Scale the border radius if needed?
|
|
||||||
// In preview, we use the raw pixel value from the slider.
|
|
||||||
// In export, the canvas might be much larger (e.g. 4K vs 800px preview).
|
|
||||||
// If we use the raw value (e.g. 20px), it will look tiny on 4K.
|
|
||||||
// We should probably scale it based on the resolution ratio relative to a "standard" preview size (e.g. 1920x1080 or similar).
|
|
||||||
// Or, we can assume the user sees it on a ~1000px wide preview.
|
|
||||||
// Let's scale it by (width / 1280) as a rough heuristic to match visual appearance?
|
|
||||||
// Actually, let's just use the raw value for now as requested "fine grain control".
|
|
||||||
// If the user sets 20px, they might expect 20px.
|
|
||||||
// BUT, if they are editing on a small screen and exporting to 4K, 20px will look different.
|
|
||||||
// Let's stick to raw value first as it's safer than guessing.
|
|
||||||
// Wait, the previous hardcoded value was percentage based: radius = min(w, h) * 0.02
|
|
||||||
// If I use raw pixels, I break that "responsiveness".
|
|
||||||
// However, the slider is in pixels (0-40).
|
|
||||||
// I will use the raw value for now.
|
|
||||||
|
|
||||||
this.maskGraphics.clear();
|
this.maskGraphics.clear();
|
||||||
this.maskGraphics.roundRect(0, 0, croppedDisplayWidth, croppedDisplayHeight, borderRadius);
|
this.maskGraphics.roundRect(0, 0, croppedDisplayWidth, croppedDisplayHeight, borderRadius);
|
||||||
this.maskGraphics.fill({ color: 0xffffff });
|
this.maskGraphics.fill({ color: 0xffffff });
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ interface VideoExporterConfig extends ExportConfig {
|
|||||||
showBlur: boolean;
|
showBlur: boolean;
|
||||||
motionBlurEnabled?: boolean;
|
motionBlurEnabled?: boolean;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
|
padding?: number;
|
||||||
videoPadding?: number;
|
videoPadding?: number;
|
||||||
cropRegion: CropRegion;
|
cropRegion: CropRegion;
|
||||||
onProgress?: (progress: ExportProgress) => void;
|
onProgress?: (progress: ExportProgress) => void;
|
||||||
@@ -89,6 +90,7 @@ export class VideoExporter {
|
|||||||
showBlur: this.config.showBlur,
|
showBlur: this.config.showBlur,
|
||||||
motionBlurEnabled: this.config.motionBlurEnabled,
|
motionBlurEnabled: this.config.motionBlurEnabled,
|
||||||
borderRadius: this.config.borderRadius,
|
borderRadius: this.config.borderRadius,
|
||||||
|
padding: this.config.padding,
|
||||||
cropRegion: this.config.cropRegion,
|
cropRegion: this.config.cropRegion,
|
||||||
videoWidth: videoInfo.width,
|
videoWidth: videoInfo.width,
|
||||||
videoHeight: videoInfo.height,
|
videoHeight: videoInfo.height,
|
||||||
|
|||||||
Reference in New Issue
Block a user