feat: added file type and quality options to rendering panel (#50)

The quality setting won't effect PNG, as I believe PNG files have to use
for a quality setting. The setting input will then be inert when PNG,
the default, is used. There are other design options available, but this
is the simplest option that avoids frustration for the user. The
image/webp option will not work on Safari, and there is no check for
this occurance. The best option would be a proactive error message.

Closes #24
This commit is contained in:
Ross Esmond
2022-08-25 09:19:17 -05:00
committed by GitHub
parent db50531f12
commit bee71ef267
4 changed files with 60 additions and 5 deletions

View File

@@ -1,7 +1,7 @@
import {Scene, SceneDescription} from './scenes';
import {Meta, Metadata} from './Meta';
import {EventDispatcher, ValueDispatcher} from './events';
import {Size, CanvasColorSpace} from './types';
import {Size, CanvasColorSpace, CanvasOutputMimeType} from './types';
import {AudioManager} from './media';
import {ifHot} from './utils';
@@ -72,6 +72,14 @@ export class Project {
this.updateCanvas();
}
public set fileType(value: CanvasOutputMimeType) {
this._fileType = value;
}
public set quality(value: number) {
this._quality = value;
}
public set speed(value: number) {
this._speed = value;
this.reloadAll();
@@ -119,6 +127,8 @@ export class Project {
private readonly renderLookup: Record<number, Callback> = {};
private _resolutionScale = 1;
private _colorSpace: CanvasColorSpace = 'srgb';
private _fileType: CanvasOutputMimeType = 'image/png';
private _quality = 1;
private _speed = 1;
private framesPerSeconds = 30;
private readonly sceneLookup: Record<string, Scene> = {};
@@ -315,8 +325,8 @@ export class Project {
};
hot.send('motion-canvas:export', {
frame,
data: this.canvas.toDataURL('image/png'),
mimeType: 'image/png',
data: this.canvas.toDataURL(this._fileType, this._quality),
mimeType: this._fileType,
});
}),
);

View File

@@ -1,6 +1,6 @@
import type {Project} from '../Project';
import {EventDispatcher, ValueDispatcher} from '../events';
import type {CanvasColorSpace} from '../types';
import type {CanvasColorSpace, CanvasOutputMimeType} from '../types';
const MAX_AUDIO_DESYNC = 1 / 50;
@@ -18,6 +18,8 @@ export interface PlayerState extends Record<string, unknown> {
fps: number;
scale: number;
colorSpace: CanvasColorSpace;
fileType: CanvasOutputMimeType;
quality: number;
}
interface PlayerCommands {
@@ -43,6 +45,8 @@ export class Player {
fps: 30,
scale: 1,
colorSpace: 'srgb',
fileType: 'image/png',
quality: 1,
});
public get onFrameChanged() {
@@ -174,6 +178,16 @@ export class Player {
this.project.render();
}
public setFileType(fileType: CanvasOutputMimeType) {
this.project.fileType = fileType;
this.updateState({fileType});
}
public setQuality(quality: number) {
this.project.quality = quality;
this.updateState({quality});
}
public requestPreviousFrame(): void {
this.commands.seek = this.frame.current - this.state.current.speed;
}

View File

@@ -1 +1,2 @@
export type CanvasColorSpace = 'srgb' | 'display-p3';
export type CanvasOutputMimeType = 'image/png' | 'image/jpeg' | 'image/webp';

View File

@@ -2,7 +2,10 @@ import {usePlayerState} from '../../hooks';
import {Button, Group, Input, Label, Select} from '../controls';
import {Pane} from '../tabs';
import {usePlayer} from '../../contexts';
import type {CanvasColorSpace} from '@motion-canvas/core/lib/types';
import type {
CanvasColorSpace,
CanvasOutputMimeType,
} from '@motion-canvas/core/lib/types';
export function Rendering() {
const player = usePlayer();
@@ -20,6 +23,12 @@ export function Rendering() {
{value: 'display-p3', text: 'DCI-P3'},
];
const fileTypes = [
{value: 'image/png', text: 'png'},
{value: 'image/jpeg', text: 'jpeg'},
{value: 'image/webp', text: 'webp'},
];
return (
<Pane title="Rendering">
<Group>
@@ -94,6 +103,27 @@ export function Rendering() {
onChange={value => player.setColorSpace(value as CanvasColorSpace)}
/>
</Group>
<Group>
<Label>File Type</Label>
<Select
options={fileTypes}
value={state.fileType}
onChange={value => player.setFileType(value as CanvasOutputMimeType)}
/>
</Group>
<Group>
<Label>Quality</Label>
<Input
type="number"
min={0}
max={1}
value={state.quality}
onChange={event => {
const value = parseFloat((event.target as HTMLInputElement).value);
player.setQuality(value);
}}
/>
</Group>
<Group>
<Label />
<Button main onClick={() => player.toggleRendering()}>