Browse Source

Fix virtual background image being stretched instead of cropped

If the source values are omitted by default "drawImage" draws the whole
source onto the canvas, therefore stretching it if the background and
the canvas do not have the same aspect ratio. Now the background image
is cropped and centered as needed to cover the whole canvas while
maintaining the original aspect ratio of the background.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
pull/9549/head
Daniel Calviño Sánchez 3 years ago
parent
commit
7f0d902ab5
  1. 69
      src/utils/media/effects/virtual-background/JitsiStreamBackgroundEffect.js
  2. 68
      src/utils/media/effects/virtual-background/JitsiStreamBackgroundEffect.spec.js

69
src/utils/media/effects/virtual-background/JitsiStreamBackgroundEffect.js

@ -257,14 +257,35 @@ export default class JitsiStreamBackgroundEffect {
if (backgroundType === VIRTUAL_BACKGROUND.BACKGROUND_TYPE.IMAGE
|| backgroundType === VIRTUAL_BACKGROUND.BACKGROUND_TYPE.VIDEO
|| backgroundType === VIRTUAL_BACKGROUND.BACKGROUND_TYPE.VIDEO_STREAM) {
let source
let sourceWidthOriginal
let sourceHeightOriginal
if (backgroundType === VIRTUAL_BACKGROUND.BACKGROUND_TYPE.IMAGE) {
source = this._virtualImage
sourceWidthOriginal = source.naturalWidth
sourceHeightOriginal = source.naturalHeight
} else {
source = this._virtualVideo
sourceWidthOriginal = source.videoWidth
sourceHeightOriginal = source.videoHeight
}
const destinationWidth = this._outputCanvasElement.width
const destinationHeight = this._outputCanvasElement.height
const [sourceX, sourceY, sourceWidth, sourceHeight] = JitsiStreamBackgroundEffect.getSourcePropertiesForDrawingBackgroundImage(sourceWidthOriginal, sourceHeightOriginal, destinationWidth, destinationHeight)
this._outputCanvasCtx.drawImage(
backgroundType === VIRTUAL_BACKGROUND.BACKGROUND_TYPE.IMAGE
? this._virtualImage
: this._virtualVideo,
source,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
0,
0,
this._outputCanvasElement.width,
this._outputCanvasElement.height
destinationWidth,
destinationHeight
)
} else {
this._outputCanvasCtx.filter = `blur(${backgroundBlurValue}px)`
@ -272,6 +293,44 @@ export default class JitsiStreamBackgroundEffect {
}
}
/**
* Returns the coordinates, width and height to draw the background image
* onto the canvas.
*
* The background image is cropped and centered as needed to cover the whole
* canvas while maintaining the original aspect ratio of the background.
*
* @param {number} sourceWidth the width of the source image
* @param {number} sourceHeight the height of the source image
* @param {number} destinationWidth the width of the destination canvas
* @param {number} destinationHeight the height of the destination canvas
* @return {Array} the X and Y coordinates, width and height of the source
* image after cropping and centering
*/
static getSourcePropertiesForDrawingBackgroundImage(sourceWidth, sourceHeight, destinationWidth, destinationHeight) {
let croppedSourceX = 0
let croppedSourceY = 0
let croppedSourceWidth = sourceWidth
let croppedSourceHeight = sourceHeight
if (sourceWidth <= 0 || sourceHeight <= 0 || destinationWidth <= 0 || destinationHeight <= 0) {
return [croppedSourceX, croppedSourceY, croppedSourceWidth, croppedSourceHeight]
}
const sourceAspectRatio = sourceWidth / sourceHeight
const destinationAspectRatio = destinationWidth / destinationHeight
if (sourceAspectRatio > destinationAspectRatio) {
croppedSourceWidth = sourceHeight * destinationAspectRatio
croppedSourceX = (sourceWidth - croppedSourceWidth) / 2
} else {
croppedSourceHeight = sourceWidth / destinationAspectRatio
croppedSourceY = (sourceHeight - croppedSourceHeight) / 2
}
return [croppedSourceX, croppedSourceY, croppedSourceWidth, croppedSourceHeight]
}
/**
* Represents the run Tensorflow Interference.
* Worker partly

68
src/utils/media/effects/virtual-background/JitsiStreamBackgroundEffect.spec.js

@ -0,0 +1,68 @@
/**
*
* @copyright Copyright (c) 2023, Daniel Calviño Sánchez (danxuliu@gmail.com)
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import JitsiStreamBackgroundEffect from './JitsiStreamBackgroundEffect.js'
describe('JitsiStreamBackgroundEffect', () => {
describe('getSourcePropertiesForDrawingBackgroundImage', () => {
test.each([
['landscape source and landscape destination, wider aspect ratio source, wider and higher source', [1200, 500], [300, 200], [225, 0], [750, 500]],
['landscape source and landscape destination, wider aspect ratio source, wider source', [450, 150], [300, 200], [112.5, 0], [225, 150]],
['landscape source and landscape destination, wider aspect ratio source, same width', [300, 100], [300, 200], [75, 0], [150, 100]],
['landscape source and landscape destination, wider aspect ratio source, narrower source', [200, 50], [300, 200], [62.5, 0], [75, 50]],
['landscape source and landscape destination, wider aspect ratio destination, wider and higher destination', [300, 200], [1200, 500], [0, 37.5], [300, 125]],
['landscape source and landscape destination, wider aspect ratio destination, wider destination', [300, 200], [450, 150], [0, 50], [300, 100]],
['landscape source and landscape destination, wider aspect ratio destination, same width', [300, 200], [300, 100], [0, 50], [300, 100]],
['landscape source and landscape destination, wider aspect ratio destination, narrower destination', [300, 200], [200, 50], [0, 62.5], [300, 75]],
['landscape source and portrait destination, wider and higher source', [1200, 500], [201, 300], [432.5, 0], [335, 500]],
['landscape source and portrait destination, wider source', [450, 150], [200, 300], [175, 0], [100, 150]],
['landscape source and portrait destination, same width', [200, 100.5], [200, 300], [66.5, 0], [67, 100.5]],
['landscape source and portrait destination, narrower source', [150, 51], [200, 300], [58, 0], [34, 51]],
['portrait source and landscape destination, wider and higher source', [501, 1200], [300, 200], [0, 433], [501, 334]],
['portrait source and landscape destination, higher source', [150, 450], [300, 200], [0, 175], [150, 100]],
['portrait source and landscape destination, same height', [99, 200], [300, 200], [0, 67], [99, 66]],
['portrait source and landscape destination, shorter source', [51, 150], [300, 200], [0, 58], [51, 34]],
['portrait source and portrait destination, higher aspect ratio source, wider and higher source', [500, 1200], [200, 300], [0, 225], [500, 750]],
['portrait source and portrait destination, higher aspect ratio source, higher source', [150, 450], [200, 300], [0, 112.5], [150, 225]],
['portrait source and portrait destination, higher aspect ratio source, same height', [100, 300], [200, 300], [0, 75], [100, 150]],
['portrait source and portrait destination, higher aspect ratio source, shorter source', [50, 200], [200, 300], [0, 62.5], [50, 75]],
['portrait source and portrait destination, higher aspect ratio destination, wider and higher destination', [200, 300], [500, 1200], [37.5, 0], [125, 300]],
['portrait source and portrait destination, higher aspect ratio destination, higher destination', [200, 300], [150, 450], [50, 0], [100, 300]],
['portrait source and portrait destination, higher aspect ratio destination, same height', [200, 300], [100, 300], [50, 0], [100, 300]],
['portrait source and portrait destination, higher aspect ratio destination, shorter destination', [200, 300], [50, 200], [62.5, 0], [75, 300]],
['invalid source width', [0, 200], [100, 50], [0, 0], [0, 200]],
['invalid source height', [200, 0], [100, 50], [0, 0], [200, 0]],
['invalid destination width', [100, 50], [0, 200], [0, 0], [100, 50]],
['invalid destination height', [100, 50], [200, 0], [0, 0], [100, 50]],
])('%s', (name, [sourceWidth, sourceHeight], [destinationWidth, destinationHeight], [expectedSourceX, expectedSourceY], [expectedSourceWidth, expectedSourceHeight]) => {
let sourceX
let sourceY
[sourceX, sourceY, sourceWidth, sourceHeight] = JitsiStreamBackgroundEffect.getSourcePropertiesForDrawingBackgroundImage(sourceWidth, sourceHeight, destinationWidth, destinationHeight)
expect(sourceX).toBe(expectedSourceX)
expect(sourceY).toBe(expectedSourceY)
expect(sourceWidth).toBe(expectedSourceWidth)
expect(sourceHeight).toBe(expectedSourceHeight)
})
})
})
Loading…
Cancel
Save