Browse Source

Implement better image scaling and blurring #60

stateless
Mikael Finstad 6 years ago
parent
commit
9afa27468f
  1. 13
      README.md
  2. 19
      examples/image.json5
  3. 46
      sources/fabric/fabricFrameSources.js
  4. 2
      sources/videoFrameSource.js

13
README.md

@ -188,7 +188,7 @@ For video layers, if parent `clip.duration` is specified, the video will be slow
| Parameter | Description | Default | |
|-|-|-|-|
| `path` | Path to video file | | |
| `resizeMode` | One of `cover`, `contain`, `stretch` | `contain` | |
| `resizeMode` | See [Resize modes](#resize-modes) | | |
| `cutFrom` | Time value to cut from | `0` | sec |
| `cutTo` | Time value to cut to | *end of video* | sec |
| `backgroundColor` | Background of letterboxing | `#000000` | |
@ -207,17 +207,18 @@ Audio layers will be mixed together. If `cutFrom`/`cutTo` is set, the resulting
#### Layer type 'image'
Full screen image (auto letterboxed)
Full screen image
| Parameter | Description | Default | |
|-|-|-|-|
| `path` | Path to image file | | |
| `resizeMode` | See [Resize modes](#resize-modes) | | |
See also See [Ken Burns parameters](#ken-burns-parameters).
#### Layer type 'image-overlay'
Image overlay with a custom position on the screen.
Image overlay with a custom position and size on the screen.
| Parameter | Description | Default | |
|-|-|-|-|
@ -296,6 +297,12 @@ Loads a GLSL shader. See [gl.json5](https://github.com/mifi/editly/blob/master/e
- `fragmentPath`
- `vertexPath` (optional)
### Resize modes
`resizeMode` - How to fit image to screen. Can be one of `contain`, `contain-blur`, `cover`, `stretch`. Default `contain-blur`.
See [image.json5](https://github.com/mifi/editly/blob/master/examples/image.json5)
### Position parameter
Certain layers support the position parameter

19
examples/image.json5

@ -0,0 +1,19 @@
{
width: 600,
height: 300,
outPath: './image.mp4',
defaults: {
transition: null,
duration: 0.2,
},
clips: [
{ layers: [{ type: 'image', path: './assets/pano.jpg' }] },
{ layers: [{ type: 'image', path: './assets/vertical.jpg' }] },
{ layers: [{ type: 'fill-color', color: 'white' }, { type: 'image', path: './assets/pano.jpg', resizeMode: 'contain' }] },
{ layers: [{ type: 'fill-color', color: 'white' }, { type: 'image', path: './assets/vertical.jpg', resizeMode: 'contain' }] },
{ layers: [{ type: 'image', path: './assets/pano.jpg', resizeMode: 'cover' }] },
{ layers: [{ type: 'image', path: './assets/vertical.jpg', resizeMode: 'cover' }] },
{ layers: [{ type: 'image', path: './assets/pano.jpg', resizeMode: 'stretch' }] },
{ layers: [{ type: 'image', path: './assets/vertical.jpg', resizeMode: 'stretch' }] },
],
}

46
sources/fabric/fabricFrameSources.js

@ -20,7 +20,7 @@ function getZoomParams({ progress, zoomDirection, zoomAmount }) {
}
async function imageFrameSource({ verbose, params, width, height }) {
const { path, zoomDirection = 'in', zoomAmount = 0.1 } = params;
const { path, zoomDirection = 'in', zoomAmount = 0.1, resizeMode = 'contain-blur' } = params;
if (verbose) console.log('Loading', path);
@ -33,28 +33,52 @@ async function imageFrameSource({ verbose, params, width, height }) {
top: height / 2,
});
let blurredImg;
// Blurred version
const blurredImg = getImg();
blurredImg.filters = [new fabric.Image.filters.Resize({ scaleX: 0.01, scaleY: 0.01 })];
blurredImg.applyFilters();
if (blurredImg.height > blurredImg.width) blurredImg.scaleToWidth(width);
else blurredImg.scaleToHeight(height);
if (resizeMode === 'contain-blur') {
blurredImg = getImg();
if (verbose) console.log('Blurring background');
blurredImg.filters = [
// It is much faster on large images to first resize, but quality is almost the same
new fabric.Image.filters.Resize({ scaleX: 0.1, scaleY: 0.1 }),
new fabric.Image.filters.Blur({ blur: 0.1 }),
];
blurredImg.applyFilters();
// Resize it to fit
blurredImg.setOptions({ scaleX: width / blurredImg.width, scaleY: height / blurredImg.height });
}
async function onRender(progress, canvas) {
const img = getImg();
const scaleFactor = getZoomParams({ progress, zoomDirection, zoomAmount });
if (img.height > img.width) img.scaleToHeight(height * scaleFactor);
else img.scaleToWidth(width * scaleFactor);
const ratioW = width / img.width;
const ratioH = height / img.height;
if (['contain', 'contain-blur'].includes(resizeMode)) {
if (ratioW > ratioH) {
img.scaleToHeight(height * scaleFactor);
} else {
img.scaleToWidth(width * scaleFactor);
}
} else if (resizeMode === 'cover') {
if (ratioW > ratioH) {
img.scaleToWidth(width * scaleFactor);
} else {
img.scaleToHeight(height * scaleFactor);
}
} else if (resizeMode === 'stretch') {
img.setOptions({ scaleX: width / img.width, scaleY: height / img.height });
}
canvas.add(blurredImg);
if (blurredImg) canvas.add(blurredImg);
canvas.add(img);
}
function onClose() {
blurredImg.dispose();
if (blurredImg) blurredImg.dispose();
// imgData.dispose();
}

2
sources/videoFrameSource.js

@ -24,7 +24,7 @@ module.exports = async ({ width, height, channels, framerateStr, verbose, ffmpeg
let scaleFilter;
if (resizeMode === 'stretch') scaleFilter = `scale=${width}:${height}`;
// https://superuser.com/questions/891145/ffmpeg-upscale-and-letterbox-a-video/891478
else if (resizeMode === 'contain') scaleFilter = `scale=(iw*sar)*min(${width}/(iw*sar)\\,${height}/ih):ih*min(${width}/(iw*sar)\\,${height}/ih), pad=${width}:${height}:(${width}-iw*min(${width}/iw\\,${height}/ih))/2:(${height}-ih*min(${width}/iw\\,${height}/ih))/2:${backgroundColor}`;
else if (resizeMode === 'contain' || resizeMode === 'contain-blur') scaleFilter = `scale=(iw*sar)*min(${width}/(iw*sar)\\,${height}/ih):ih*min(${width}/(iw*sar)\\,${height}/ih), pad=${width}:${height}:(${width}-iw*min(${width}/iw\\,${height}/ih))/2:(${height}-ih*min(${width}/iw\\,${height}/ih))/2:${backgroundColor}`;
// Cover: https://unix.stackexchange.com/a/192123
else scaleFilter = `scale=(iw*sar)*max(${width}/(iw*sar)\\,${height}/ih):ih*max(${width}/(iw*sar)\\,${height}/ih),crop=${width}:${height}`;

Loading…
Cancel
Save