From 9afa27468f9723fa631b97107a047e76f78920a5 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 23 Sep 2020 21:07:52 +0200 Subject: [PATCH] Implement better image scaling and blurring #60 --- README.md | 13 ++++++-- examples/image.json5 | 19 ++++++++++++ sources/fabric/fabricFrameSources.js | 46 +++++++++++++++++++++------- sources/videoFrameSource.js | 2 +- 4 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 examples/image.json5 diff --git a/README.md b/README.md index 36126e2..c22f49f 100644 --- a/README.md +++ b/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 diff --git a/examples/image.json5 b/examples/image.json5 new file mode 100644 index 0000000..d040e26 --- /dev/null +++ b/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' }] }, + ], +} diff --git a/sources/fabric/fabricFrameSources.js b/sources/fabric/fabricFrameSources.js index 11a2d3d..e9aba40 100644 --- a/sources/fabric/fabricFrameSources.js +++ b/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(); } diff --git a/sources/videoFrameSource.js b/sources/videoFrameSource.js index 6017b08..fbe0336 100644 --- a/sources/videoFrameSource.js +++ b/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}`;